diff options
53 files changed, 870 insertions, 567 deletions
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index c47e47c74e9..4d67bb9448c 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -17,7 +17,6 @@ Layout/LineLength: - 'app/controllers/concerns/analytics/cycle_analytics/stage_actions.rb' - 'app/controllers/concerns/clientside_preview_csp.rb' - 'app/controllers/concerns/confirm_email_warning.rb' - - 'app/controllers/concerns/cycle_analytics_params.rb' - 'app/controllers/concerns/integrations/actions.rb' - 'app/controllers/concerns/issuable_actions.rb' - 'app/controllers/concerns/issuable_collections.rb' @@ -328,7 +327,6 @@ Layout/LineLength: - 'app/models/concerns/enums/vulnerability.rb' - 'app/models/concerns/fast_destroy_all.rb' - 'app/models/concerns/group_descendant.rb' - - 'app/models/concerns/has_user_type.rb' - 'app/models/concerns/id_in_ordered.rb' - 'app/models/concerns/ignorable_columns.rb' - 'app/models/concerns/iid_routes.rb' @@ -464,7 +462,6 @@ Layout/LineLength: - 'app/models/wiki_page.rb' - 'app/policies/base_policy.rb' - 'app/policies/global_policy.rb' - - 'app/policies/group_member_policy.rb' - 'app/policies/group_policy.rb' - 'app/policies/project_policy.rb' - 'app/presenters/blob_presenter.rb' @@ -519,12 +516,7 @@ Layout/LineLength: - 'app/services/ci/runners/unregister_runner_service.rb' - 'app/services/clusters/agent_tokens/create_service.rb' - 'app/services/clusters/agents/delete_service.rb' - - 'app/services/clusters/applications/check_progress_service.rb' - - 'app/services/clusters/aws/finalize_creation_service.rb' - - 'app/services/clusters/aws/verify_provision_status_service.rb' - 'app/services/clusters/build_kubernetes_namespace_service.rb' - - 'app/services/clusters/gcp/finalize_creation_service.rb' - - 'app/services/clusters/gcp/verify_provision_status_service.rb' - 'app/services/clusters/integrations/create_service.rb' - 'app/services/clusters/integrations/prometheus_health_check_service.rb' - 'app/services/clusters/kubernetes/create_or_update_service_account_service.rb' @@ -716,7 +708,6 @@ Layout/LineLength: - 'app/workers/merge_request_mergeability_check_worker.rb' - 'app/workers/object_storage/migrate_uploads_worker.rb' - 'app/workers/packages/maven/metadata/sync_worker.rb' - - 'app/workers/personal_access_tokens/expired_notification_worker.rb' - 'app/workers/pipeline_metrics_worker.rb' - 'app/workers/repository_fork_worker.rb' - 'app/workers/repository_import_worker.rb' @@ -765,49 +756,6 @@ Layout/LineLength: - 'danger/roulette/Dangerfile' - 'danger/vue_shared_documentation/Dangerfile' - 'danger/z_metadata/Dangerfile' - - 'db/migrate/20210302103851_add_deployed_deployment_id_index_to_project_pages_metadata.rb' - - 'db/migrate/20210302155904_remove_index_for_security_orchestration_policy.rb' - - 'db/migrate/20210302160544_add_index_to_security_orchestration_policy.rb' - - 'db/migrate/20210305031822_create_dast_site_profile_variables.rb' - - 'db/migrate/20210305182855_create_ci_unit_test_failures.rb' - - 'db/migrate/20210313045845_add_verification_indexes_to_snippet_repositories.rb' - - 'db/migrate/20210316171009_create_packages_helm_file_metadata.rb' - - 'db/migrate/20210317035357_create_dast_profiles_pipelines.rb' - - 'db/migrate/20210317123054_add_throttle_package_registry_columns.rb' - - 'db/migrate/20210323131543_add_external_approval_rule_foreign_key_to_status_check_responses.rb' - - 'db/migrate/20210325152011_add_verification_indexes_to_ci_pipeline_artifacts.rb' - - 'db/migrate/20210326190903_create_vulnerability_finding_evidences.rb' - - 'db/migrate/20210409084242_create_index_on_notes_for_cherry_picked_merge_requests.rb' - - 'db/migrate/20210412111213_create_security_orchestration_policy_rule_schedule.rb' - - 'db/migrate/20210414133310_add_bulk_import_export_uploads_table.rb' - - 'db/migrate/20210415142700_add_url_limit_to_pipeline_validation.rb' - - 'db/migrate/20210415172516_create_vulnerability_finding_evidence_requests.rb' - - 'db/migrate/20210416172516_create_vulnerability_finding_evidence_responses.rb' - - 'db/migrate/20210420173030_add_verification_indexes_to_terraform_state_versions.rb' - - 'db/migrate/20210420210642_recreate_index_for_project_deployments_with_environment_id_and_date_at.rb' - - 'db/migrate/20210422142647_add_project_id_next_run_at_index_to_container_expiration_policies.rb' - - 'db/migrate/20210422195929_create_elastic_reindexing_slices.rb' - - 'db/migrate/20210423054022_create_dast_site_profiles_pipelines.rb' - - 'db/migrate/20210423054537_add_dast_site_profile_id_fk_to_dast_site_profiles_pipelines.rb' - - 'db/migrate/20210423054846_add_ci_pipeline_id_fk_to_dast_site_profiles_pipelines.rb' - - 'db/migrate/20210423171304_re_order_fk_source_project_id_in_merge_requests.rb' - - 'db/migrate/20210427062807_add_index_to_batched_migration_jobs_status.rb' - - 'db/migrate/20210427094931_add_execution_order_index_to_batched_background_migration_jobs.rb' - - 'db/migrate/20210429032320_add_escalation_rules.rb' - - 'db/migrate/20210505170152_add_verification_indexes_to_merge_request_diff_details_table.rb' - - 'db/migrate/20210506150833_create_vulnerability_finding_evidence_headers.rb' - - 'db/migrate/20210511104929_add_epic_board_recent_visits_table.rb' - - 'db/migrate/20210511165250_add_foreign_key_to_lfs_objects_projects.rb' - - 'db/migrate/20210512120122_add_pending_builds_table.rb' - - 'db/migrate/20210521073920_drop_devops_adoption_namespace_uniqueness.rb' - - 'db/migrate/20210526181821_add_foreign_key_for_latest_pipeline_id_to_ci_pipelines.rb' - - 'db/migrate/20210527194558_create_ci_job_token_project_scope_links.rb' - - 'db/migrate/20210529164247_change_iterations_title_uniqueness_index.rb' - - 'db/migrate/20210601123341_add_running_builds_table.rb' - - 'db/migrate/20210601125410_add_runners_created_at_index.rb' - - 'db/migrate/20210601132134_remove_partial_index_for_hashed_storage_migration.rb' - - 'db/migrate/20210601133459_replace_runners_contacted_at_index.rb' - - 'db/migrate/20210602122233_add_runners_description_index.rb' - 'db/migrate/20210604032738_create_dast_site_profiles_builds.rb' - 'db/migrate/20210604034354_add_dast_site_profile_id_fk_to_dast_site_profiles_builds.rb' - 'db/migrate/20210604051330_create_dast_scanner_profiles_builds.rb' @@ -911,16 +859,6 @@ Layout/LineLength: - 'db/migrate/20220310101118_update_holder_name_limit.rb' - 'db/migrate/20220314184209_add_group_fk_to_protected_environment_approval_rules.rb' - 'db/migrate/20220314204009_add_approval_rule_fk_to_deployment_approvals.rb' - - 'db/post_migrate/20210328214434_remove_temporary_index_from_vulnerabilities_table.rb' - - 'db/post_migrate/20210401131948_move_container_registry_enabled_to_project_features2.rb' - - 'db/post_migrate/20210402005225_add_source_and_level_index_on_notification_settings.rb' - - 'db/post_migrate/20210407150240_confirm_support_bot_user.rb' - - 'db/post_migrate/20210415155043_move_container_registry_enabled_to_project_features3.rb' - - 'db/post_migrate/20210430121542_backfill_ci_build_trace_sections_for_bigint_conversion.rb' - - 'db/post_migrate/20210505092746_create_partial_covering_index_for_pending_builds.rb' - - 'db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb' - - 'db/post_migrate/20210514063252_schedule_cleanup_orphaned_lfs_objects_projects.rb' - - 'db/post_migrate/20210526160133_remove_segment_selections_table.rb' - 'db/post_migrate/20210606143426_add_index_for_container_registry_access_level.rb' - 'db/post_migrate/20210611080951_fix_missing_traversal_ids.rb' - 'db/post_migrate/20210615234935_fix_batched_migrations_old_format_job_arguments.rb' @@ -968,7 +906,6 @@ Layout/LineLength: - 'db/post_migrate/20211112113300_remove_ci_pipeline_chat_data_fk_on_chat_names.rb' - 'db/post_migrate/20211118194239_drop_invalid_remediations.rb' - 'db/post_migrate/20211201101541_drop_clusters_applications_runners_ci_runners_fk.rb' - - 'db/post_migrate/20211206162601_cleanup_after_add_primary_email_to_emails_if_user_confirmed.rb' - 'db/post_migrate/20211207173510_remove_extra_finding_evidence_tables_foreign_keys.rb' - 'db/post_migrate/20211207173511_remove_extra_finding_evidence_tables.rb' - 'db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb' @@ -1174,7 +1111,6 @@ Layout/LineLength: - 'ee/app/helpers/billing_plans_helper.rb' - 'ee/app/helpers/ee/application_helper.rb' - 'ee/app/helpers/ee/button_helper.rb' - - 'ee/app/helpers/ee/environments_helper.rb' - 'ee/app/helpers/ee/feature_flags_helper.rb' - 'ee/app/helpers/ee/geo_helper.rb' - 'ee/app/helpers/ee/groups/analytics/cycle_analytics_helper.rb' @@ -1428,7 +1364,6 @@ Layout/LineLength: - 'ee/app/services/merge_trains/create_pipeline_service.rb' - 'ee/app/services/merge_trains/refresh_merge_request_service.rb' - 'ee/app/services/personal_access_tokens/rotation_verifier_service.rb' - - 'ee/app/services/projects/licenses/create_policy_service.rb' - 'ee/app/services/projects/mark_for_deletion_service.rb' - 'ee/app/services/projects/update_mirror_service.rb' - 'ee/app/services/resource_events/change_weight_service.rb' @@ -1535,9 +1470,7 @@ Layout/LineLength: - 'ee/lib/api/project_push_rule.rb' - 'ee/lib/api/protected_environments.rb' - 'ee/lib/api/resource_iteration_events.rb' - - 'ee/lib/api/scim.rb' - 'ee/lib/api/status_checks.rb' - - 'ee/lib/api/vulnerability_findings.rb' - 'ee/lib/api/vulnerability_issue_links.rb' - 'ee/lib/ee/api/deployments.rb' - 'ee/lib/ee/api/entities/application_setting.rb' @@ -1589,7 +1522,6 @@ Layout/LineLength: - 'ee/lib/ee/gitlab/ci/pipeline/chain/create_cross_database_associations.rb' - 'ee/lib/ee/gitlab/ci/pipeline/chain/validate/after_config.rb' - 'ee/lib/ee/gitlab/ci/pipeline/chain/validate/security_orchestration_policy.rb' - - 'ee/lib/ee/gitlab/ci/reports/security/reports.rb' - 'ee/lib/ee/gitlab/ci/status/build/manual.rb' - 'ee/lib/ee/gitlab/git_access.rb' - 'ee/lib/ee/gitlab/import_export/after_export_strategies/custom_template_export_import_strategy.rb' @@ -1600,7 +1532,6 @@ Layout/LineLength: - 'ee/lib/ee/gitlab/quick_actions/issue_actions.rb' - 'ee/lib/ee/gitlab/rack_attack.rb' - 'ee/lib/ee/gitlab/repository_size_checker.rb' - - 'ee/lib/ee/gitlab/scim/deprovision_service.rb' - 'ee/lib/ee/gitlab/usage_data.rb' - 'ee/lib/ee/sidebars/groups/panel.rb' - 'ee/lib/ee/sidebars/projects/menus/security_compliance_menu.rb' @@ -1857,7 +1788,6 @@ Layout/LineLength: - 'ee/spec/finders/productivity_analytics_finder_spec.rb' - 'ee/spec/finders/projects/integrations/jira/by_ids_finder_spec.rb' - 'ee/spec/finders/projects/integrations/jira/issues_finder_spec.rb' - - 'ee/spec/finders/requirements_management/requirements_finder_spec.rb' - 'ee/spec/finders/security/findings_finder_spec.rb' - 'ee/spec/finders/security/pipeline_vulnerabilities_finder_spec.rb' - 'ee/spec/finders/security/training_providers/base_url_finder_spec.rb' @@ -2038,14 +1968,12 @@ Layout/LineLength: - 'ee/spec/lib/ee/gitlab/elastic/helper_spec.rb' - 'ee/spec/lib/ee/gitlab/email/handler/service_desk_handler_spec.rb' - 'ee/spec/lib/ee/gitlab/etag_caching/router/rails_spec.rb' - - 'ee/spec/lib/ee/gitlab/git_access_snippet_spec.rb' - 'ee/spec/lib/ee/gitlab/gon_helper_spec.rb' - 'ee/spec/lib/ee/gitlab/group_search_results_spec.rb' - 'ee/spec/lib/ee/gitlab/import_export/project/tree_restorer_spec.rb' - 'ee/spec/lib/ee/gitlab/import_export/wiki_repo_saver_spec.rb' - 'ee/spec/lib/ee/gitlab/repo_path_spec.rb' - 'ee/spec/lib/ee/gitlab/repository_size_checker_spec.rb' - - 'ee/spec/lib/ee/gitlab/scim/deprovision_service_spec.rb' - 'ee/spec/lib/ee/gitlab/security/scan_configuration_spec.rb' - 'ee/spec/lib/ee/gitlab/url_builder_spec.rb' - 'ee/spec/lib/ee/gitlab/usage/service_ping/payload_keys_processor_spec.rb' @@ -2264,7 +2192,6 @@ Layout/LineLength: - 'ee/spec/models/ee/project_authorization_spec.rb' - 'ee/spec/models/ee/protected_branch_spec.rb' - 'ee/spec/models/ee/service_desk_setting_spec.rb' - - 'ee/spec/models/ee/user_highest_role_spec.rb' - 'ee/spec/models/ee/user_spec.rb' - 'ee/spec/models/ee/vulnerability_spec.rb' - 'ee/spec/models/elastic/migration_record_spec.rb' @@ -2424,7 +2351,6 @@ Layout/LineLength: - 'ee/spec/requests/api/graphql/project/incident_management/escalation_policies_spec.rb' - 'ee/spec/requests/api/graphql/project/incident_management/escalation_policy/rules_spec.rb' - 'ee/spec/requests/api/graphql/project/incident_management/oncall_shifts_spec.rb' - - 'ee/spec/requests/api/graphql/project/issues_spec.rb' - 'ee/spec/requests/api/graphql/project/pipeline/security_report_summary_spec.rb' - 'ee/spec/requests/api/graphql/project/requirements_management/requirements_spec.rb' - 'ee/spec/requests/api/graphql/project/vulnerability_severities_count_spec.rb' @@ -2460,7 +2386,6 @@ Layout/LineLength: - 'ee/spec/requests/api/related_epic_links_spec.rb' - 'ee/spec/requests/api/releases_spec.rb' - 'ee/spec/requests/api/resource_iteration_events_spec.rb' - - 'ee/spec/requests/api/scim_spec.rb' - 'ee/spec/requests/api/search_spec.rb' - 'ee/spec/requests/api/settings_spec.rb' - 'ee/spec/requests/api/status_checks_spec.rb' @@ -2585,7 +2510,6 @@ Layout/LineLength: - 'ee/spec/services/ee/issues/move_service_spec.rb' - 'ee/spec/services/ee/issues/update_service_spec.rb' - 'ee/spec/services/ee/members/destroy_service_spec.rb' - - 'ee/spec/services/ee/merge_requests/after_create_service_spec.rb' - 'ee/spec/services/ee/merge_requests/create_from_vulnerability_data_service_spec.rb' - 'ee/spec/services/ee/merge_requests/post_merge_service_spec.rb' - 'ee/spec/services/ee/merge_requests/refresh_service_spec.rb' @@ -2828,7 +2752,6 @@ Layout/LineLength: - 'ee/spec/workers/geo/destroy_worker_spec.rb' - 'ee/spec/workers/geo/project_sync_worker_spec.rb' - 'ee/spec/workers/geo/prune_event_log_worker_spec.rb' - - 'ee/spec/workers/geo/registry_sync_worker_spec.rb' - 'ee/spec/workers/geo/repositories_clean_up_worker_spec.rb' - 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb' - 'ee/spec/workers/geo/repository_verification/primary/shard_worker_spec.rb' @@ -3035,7 +2958,6 @@ Layout/LineLength: - 'lib/gitlab/auth/o_auth/user.rb' - 'lib/gitlab/auth/saml/auth_hash.rb' - 'lib/gitlab/auth/user_access_denied_reason.rb' - - 'lib/gitlab/background_migration/add_primary_email_to_emails_if_user_confirmed.rb' - 'lib/gitlab/background_migration/backfill_issue_search_data.rb' - 'lib/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb' - 'lib/gitlab/background_migration/backfill_snippet_repositories.rb' @@ -3084,7 +3006,6 @@ Layout/LineLength: - 'lib/gitlab/ci/config/entry/trigger.rb' - 'lib/gitlab/ci/config/external/file/project.rb' - 'lib/gitlab/ci/config/external/file/remote.rb' - - 'lib/gitlab/ci/config/external/mapper.rb' - 'lib/gitlab/ci/parsers/coverage/cobertura.rb' - 'lib/gitlab/ci/parsers/coverage/sax_document.rb' - 'lib/gitlab/ci/parsers/security/common.rb' @@ -3099,7 +3020,6 @@ Layout/LineLength: - 'lib/gitlab/ci/pipeline/seed/build.rb' - 'lib/gitlab/ci/reports/codequality_reports.rb' - 'lib/gitlab/ci/reports/security/finding.rb' - - 'lib/gitlab/ci/reports/security/reports.rb' - 'lib/gitlab/ci/reports/test_case.rb' - 'lib/gitlab/ci/reports/test_suite.rb' - 'lib/gitlab/ci/reports/test_suite_comparer.rb' @@ -3307,7 +3227,6 @@ Layout/LineLength: - 'lib/gitlab/quick_actions/relate_actions.rb' - 'lib/gitlab/rack_attack.rb' - 'lib/gitlab/redis/wrapper.rb' - - 'lib/gitlab/reference_extractor.rb' - 'lib/gitlab/regex.rb' - 'lib/gitlab/relative_positioning/item_context.rb' - 'lib/gitlab/repository_size_error_message.rb' @@ -3424,15 +3343,9 @@ Layout/LineLength: - 'qa/qa/resource/protected_branch.rb' - 'qa/qa/resource/registry_repository.rb' - 'qa/qa/resource/repository/push.rb' - - 'qa/qa/resource/reusable.rb' - - 'qa/qa/resource/reusable_collection.rb' - - 'qa/qa/resource/reusable_group.rb' - - 'qa/qa/resource/reusable_project.rb' - - 'qa/qa/resource/runner.rb' - 'qa/qa/resource/snippet.rb' - 'qa/qa/resource/wiki/group_page.rb' - 'qa/qa/runtime/api/repository_storage_moves.rb' - - 'qa/qa/runtime/application_settings.rb' - 'qa/qa/runtime/env.rb' - 'qa/qa/runtime/feature.rb' - 'qa/qa/runtime/fixtures.rb' @@ -3547,9 +3460,7 @@ Layout/LineLength: - 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/purchase/free_trial_spec.rb' - - 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/purchase/purchase_ci_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/purchase/purchase_storage_spec.rb' - - 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/purchase/upgrade_group_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/11_fulfillment/purchase/user_registration_billing_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/13_secure/enable_scanning_from_configuration_spec.rb' - 'qa/qa/specs/features/ee/browser_ui/13_secure/license_compliance_spec.rb' @@ -3630,7 +3541,6 @@ Layout/LineLength: - 'scripts/changed-feature-flags' - 'scripts/failed_tests.rb' - 'scripts/flaky_examples/prune-old-flaky-examples' - - 'scripts/lib/gitlab.rb' - 'scripts/lint_templates_bash.rb' - 'scripts/no-dir-check' - 'scripts/perf/query_limiting_report.rb' @@ -3744,7 +3654,6 @@ Layout/LineLength: - 'spec/db/schema_spec.rb' - 'spec/deprecation_toolkit_env.rb' - 'spec/experiments/concerns/project_commit_count_spec.rb' - - 'spec/factories/ci/builds.rb' - 'spec/factories/ci/job_artifacts.rb' - 'spec/factories/ci/pipelines.rb' - 'spec/factories/ci/reports/codequality_degradations.rb' @@ -3840,7 +3749,6 @@ Layout/LineLength: - 'spec/features/markdown/gitlab_flavored_markdown_spec.rb' - 'spec/features/markdown/metrics_spec.rb' - 'spec/features/merge_request/batch_comments_spec.rb' - - 'spec/features/merge_request/maintainer_edits_fork_spec.rb' - 'spec/features/merge_request/user_accepts_merge_request_spec.rb' - 'spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb' - 'spec/features/merge_request/user_assigns_themselves_spec.rb' @@ -3961,7 +3869,6 @@ Layout/LineLength: - 'spec/features/security/project/snippet/public_access_spec.rb' - 'spec/features/signed_commits_spec.rb' - 'spec/features/snippets/embedded_snippet_spec.rb' - - 'spec/features/snippets/spam_snippets_spec.rb' - 'spec/features/snippets/user_edits_snippet_spec.rb' - 'spec/features/task_lists_spec.rb' - 'spec/features/unsubscribe_links_spec.rb' @@ -4172,7 +4079,6 @@ Layout/LineLength: - 'spec/helpers/users_helper_spec.rb' - 'spec/helpers/visibility_level_helper_spec.rb' - 'spec/helpers/webpack_helper_spec.rb' - - 'spec/helpers/wiki_helper_spec.rb' - 'spec/helpers/wiki_page_version_helper_spec.rb' - 'spec/initializers/00_rails_disable_joins_spec.rb' - 'spec/initializers/6_validations_spec.rb' @@ -4300,7 +4206,6 @@ Layout/LineLength: - 'spec/lib/gitlab/auth/user_access_denied_reason_spec.rb' - 'spec/lib/gitlab/auth_spec.rb' - 'spec/lib/gitlab/authorized_keys_spec.rb' - - 'spec/lib/gitlab/background_migration/add_primary_email_to_emails_if_user_confirmed_spec.rb' - 'spec/lib/gitlab/background_migration/backfill_issue_search_data_spec.rb' - 'spec/lib/gitlab/background_migration/backfill_member_namespace_for_group_members_spec.rb' - 'spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb' @@ -4384,7 +4289,6 @@ Layout/LineLength: - 'spec/lib/gitlab/ci/pipeline_object_hierarchy_spec.rb' - 'spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb' - 'spec/lib/gitlab/ci/reports/security/flag_spec.rb' - - 'spec/lib/gitlab/ci/reports/security/reports_spec.rb' - 'spec/lib/gitlab/ci/reports/security/scanner_spec.rb' - 'spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb' - 'spec/lib/gitlab/ci/runner_upgrade_check_spec.rb' @@ -4550,7 +4454,6 @@ Layout/LineLength: - 'spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb' - 'spec/lib/gitlab/import_export/command_line_util_spec.rb' - 'spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb' - - 'spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb' - 'spec/lib/gitlab/import_export/import_failure_service_spec.rb' - 'spec/lib/gitlab/import_export/importer_spec.rb' - 'spec/lib/gitlab/import_export/json/ndjson_reader_spec.rb' @@ -4658,7 +4561,6 @@ Layout/LineLength: - 'spec/lib/gitlab/search_results_spec.rb' - 'spec/lib/gitlab/serializer/pagination_spec.rb' - 'spec/lib/gitlab/serverless/service_spec.rb' - - 'spec/lib/gitlab/shell_spec.rb' - 'spec/lib/gitlab/sidekiq_config/worker_router_spec.rb' - 'spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb' - 'spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb' @@ -4750,10 +4652,6 @@ Layout/LineLength: - 'spec/mailers/emails/releases_spec.rb' - 'spec/mailers/emails/service_desk_spec.rb' - 'spec/mailers/notify_spec.rb' - - 'spec/migrations/20210423160427_schedule_drop_invalid_vulnerabilities_spec.rb' - - 'spec/migrations/20210511142748_schedule_drop_invalid_vulnerabilities2_spec.rb' - - 'spec/migrations/20210514063252_schedule_cleanup_orphaned_lfs_objects_projects_spec.rb' - - 'spec/migrations/20210601073400_fix_total_stage_in_vsa_spec.rb' - 'spec/migrations/20210610153556_delete_legacy_operations_feature_flags_spec.rb' - 'spec/migrations/2021061716138_cascade_delete_freeze_periods_spec.rb' - 'spec/migrations/20210713042000_fix_ci_sources_pipelines_index_names_spec.rb' @@ -4784,19 +4682,11 @@ Layout/LineLength: - 'spec/migrations/add_upvotes_count_index_to_issues_spec.rb' - 'spec/migrations/backfill_all_project_namespaces_spec.rb' - 'spec/migrations/backfill_cadence_id_for_boards_scoped_to_iteration_spec.rb' - - 'spec/migrations/backfill_clusters_integration_prometheus_enabled_spec.rb' - - 'spec/migrations/backfill_escalation_policies_for_oncall_schedules_spec.rb' - - 'spec/migrations/backfill_nuget_temporary_packages_to_processing_status_spec.rb' - 'spec/migrations/backfill_project_namespaces_for_group_spec.rb' - - 'spec/migrations/cleanup_after_add_primary_email_to_emails_if_user_confirmed_spec.rb' - 'spec/migrations/cleanup_after_fixing_issue_when_admin_changed_primary_email_spec.rb' - - 'spec/migrations/cleanup_move_container_registry_enabled_to_project_feature_spec.rb' - - 'spec/migrations/confirm_support_bot_user_spec.rb' - - 'spec/migrations/delete_security_findings_without_uuid_spec.rb' - 'spec/migrations/finalize_project_namespaces_backfill_spec.rb' - 'spec/migrations/fix_and_backfill_project_namespaces_for_projects_with_duplicate_name_spec.rb' - 'spec/migrations/fix_batched_migrations_old_format_job_arguments_spec.rb' - - 'spec/migrations/populate_dismissal_information_for_vulnerabilities_spec.rb' - 'spec/migrations/re_schedule_latest_pipeline_id_population_with_all_security_related_artifact_types_spec.rb' - 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_features_spec.rb' - 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_with_new_features_spec.rb' @@ -4805,11 +4695,8 @@ Layout/LineLength: - 'spec/migrations/rename_services_to_integrations_spec.rb' - 'spec/migrations/replace_external_wiki_triggers_spec.rb' - 'spec/migrations/reset_severity_levels_to_new_default_spec.rb' - - 'spec/migrations/schedule_add_primary_email_to_emails_if_user_confirmed_spec.rb' - 'spec/migrations/schedule_recalculate_vulnerability_finding_signatures_for_findings_spec.rb' - 'spec/migrations/schedule_update_timelogs_null_spent_at_spec.rb' - - 'spec/migrations/schedule_update_timelogs_project_id_spec.rb' - - 'spec/migrations/schedule_update_users_where_two_factor_auth_required_from_group_spec.rb' - 'spec/migrations/update_invalid_member_states_spec.rb' - 'spec/models/active_session_spec.rb' - 'spec/models/acts_as_taggable_on/tag_spec.rb' @@ -4832,12 +4719,9 @@ Layout/LineLength: - 'spec/models/ci/build_spec.rb' - 'spec/models/ci/build_trace_chunk_spec.rb' - 'spec/models/ci/daily_build_group_report_result_spec.rb' - - 'spec/models/ci/freeze_period_status_spec.rb' - 'spec/models/ci/group_variable_spec.rb' - 'spec/models/ci/instance_variable_spec.rb' - 'spec/models/ci/job_artifact_spec.rb' - - 'spec/models/ci/job_token/scope_spec.rb' - - 'spec/models/ci/pipeline_schedule_spec.rb' - 'spec/models/ci/pipeline_spec.rb' - 'spec/models/ci/processable_spec.rb' - 'spec/models/ci/ref_spec.rb' @@ -4859,7 +4743,6 @@ Layout/LineLength: - 'spec/models/concerns/clusters/agents/authorization_config_scopes_spec.rb' - 'spec/models/concerns/deployment_platform_spec.rb' - 'spec/models/concerns/group_descendant_spec.rb' - - 'spec/models/concerns/has_user_type_spec.rb' - 'spec/models/concerns/id_in_ordered_spec.rb' - 'spec/models/concerns/ignorable_columns_spec.rb' - 'spec/models/concerns/integrations/has_data_fields_spec.rb' @@ -5123,7 +5006,6 @@ Layout/LineLength: - 'spec/requests/api/graphql/project/container_repositories_spec.rb' - 'spec/requests/api/graphql/project/issue/designs/designs_spec.rb' - 'spec/requests/api/graphql/project/jira_import_spec.rb' - - 'spec/requests/api/graphql/project/jobs_spec.rb' - 'spec/requests/api/graphql/project/milestones_spec.rb' - 'spec/requests/api/graphql/project/pipeline_spec.rb' - 'spec/requests/api/graphql/project/project_statistics_spec.rb' @@ -5169,7 +5051,6 @@ Layout/LineLength: - 'spec/requests/api/npm_project_packages_spec.rb' - 'spec/requests/api/nuget_group_packages_spec.rb' - 'spec/requests/api/nuget_project_packages_spec.rb' - - 'spec/requests/api/oauth_tokens_spec.rb' - 'spec/requests/api/pages/internal_access_spec.rb' - 'spec/requests/api/pages/private_access_spec.rb' - 'spec/requests/api/pages/public_access_spec.rb' @@ -5220,10 +5101,8 @@ Layout/LineLength: - 'spec/requests/projects/incident_management/pagerduty_incidents_spec.rb' - 'spec/requests/projects/issue_links_controller_spec.rb' - 'spec/requests/projects/issues/discussions_spec.rb' - - 'spec/requests/projects/issues_controller_spec.rb' - 'spec/requests/projects/merge_requests/content_spec.rb' - 'spec/requests/projects/merge_requests/context_commit_diffs_spec.rb' - - 'spec/requests/projects/merge_requests_controller_spec.rb' - 'spec/requests/projects/merge_requests_discussions_spec.rb' - 'spec/requests/projects/merge_requests_spec.rb' - 'spec/requests/projects/metrics/dashboards/builder_spec.rb' @@ -5315,8 +5194,6 @@ Layout/LineLength: - 'spec/services/ci/test_failure_history_service_spec.rb' - 'spec/services/ci/unlock_artifacts_service_spec.rb' - 'spec/services/ci/update_pending_build_service_spec.rb' - - 'spec/services/clusters/aws/fetch_credentials_service_spec.rb' - - 'spec/services/clusters/aws/provision_service_spec.rb' - 'spec/services/clusters/create_service_spec.rb' - 'spec/services/clusters/integrations/prometheus_health_check_service_spec.rb' - 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb' @@ -5496,8 +5373,6 @@ Layout/LineLength: - 'spec/services/projects/import_export/export_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_import_service_spec.rb' - - 'spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb' - 'spec/services/projects/operations/update_service_spec.rb' - 'spec/services/projects/overwrite_project_service_spec.rb' - 'spec/services/projects/transfer_service_spec.rb' @@ -5617,7 +5492,6 @@ Layout/LineLength: - 'spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb' - 'spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb' - 'spec/support/shared_examples/features/2fa_shared_examples.rb' - - 'spec/support/shared_examples/features/container_registry_shared_examples.rb' - 'spec/support/shared_examples/features/discussion_comments_shared_example.rb' - 'spec/support/shared_examples/features/editable_merge_request_shared_examples.rb' - 'spec/support/shared_examples/features/error_tracking_shared_example.rb' @@ -5743,7 +5617,6 @@ Layout/LineLength: - 'spec/support/shared_examples/uploaders/upload_type_shared_examples.rb' - 'spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb' - 'spec/support/shared_examples/workers/concerns/dependency_proxy/cleanup_worker_shared_examples.rb' - - 'spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb' - 'spec/support/shared_examples/workers/gitlab/jira_import/jira_import_workers_shared_examples.rb' - 'spec/support/shared_examples/workers/project_export_shared_examples.rb' - 'spec/support_specs/database/prevent_cross_joins_spec.rb' @@ -5821,7 +5694,6 @@ Layout/LineLength: - 'spec/views/shared/milestones/_issuable.html.haml_spec.rb' - 'spec/views/shared/projects/_project.html.haml_spec.rb' - 'spec/views/shared/snippets/_snippet.html.haml_spec.rb' - - 'spec/views/shared/ssh_keys/_key_details.html.haml_spec.rb' - 'spec/views/shared/wikis/_sidebar.html.haml_spec.rb' - 'spec/workers/analytics/usage_trends/counter_job_worker_spec.rb' - 'spec/workers/authorized_project_update/project_recalculate_per_user_worker_spec.rb' diff --git a/.rubocop_todo/style/format_string.yml b/.rubocop_todo/style/format_string.yml index c1ba754edca..827a6a66998 100644 --- a/.rubocop_todo/style/format_string.yml +++ b/.rubocop_todo/style/format_string.yml @@ -3,22 +3,6 @@ Style/FormatString: Exclude: - 'app/components/diffs/overflow_warning_component.rb' - - 'app/controllers/admin/application_settings_controller.rb' - - 'app/controllers/admin/groups_controller.rb' - - 'app/controllers/admin/impersonation_tokens_controller.rb' - - 'app/controllers/admin/projects_controller.rb' - - 'app/controllers/admin/spam_logs_controller.rb' - - 'app/controllers/admin/topics_controller.rb' - - 'app/controllers/admin/users_controller.rb' - - 'app/controllers/concerns/access_tokens_actions.rb' - - 'app/controllers/concerns/confirm_email_warning.rb' - - 'app/controllers/concerns/enforces_two_factor_authentication.rb' - - 'app/controllers/concerns/integrations/actions.rb' - - 'app/controllers/concerns/membership_actions.rb' - - 'app/controllers/concerns/redirects_for_missing_path_on_tree.rb' - - 'app/controllers/concerns/spammable_actions/akismet_mark_as_spam_action.rb' - - 'app/controllers/concerns/verifies_with_email.rb' - - 'app/controllers/groups/settings/ci_cd_controller.rb' - 'app/controllers/import/bitbucket_server_controller.rb' - 'app/controllers/import/bulk_imports_controller.rb' - 'app/controllers/import/fogbugz_controller.rb' diff --git a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue index d7bbd6daed2..734d3ca0d49 100644 --- a/app/assets/javascripts/jobs/components/job/manual_variables_form.vue +++ b/app/assets/javascripts/jobs/components/job/manual_variables_form.vue @@ -68,6 +68,7 @@ export default { required: true, }, }, + clearBtnSharedClasses: ['gl-flex-grow-0 gl-flex-basis-0'], inputTypes: { key: 'key', value: 'value', @@ -229,16 +230,23 @@ export default { v-gl-tooltip :aria-label="$options.i18n.clearInputs" :title="$options.i18n.clearInputs" - class="gl-flex-grow-0 gl-flex-basis-0" + :class="$options.clearBtnSharedClasses" category="tertiary" variant="danger" icon="clear" data-testid="delete-variable-btn" @click="deleteVariable(variable.id)" /> - - <!-- delete variable button placeholder to not break flex layout --> - <div v-else class="gl-w-7 gl-mr-3" data-testid="delete-variable-btn-placeholder"></div> + <!-- Placeholder button to keep the layout fixed --> + <gl-button + v-else + class="gl-opacity-0 gl-pointer-events-none" + :class="$options.clearBtnSharedClasses" + data-testid="delete-variable-btn-placeholder" + category="tertiary" + variant="danger" + icon="clear" + /> </div> <div class="gl-text-center gl-mt-5"> diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js index 23f83b8d6cc..4f9bba1e0cb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js @@ -38,8 +38,10 @@ export default { statusIcon() { if (this.collapsedData.newErrors.length >= 1) { return EXTENSION_ICONS.warning; + } else if (this.collapsedData.resolvedErrors.length >= 1) { + return EXTENSION_ICONS.success; } - return EXTENSION_ICONS.success; + return EXTENSION_ICONS.neutral; }, }, methods: { diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js index c97e191b630..ac83cb78bc0 100644 --- a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js @@ -1,18 +1,4 @@ -import { s__ } from '~/locale'; - export const REGISTRATION_TOKEN_PLACEHOLDER = '$REGISTRATION_TOKEN'; -export const INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES = { - docker: { - instructions: s__( - 'Runners|To install Runner in a container follow the instructions described in the GitLab documentation', - ), - link: 'https://docs.gitlab.com/runner/install/docker.html', - }, - kubernetes: { - instructions: s__( - 'Runners|To install Runner in Kubernetes follow the instructions described in the GitLab documentation.', - ), - link: 'https://docs.gitlab.com/runner/install/kubernetes.html', - }, -}; +export const PLATFORM_DOCKER = 'docker'; +export const PLATFORM_KUBERNETES = 'kubernetes'; diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue new file mode 100644 index 00000000000..36e608a068b --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue @@ -0,0 +1,169 @@ +<script> +import { GlButton, GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; +import { REGISTRATION_TOKEN_PLACEHOLDER } from '../constants'; +import getRunnerSetupInstructionsQuery from '../graphql/get_runner_setup.query.graphql'; + +export default { + components: { + GlButton, + GlDropdown, + GlDropdownItem, + GlLoadingIcon, + ModalCopyButton, + }, + props: { + platform: { + type: Object, + required: false, + default: null, + }, + registrationToken: { + type: String, + required: false, + default: null, + }, + }, + data() { + return { + selectedArchitecture: this.platform?.architectures[0] || null, + instructions: null, + }; + }, + apollo: { + instructions: { + query: getRunnerSetupInstructionsQuery, + skip() { + return !this.platform || !this.selectedArchitecture; + }, + variables() { + return { + platform: this.platform.name, + architecture: this.selectedArchitecture.name, + }; + }, + update(data) { + return data?.runnerSetup; + }, + error() { + this.$emit('error'); + }, + }, + }, + computed: { + architectures() { + return this.platform?.architectures || []; + }, + binaryUrl() { + return this.selectedArchitecture?.downloadLocation; + }, + registerInstructionsWithToken() { + const { registerInstructions } = this.instructions || {}; + + if (this.registrationToken) { + return registerInstructions?.replace( + REGISTRATION_TOKEN_PLACEHOLDER, + this.registrationToken, + ); + } + return registerInstructions; + }, + }, + watch: { + platform() { + // reset selection if architecture is not in this list + const arch = this.architectures.find(({ name }) => name === this.selectedArchitecture.name); + if (!arch) { + this.selectArchitecture(this.architectures[0]); + } + }, + }, + methods: { + selectArchitecture(architecture) { + this.selectedArchitecture = architecture; + }, + onClose() { + this.$emit('close'); + }, + }, + i18n: { + architecture: s__('Runners|Architecture'), + downloadInstallBinary: s__('Runners|Download and install binary'), + downloadLatestBinary: s__('Runners|Download latest binary'), + registerRunnerCommand: s__('Runners|Command to register runner'), + copyInstructions: s__('Runners|Copy instructions'), + }, +}; +</script> + +<template> + <div> + <h5> + {{ $options.i18n.architecture }} + <gl-loading-icon v-if="$apollo.loading" size="sm" inline /> + </h5> + + <gl-dropdown class="gl-mb-3" :text="selectedArchitecture.name"> + <gl-dropdown-item + v-for="architecture in architectures" + :key="architecture.name" + is-check-item + :is-checked="selectedArchitecture.name === architecture.name" + data-testid="architecture-dropdown-item" + @click="selectArchitecture(architecture)" + > + {{ architecture.name }} + </gl-dropdown-item> + </gl-dropdown> + <div class="gl-sm-display-flex gl-align-items-center gl-mb-3"> + <h5>{{ $options.i18n.downloadInstallBinary }}</h5> + <gl-button + v-if="binaryUrl" + class="gl-ml-auto" + :href="binaryUrl" + download + icon="download" + data-testid="binary-download-button" + > + {{ $options.i18n.downloadLatestBinary }} + </gl-button> + </div> + + <template v-if="instructions"> + <div class="gl-display-flex"> + <pre + class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line" + data-testid="binary-instructions" + >{{ instructions.installInstructions }}</pre + > + <modal-copy-button + :title="$options.i18n.copyInstructions" + :text="instructions.installInstructions" + :modal-id="$options.modalId" + css-classes="gl-align-self-start gl-ml-2 gl-mt-2" + category="tertiary" + /> + </div> + <h5 class="gl-mb-3">{{ $options.i18n.registerRunnerCommand }}</h5> + <div class="gl-display-flex"> + <pre + class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line" + data-testid="register-command" + >{{ registerInstructionsWithToken }}</pre + > + <modal-copy-button + :title="$options.i18n.copyInstructions" + :text="registerInstructionsWithToken" + :modal-id="$options.modalId" + css-classes="gl-align-self-start gl-ml-2 gl-mt-2" + category="tertiary" + /> + </div> + </template> + + <footer class="gl-display-flex gl-justify-content-end gl-pt-3"> + <gl-button @click="onClose()">{{ __('Close') }}</gl-button> + </footer> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue new file mode 100644 index 00000000000..ff7e803af2a --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue @@ -0,0 +1,35 @@ +<script> +import { GlButton, GlIcon } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + components: { + GlButton, + GlIcon, + }, + methods: { + onClose() { + this.$emit('close'); + }, + }, + I18N_INSTRUCTIONS_TEXT: s__( + 'Runners|To install Runner in a container follow the instructions described in the GitLab documentation', + ), + I18N_VIEW_INSTRUCTIONS: s__('Runners|View installation instructions'), + HELP_URL: 'https://docs.gitlab.com/runner/install/docker.html', +}; +</script> +<template> + <div> + <p> + {{ $options.I18N_INSTRUCTIONS_TEXT }} + </p> + <gl-button :href="$options.HELP_URL"> + <gl-icon name="external-link" /> + {{ $options.I18N_VIEW_INSTRUCTIONS }} + </gl-button> + <footer class="gl-display-flex gl-justify-content-end gl-pt-3"> + <gl-button @click="onClose()">{{ __('Close') }}</gl-button> + </footer> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue new file mode 100644 index 00000000000..ee41dab0cec --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue @@ -0,0 +1,35 @@ +<script> +import { GlButton, GlIcon } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + components: { + GlButton, + GlIcon, + }, + methods: { + onClose() { + this.$emit('close'); + }, + }, + I18N_INSTRUCTIONS_TEXT: s__( + 'Runners|To install Runner in Kubernetes follow the instructions described in the GitLab documentation.', + ), + I18N_VIEW_INSTRUCTIONS: s__('Runners|View installation instructions'), + HELP_URL: 'https://docs.gitlab.com/runner/install/kubernetes.html', +}; +</script> +<template> + <div> + <p> + {{ $options.I18N_INSTRUCTIONS_TEXT }} + </p> + <gl-button :href="$options.HELP_URL"> + <gl-icon name="external-link" /> + {{ $options.I18N_VIEW_INSTRUCTIONS }} + </gl-button> + <footer class="gl-display-flex gl-justify-content-end gl-pt-3"> + <gl-button @click="onClose()">{{ __('Close') }}</gl-button> + </footer> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue index f41654c0090..729fe9c462c 100644 --- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue @@ -12,15 +12,13 @@ import { GlResizeObserverDirective, } from '@gitlab/ui'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; -import { isEmpty } from 'lodash'; import { __, s__ } from '~/locale'; -import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; -import { - INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES, - REGISTRATION_TOKEN_PLACEHOLDER, -} from './constants'; import getRunnerPlatformsQuery from './graphql/get_runner_platforms.query.graphql'; -import getRunnerSetupInstructionsQuery from './graphql/get_runner_setup.query.graphql'; +import { PLATFORM_DOCKER, PLATFORM_KUBERNETES } from './constants'; + +import RunnerCliInstructions from './instructions/runner_cli_instructions.vue'; +import RunnerDockerInstructions from './instructions/runner_docker_instructions.vue'; +import RunnerKubernetesInstructions from './instructions/runner_kubernetes_instructions.vue'; export default { components: { @@ -33,7 +31,7 @@ export default { GlIcon, GlLoadingIcon, GlSkeletonLoader, - ModalCopyButton, + RunnerDockerInstructions, }, directives: { GlResizeObserver: GlResizeObserverDirective, @@ -74,27 +72,13 @@ export default { ); }, result() { - // If it is set and available, select the defaultSelectedPlatform. + // If found, select the defaultSelectedPlatform. // Otherwise, select the first available platform - this.selectPlatform(this.defaultPlatformName || this.platforms?.[0].name); - }, - error() { - this.toggleAlert(true); - }, - }, - instructions: { - query: getRunnerSetupInstructionsQuery, - skip() { - return !this.shown || !this.selectedPlatform; - }, - variables() { - return { - platform: this.selectedPlatform, - architecture: this.selectedArchitecture || '', - }; - }, - update(data) { - return data?.runnerSetup; + const platform = + this.platforms?.find(({ name }) => this.defaultPlatformName === name) || + this.platforms?.[0]; + + this.selectPlatform(platform); }, error() { this.toggleAlert(true); @@ -106,39 +90,23 @@ export default { shown: false, platforms: [], selectedPlatform: null, - selectedArchitecture: null, showAlert: false, - instructions: {}, platformsButtonGroupVertical: false, }; }, computed: { - instructionsEmpty() { - return isEmpty(this.instructions); - }, - architectures() { - return this.platforms.find(({ name }) => name === this.selectedPlatform)?.architectures || []; - }, - binaryUrl() { - return this.architectures.find(({ name }) => name === this.selectedArchitecture) - ?.downloadLocation; - }, - instructionsWithoutArchitecture() { - return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform]?.instructions; - }, - runnerInstallationLink() { - return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform]?.link; - }, - registerInstructionsWithToken() { - const { registerInstructions } = this.instructions || {}; - - if (this.registrationToken) { - return registerInstructions?.replace( - REGISTRATION_TOKEN_PLACEHOLDER, - this.registrationToken, - ); + instructionsComponent() { + if (this.selectedPlatform?.architectures?.length) { + return RunnerCliInstructions; + } + switch (this.selectedPlatform?.name) { + case PLATFORM_DOCKER: + return RunnerDockerInstructions; + case PLATFORM_KUBERNETES: + return RunnerKubernetesInstructions; + default: + return null; } - return registerInstructions; }, }, updated() { @@ -149,6 +117,12 @@ export default { show() { this.$refs.modal.show(); }, + close() { + this.$refs.modal.close(); + }, + onClose() { + this.close(); + }, onShown() { this.shown = true; this.refocusSelectedPlatformButton(); @@ -159,21 +133,13 @@ export default { // get focused when setting a `defaultPlatformName`. // This method refocuses the expected button. // See more about this auto-focus: https://bootstrap-vue.org/docs/components/modal#auto-focus-on-open - this.$refs[this.selectedPlatform]?.[0].$el.focus(); + this.$refs[this.selectedPlatform?.name]?.[0].$el.focus(); }, - selectPlatform(platformName) { - this.selectedPlatform = platformName; - - // Update architecture when platform changes - const arch = this.architectures.find(({ name }) => name === this.selectedArchitecture); - if (arch) { - this.selectArchitecture(arch.name); - } else { - this.selectArchitecture(this.architectures[0]?.name); - } + selectPlatform(platform) { + this.selectedPlatform = platform; }, - selectArchitecture(architecture) { - this.selectedArchitecture = architecture; + isPlatformSelected(platform) { + return this.selectedPlatform.name === platform.name; }, toggleAlert(state) { this.showAlert = state; @@ -189,17 +155,9 @@ export default { i18n: { environment: __('Environment'), installARunner: s__('Runners|Install a runner'), - architecture: s__('Runners|Architecture'), downloadInstallBinary: s__('Runners|Download and install binary'), downloadLatestBinary: s__('Runners|Download latest binary'), - registerRunnerCommand: s__('Runners|Command to register runner'), fetchError: s__('Runners|An error has occurred fetching instructions'), - copyInstructions: s__('Runners|Copy instructions'), - viewInstallationInstructions: s__('Runners|View installation instructions'), - }, - closeButton: { - text: __('Close'), - attributes: [{ variant: 'default' }], }, }; </script> @@ -208,8 +166,8 @@ export default { ref="modal" :modal-id="modalId" :title="$options.i18n.installARunner" - :action-secondary="$options.closeButton" v-bind="$attrs" + hide-footer v-on="$listeners" @shown="onShown" > @@ -234,88 +192,23 @@ export default { v-for="platform in platforms" :key="platform.name" :ref="platform.name" - :selected="selectedPlatform === platform.name" - @click="selectPlatform(platform.name)" + :selected="isPlatformSelected(platform)" + @click="selectPlatform(platform)" > {{ platform.humanReadableName }} </gl-button> </gl-button-group> </div> </template> - <template v-if="architectures.length"> - <template v-if="selectedPlatform"> - <h5> - {{ $options.i18n.architecture }} - <gl-loading-icon v-if="$apollo.loading" size="sm" inline /> - </h5> - - <gl-dropdown class="gl-mb-3" :text="selectedArchitecture"> - <gl-dropdown-item - v-for="architecture in architectures" - :key="architecture.name" - is-check-item - :is-checked="selectedArchitecture === architecture.name" - data-testid="architecture-dropdown-item" - @click="selectArchitecture(architecture.name)" - > - {{ architecture.name }} - </gl-dropdown-item> - </gl-dropdown> - <div class="gl-sm-display-flex gl-align-items-center gl-mb-3"> - <h5>{{ $options.i18n.downloadInstallBinary }}</h5> - <gl-button - v-if="binaryUrl" - class="gl-ml-auto" - :href="binaryUrl" - download - icon="download" - data-testid="binary-download-button" - > - {{ $options.i18n.downloadLatestBinary }} - </gl-button> - </div> - </template> - <template v-if="!instructionsEmpty"> - <div class="gl-display-flex"> - <pre - class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line" - data-testid="binary-instructions" - >{{ instructions.installInstructions }}</pre - > - <modal-copy-button - :title="$options.i18n.copyInstructions" - :text="instructions.installInstructions" - :modal-id="$options.modalId" - css-classes="gl-align-self-start gl-ml-2 gl-mt-2" - category="tertiary" - /> - </div> - <h5 class="gl-mb-3">{{ $options.i18n.registerRunnerCommand }}</h5> - <div class="gl-display-flex"> - <pre - class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line" - data-testid="register-command" - >{{ registerInstructionsWithToken }}</pre - > - <modal-copy-button - :title="$options.i18n.copyInstructions" - :text="registerInstructionsWithToken" - :modal-id="$options.modalId" - css-classes="gl-align-self-start gl-ml-2 gl-mt-2" - category="tertiary" - /> - </div> - </template> - </template> - <template v-else> - <div> - <p>{{ instructionsWithoutArchitecture }}</p> - <gl-button :href="runnerInstallationLink"> - <gl-icon name="external-link" /> - {{ $options.i18n.viewInstallationInstructions }} - </gl-button> - </div> - </template> + <keep-alive> + <component + :is="instructionsComponent" + :registration-token="registrationToken" + :platform="selectedPlatform" + @close="onClose" + @error="toggleAlert(true)" + /> + </keep-alive> </gl-modal> </template> diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b8c1bc266f7..ade58ca0970 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -137,8 +137,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController unless job_id.length <= PARAM_JOB_ID_MAX_SIZE return render status: :bad_request, json: { - message: _('Parameter "job_id" cannot exceed length of %{job_id_max_size}' % - { job_id_max_size: PARAM_JOB_ID_MAX_SIZE }) + message: format(_('Parameter "job_id" cannot exceed length of %{job_id_max_size}'), job_id_max_size: PARAM_JOB_ID_MAX_SIZE) } end @@ -174,8 +173,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController unless job_id.length <= PARAM_JOB_ID_MAX_SIZE return render status: :bad_request, json: { - message: _('Parameter "job_id" cannot exceed length of %{job_id_max_size}' % - { job_id_max_size: PARAM_JOB_ID_MAX_SIZE }) + message: format(_('Parameter "job_id" cannot exceed length of %{job_id_max_size}'), job_id_max_size: PARAM_JOB_ID_MAX_SIZE) } end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 8005babe19e..e3a33bafb62 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -41,7 +41,7 @@ class Admin::GroupsController < Admin::ApplicationController @group = ::Groups::CreateService.new(current_user, group_params).execute if @group.persisted? - redirect_to [:admin, @group], notice: _('Group %{group_name} was successfully created.') % { group_name: @group.name } + redirect_to [:admin, @group], notice: format(_('Group %{group_name} was successfully created.'), group_name: @group.name) else render "new" end @@ -66,7 +66,7 @@ class Admin::GroupsController < Admin::ApplicationController redirect_to admin_groups_path, status: :found, - alert: _('Group %{group_name} was scheduled for deletion.') % { group_name: @group.name } + alert: format(_('Group %{group_name} was scheduled for deletion.'), group_name: @group.name) end private diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb index 9d884478e98..ddc555add5c 100644 --- a/app/controllers/admin/impersonation_tokens_controller.rb +++ b/app/controllers/admin/impersonation_tokens_controller.rb @@ -25,9 +25,9 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController @impersonation_token = finder.find(params[:id]) if @impersonation_token.revoke! - flash[:notice] = _("Revoked impersonation token %{token_name}!") % { token_name: @impersonation_token.name } + flash[:notice] = format(_("Revoked impersonation token %{token_name}!"), token_name: @impersonation_token.name) else - flash[:alert] = _("Could not revoke impersonation token %{token_name}.") % { token_name: @impersonation_token.name } + flash[:alert] = format(_("Could not revoke impersonation token %{token_name}."), token_name: @impersonation_token.name) end redirect_to admin_user_impersonation_tokens_path diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 9e841487508..5d37bd27302 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -43,7 +43,7 @@ class Admin::ProjectsController < Admin::ApplicationController def destroy ::Projects::DestroyService.new(@project, current_user, {}).async_execute - flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name } + flash[:notice] = format(_("Project '%{project_name}' is in the process of being deleted."), project_name: @project.full_name) redirect_to admin_projects_path, status: :found rescue Projects::DestroyService::DestroyError => e diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb index 180f4634136..984ae736697 100644 --- a/app/controllers/admin/spam_logs_controller.rb +++ b/app/controllers/admin/spam_logs_controller.rb @@ -16,7 +16,7 @@ class Admin::SpamLogsController < Admin::ApplicationController spam_log.remove_user(deleted_by: current_user) redirect_to admin_spam_logs_path, status: :found, - notice: _('User %{username} was successfully removed.') % { username: spam_log.user.username } + notice: format(_('User %{username} was successfully removed.'), username: spam_log.user.username) else spam_log.destroy head :ok diff --git a/app/controllers/admin/topics_controller.rb b/app/controllers/admin/topics_controller.rb index e97ead12f71..345a778772d 100644 --- a/app/controllers/admin/topics_controller.rb +++ b/app/controllers/admin/topics_controller.rb @@ -23,7 +23,7 @@ class Admin::TopicsController < Admin::ApplicationController @topic = Projects::Topic.new(topic_params) if @topic.save - redirect_to edit_admin_topic_path(@topic), notice: _('Topic %{topic_name} was successfully created.') % { topic_name: @topic.name } + redirect_to edit_admin_topic_path(@topic), notice: format(_('Topic %{topic_name} was successfully created.'), topic_name: @topic.name) else render "new" end @@ -42,7 +42,7 @@ class Admin::TopicsController < Admin::ApplicationController redirect_to admin_topics_path, status: :found, - notice: _('Topic %{topic_name} was successfully removed.') % { topic_name: @topic.title_or_name } + notice: format(_('Topic %{topic_name} was successfully removed.'), topic_name: @topic.title_or_name) end def merge @@ -53,7 +53,7 @@ class Admin::TopicsController < Admin::ApplicationController return render status: :bad_request, json: { type: :alert, message: response.message } if response.error? message = _('Topic %{source_topic} was successfully merged into topic %{target_topic}.') - flash[:toast] = message % { source_topic: source_topic.name, target_topic: target_topic.name } + flash[:toast] = format(message, source_topic: source_topic.name, target_topic: target_topic.name) redirect_to admin_topics_path, status: :found end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index abefbe28f35..b191a5f967a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -57,7 +57,7 @@ class Admin::UsersController < Admin::ApplicationController log_impersonation_event - flash[:alert] = _("You are now impersonating %{username}") % { username: user.username } + flash[:alert] = format(_("You are now impersonating %{username}"), username: user.username) redirect_to root_path else @@ -81,7 +81,7 @@ class Admin::UsersController < Admin::ApplicationController result = Users::RejectService.new(current_user).execute(user) if result[:status] == :success - redirect_back_or_admin_user(notice: _("You've rejected %{user}" % { user: user.name })) + redirect_back_or_admin_user(notice: format(_("You've rejected %{user}"), user: user.name)) else redirect_back_or_admin_user(alert: result[:message]) end @@ -105,7 +105,7 @@ class Admin::UsersController < Admin::ApplicationController return redirect_back_or_admin_user(notice: _("Internal users cannot be deactivated")) if user.internal? unless user.can_be_deactivated? - return redirect_back_or_admin_user(notice: _("The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated") % { minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period }) + return redirect_back_or_admin_user(notice: format(_("The user you are trying to deactivate has been active in the past %{minimum_inactive_days} days and cannot be deactivated"), minimum_inactive_days: Gitlab::CurrentSettings.deactivate_dormant_users_period)) end user.deactivate @@ -378,7 +378,7 @@ class Admin::UsersController < Admin::ApplicationController end def log_impersonation_event - Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username }) + Gitlab::AppLogger.info(format(_("User %{current_user_username} has started impersonating %{username}"), current_user_username: current_user.username, username: user.username)) end def can_impersonate_user diff --git a/app/controllers/concerns/access_tokens_actions.rb b/app/controllers/concerns/access_tokens_actions.rb index fdb08c6572f..6a84c436aae 100644 --- a/app/controllers/concerns/access_tokens_actions.rb +++ b/app/controllers/concerns/access_tokens_actions.rb @@ -43,9 +43,9 @@ module AccessTokensActions revoked_response = ResourceAccessTokens::RevokeService.new(current_user, resource, @resource_access_token).execute if revoked_response.success? - flash[:notice] = _("Revoked access token %{access_token_name}!") % { access_token_name: @resource_access_token.name } + flash[:notice] = format(_("Revoked access token %{access_token_name}!"), access_token_name: @resource_access_token.name) else - flash[:alert] = _("Could not revoke access token %{access_token_name}.") % { access_token_name: @resource_access_token.name } + flash[:alert] = format(_("Could not revoke access token %{access_token_name}."), access_token_name: @resource_access_token.name) end redirect_to resource_access_tokens_path diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb index 32e1a46e580..ec5140bf223 100644 --- a/app/controllers/concerns/confirm_email_warning.rb +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -19,10 +19,17 @@ module ConfirmEmailWarning email = current_user.unconfirmed_email || current_user.email - flash.now[:warning] = _("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.").html_safe % { + flash.now[:warning] = format( + confirm_warning_message, email: email, resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post), update_link: view_context.link_to(_('Update it'), profile_path) - } + ).html_safe + end + + private + + def confirm_warning_message + _("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.") end end diff --git a/app/controllers/concerns/enforces_two_factor_authentication.rb b/app/controllers/concerns/enforces_two_factor_authentication.rb index c8de041d5bd..cdef1a45a27 100644 --- a/app/controllers/concerns/enforces_two_factor_authentication.rb +++ b/app/controllers/concerns/enforces_two_factor_authentication.rb @@ -25,8 +25,9 @@ module EnforcesTwoFactorAuthentication case self when GraphqlController render_error( - _("Authentication error: enable 2FA in your profile settings to continue using GitLab: %{mfa_help_page}") % - { mfa_help_page: mfa_help_page_url }, + format( + _("Authentication error: enable 2FA in your profile settings to continue using GitLab: %{mfa_help_page}"), + mfa_help_page: mfa_help_page_url), status: :unauthorized ) else diff --git a/app/controllers/concerns/integrations/actions.rb b/app/controllers/concerns/integrations/actions.rb index e0a12555e11..7bebafae0fd 100644 --- a/app/controllers/concerns/integrations/actions.rb +++ b/app/controllers/concerns/integrations/actions.rb @@ -57,9 +57,9 @@ module Integrations::Actions def success_message if integration.active? - s_('Integrations|%{integration} settings saved and active.') % { integration: integration.title } + format(s_('Integrations|%{integration} settings saved and active.'), integration: integration.title) else - s_('Integrations|%{integration} settings saved, but not active.') % { integration: integration.title } + format(s_('Integrations|%{integration} settings saved, but not active.'), integration: integration.title) end end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index a8af5cf7c6b..7c6e449b509 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -66,8 +66,7 @@ module MembershipActions notice: _('Your request for access has been queued for review.') else redirect_to polymorphic_path(membershipable), - alert: _("Your request for access could not be processed: %{error_message}") % - { error_message: access_requester.errors.full_messages.to_sentence } + alert: format(_("Your request for access could not be processed: %{error_message}"), error_message: access_requester.errors.full_messages.to_sentence) end end @@ -87,9 +86,9 @@ module MembershipActions notice = if member.request? - _("Your access request to the %{source_type} has been withdrawn.") % { source_type: source_type } + format(_("Your access request to the %{source_type} has been withdrawn."), source_type: source_type) else - _("You left the \"%{membershipable_human_name}\" %{source_type}.") % { membershipable_human_name: membershipable.human_name, source_type: source_type } + format(_("You left the \"%{membershipable_human_name}\" %{source_type}."), membershipable_human_name: membershipable.human_name, source_type: source_type) end respond_to do |format| diff --git a/app/controllers/concerns/redirects_for_missing_path_on_tree.rb b/app/controllers/concerns/redirects_for_missing_path_on_tree.rb index 085afbf3975..92574dfade9 100644 --- a/app/controllers/concerns/redirects_for_missing_path_on_tree.rb +++ b/app/controllers/concerns/redirects_for_missing_path_on_tree.rb @@ -8,7 +8,7 @@ module RedirectsForMissingPathOnTree private def missing_path_on_ref(path, ref) - _('"%{path}" did not exist on "%{ref}"') % { path: truncate_path(path), ref: ref } + format(_('"%{path}" did not exist on "%{ref}"'), path: truncate_path(path), ref: ref) end def truncate_path(path) diff --git a/app/controllers/concerns/spammable_actions/akismet_mark_as_spam_action.rb b/app/controllers/concerns/spammable_actions/akismet_mark_as_spam_action.rb index 044519004b2..6ba079ee658 100644 --- a/app/controllers/concerns/spammable_actions/akismet_mark_as_spam_action.rb +++ b/app/controllers/concerns/spammable_actions/akismet_mark_as_spam_action.rb @@ -9,7 +9,7 @@ module SpammableActions::AkismetMarkAsSpamAction def mark_as_spam if Spam::AkismetMarkAsSpamService.new(target: spammable).execute - redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase } + redirect_to spammable_path, notice: format(_("%{spammable_titlecase} was submitted to Akismet successfully."), spammable_titlecase: spammable.spammable_entity_type.titlecase) else redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.') end diff --git a/app/controllers/concerns/verifies_with_email.rb b/app/controllers/concerns/verifies_with_email.rb index 3cada24a81a..82388090350 100644 --- a/app/controllers/concerns/verifies_with_email.rb +++ b/app/controllers/concerns/verifies_with_email.rb @@ -105,8 +105,10 @@ module VerifiesWithEmail end def render_sign_in_rate_limited - message = s_('IdentityVerification|Maximum login attempts exceeded. '\ - 'Wait %{interval} and try again.') % { interval: user_sign_in_interval } + message = format( + s_('IdentityVerification|Maximum login attempts exceeded. Wait %{interval} and try again.'), + interval: user_sign_in_interval + ) redirect_to new_user_session_path, alert: message end diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index 1dfa8cdf133..78e3ffa4af9 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -23,7 +23,7 @@ module Groups if update_group_service.execute flash[:notice] = s_('GroupSettings|Pipeline settings was updated for the group') else - flash[:alert] = s_("GroupSettings|There was a problem updating the pipeline settings: %{error_messages}." % { error_messages: group.errors.full_messages }) + flash[:alert] = format(s_("GroupSettings|There was a problem updating the pipeline settings: %{error_messages}."), error_messages: group.errors.full_messages) end redirect_to group_settings_ci_cd_path @@ -33,7 +33,7 @@ module Groups if auto_devops_service.execute flash[:notice] = s_('GroupSettings|Auto DevOps pipeline was updated for the group') else - flash[:alert] = s_("GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}." % { error_messages: group.errors.full_messages }) + flash[:alert] = format(s_("GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."), error_messages: group.errors.full_messages) end redirect_to group_settings_ci_cd_path diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb index 0bb92dfd118..4bd89a3d4e2 100644 --- a/app/helpers/version_check_helper.rb +++ b/app/helpers/version_check_helper.rb @@ -18,7 +18,7 @@ module VersionCheckHelper strong_memoize_attr :gitlab_version_check def show_security_patch_upgrade_alert? - return false unless show_version_check? && gitlab_version_check + return false unless Feature.enabled?(:critical_security_alert) && show_version_check? && gitlab_version_check gitlab_version_check['severity'] === SECURITY_ALERT_SEVERITY end diff --git a/config/feature_flags/development/critical_security_alert.yml b/config/feature_flags/development/critical_security_alert.yml new file mode 100644 index 00000000000..6bb5100efc8 --- /dev/null +++ b/config/feature_flags/development/critical_security_alert.yml @@ -0,0 +1,8 @@ +--- +name: critical_security_alert +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108732 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387719 +milestone: '15.8' +type: development +group: group::distribution +default_enabled: false diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md index 4c9ad1c8bc5..2f2759d7339 100644 --- a/doc/administration/geo/replication/troubleshooting.md +++ b/doc/administration/geo/replication/troubleshooting.md @@ -1317,6 +1317,18 @@ registry = Geo::PackageFileRegistry.find(registry_id) registry.replicator.send(:download) ``` +#### Find registry records of blobs that failed to sync + +```ruby +Geo::PackageFileRegistry.failed +``` + +#### Find registry records of blobs that are missing on the primary site + +```ruby +Geo::PackageFileRegistry.where(last_sync_failure: 'The file is missing on the Geo primary site') +``` + #### Verify package files on the secondary manually This iterates over all package files on the secondary, looking at the @@ -1344,7 +1356,7 @@ status.keys.each {|key| puts "#{key} count: #{status[key].count}"} status ``` -### Reverify all uploads (or any SSF data type which is verified) +#### Reverify all uploads (or any SSF data type which is verified) 1. SSH into a GitLab Rails node in the primary Geo site. 1. Open [Rails console](../../../administration/operations/rails_console.md#starting-a-rails-console-session). @@ -1400,21 +1412,6 @@ registry = Geo::SnippetRepositoryRegistry.find(registry_id) registry.replicator.send(:sync_repository) ``` -### Find failed artifacts - -[Start a Rails console session](../../../administration/operations/rails_console.md#starting-a-rails-console-session) -to run the following commands: - -```ruby -Geo::JobArtifactRegistry.failed -``` - -#### Find `ID` of synced artifacts that are missing on primary - -```ruby -Geo::JobArtifactRegistry.synced.missing_on_primary.pluck(:artifact_id) -``` - ### Project or project wiki repositories #### Find repository verification failures diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md index 2485f44d173..bb50f66aff0 100644 --- a/doc/administration/reference_architectures/10k_users.md +++ b/doc/administration/reference_architectures/10k_users.md @@ -1855,7 +1855,7 @@ Updates to example must be made at: # Set number of Sidekiq queue processes to the same number as available CPUs sidekiq['queue_groups'] = ['*'] * 4 - # Set number of Sidekiq threads per queue process to the recommend number of 20 + # Set number of Sidekiq threads per queue process to the recommended number of 20 sidekiq['max_concurrency'] = 20 # Monitoring diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md index 7c5feb24e15..0cd34ca16df 100644 --- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md +++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md @@ -74,7 +74,7 @@ Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md ### Artifacts -Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#find-failed-artifacts). +Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#find-registry-records-of-blobs-that-failed-to-sync). ### Repository verification failures diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md index 08c2865a5a6..961a47e005b 100644 --- a/doc/development/elasticsearch.md +++ b/doc/development/elasticsearch.md @@ -234,8 +234,6 @@ Backfills a specific field in an index. In most cases, the mapping for the field Requires the `index_name` and `field_name` methods. -<details><summary>Example</summary> - ```ruby class MigrationName < Elastic::Migration include Elastic::MigrationBackfillHelper @@ -252,16 +250,12 @@ class MigrationName < Elastic::Migration end ``` -</details> - #### `Elastic::MigrationUpdateMappingsHelper` Updates a mapping in an index by calling `put_mapping` with the mapping specified. Requires the `index_name` and `new_mappings` methods. -<details><summary>Example</summary> - ```ruby class MigrationName < Elastic::Migration include Elastic::MigrationUpdateMappingsHelper @@ -282,28 +276,20 @@ class MigrationName < Elastic::Migration end ``` -</details> - #### `Elastic::MigrationObsolete` Marks a migration as obsolete when it's no longer required. -<details><summary>Example</summary> - ```ruby class MigrationName < Elastic::Migration include Elastic::MigrationObsolete end ``` -</details> - #### `Elastic::MigrationHelper` Contains methods you can use when a migration doesn't fit the previous examples. -<details><summary>Example</summary> - ```ruby class MigrationName < Elastic::Migration include Elastic::MigrationHelper @@ -318,8 +304,6 @@ class MigrationName < Elastic::Migration end ``` -</details> - ### Migration options supported by the `Elastic::MigrationWorker` [`Elastic::MigrationWorker`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/workers/elastic/migration_worker.rb) supports the following migration options: diff --git a/doc/install/installation.md b/doc/install/installation.md index 543a377b8ee..68c2b663566 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -344,6 +344,12 @@ In GitLab 12.1 and later, only PostgreSQL is supported. In GitLab 14.0 and later sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS btree_gist;" ``` +1. Create the `plpgsql` extension: + + ```shell + sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS plpgsql;" + ``` + 1. Create the GitLab production database and grant all privileges on the database: ```shell @@ -392,6 +398,24 @@ In GitLab 12.1 and later, only PostgreSQL is supported. In GitLab 14.0 and later (1 row) ``` +1. Check if the `plpgsql` extension is enabled: + + ```sql + SELECT true AS enabled + FROM pg_available_extensions + WHERE name = 'plpgsql' + AND installed_version IS NOT NULL; + ``` + + If the extension is enabled this produces the following output: + + ```plaintext + enabled + --------- + t + (1 row) + ``` + 1. Quit the database session: ```shell diff --git a/doc/raketasks/backup_gitlab.md b/doc/raketasks/backup_gitlab.md index aea61e36037..350cf110878 100644 --- a/doc/raketasks/backup_gitlab.md +++ b/doc/raketasks/backup_gitlab.md @@ -892,7 +892,7 @@ When troubleshooting backup problems, however, replace `CRON=1` with `--trace` t ## Limit backup lifetime for local files (prune old backups) WARNING: -The process described in this section don't work if you used a [custom filename](#backup-filename) +The process described in this section doesn't work if you used a [custom filename](#backup-filename) for your backups. To prevent regular backups from using all your disk space, you may want to set a limited lifetime diff --git a/doc/user/ssh.md b/doc/user/ssh.md index 1d82d301e9a..b71b120d246 100644 --- a/doc/user/ssh.md +++ b/doc/user/ssh.md @@ -278,7 +278,8 @@ To learn more about using 1Password with SSH keys, see [1Password's documentatio ## Add an SSH key to your GitLab account -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/271239) in GitLab 15.4, default expiration date suggested in UI. +> - Suggested default expiration date for keys [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/271239) in GitLab 15.4. +> - Usage types for SSH keys [added](https://gitlab.com/gitlab-org/gitlab/-/issues/383046) in GitLab 15.7. To use SSH with GitLab, copy your public key to your GitLab account: diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh index 923b633fcc9..646f9821a60 100644 --- a/scripts/rspec_helpers.sh +++ b/scripts/rspec_helpers.sh @@ -105,9 +105,13 @@ function rspec_simple_job() { export NO_KNAPSACK="1" local rspec_cmd="bin/rspec $(rspec_args "${1}" "${2}")" + local rspec_run_status=0 + local rspec_log="${CI_PROJECT_DIR}/tmp/rspec.log" echoinfo "Running RSpec command: ${rspec_cmd}" - eval "${rspec_cmd}" + eval "${rspec_cmd}" | tee "${rspec_log}" || rspec_run_status=$? + + handle_retry_rspec_in_new_process $rspec_run_status $rspec_log } function rspec_db_library_code() { @@ -136,6 +140,29 @@ function debug_rspec_variables() { echoinfo "CRYSTALBALL: ${CRYSTALBALL}" } +function handle_retry_rspec_in_new_process() { + local rspec_run_status="${1}" + local rspec_log="${2}" + + # Experiment to retry failed examples in a new RSpec process: https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1148 + if [[ $rspec_run_status -ne 0 ]]; then + if [[ "${RETRY_FAILED_TESTS_IN_NEW_PROCESS}" == "true" ]]; then + if grep -q "error occurred outside of examples" "${rspec_log}"; then + echoerr "Not retrying failing examples since there were errors happening outside of the RSpec examples!" + else + retry_failed_rspec_examples + rspec_run_status=$? + fi + else + echoerr "Not retrying failing examples since \$RETRY_FAILED_TESTS_IN_NEW_PROCESS != 'true'!" + fi + else + echosuccess "No examples to retry, congrats!" + fi + + exit $rspec_run_status +} + function rspec_paralellized_job() { read -ra job_name <<< "${CI_JOB_NAME}" local test_tool="${job_name[0]}" @@ -146,6 +173,7 @@ function rspec_paralellized_job() { local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/" local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}")/" local rspec_run_status=0 + local rspec_log="${CI_PROJECT_DIR}/tmp/rspec.log" if [[ "${test_tool}" =~ "-ee" ]]; then spec_folder_prefixes="'ee/'" @@ -197,24 +225,14 @@ function rspec_paralellized_job() { debug_rspec_variables if [[ -n "${RSPEC_TESTS_MAPPING_ENABLED}" ]]; then - tooling/bin/parallel_rspec --rspec_args "$(rspec_args "${rspec_opts}")" --filter "${RSPEC_TESTS_FILTER_FILE}" || rspec_run_status=$? + tooling/bin/parallel_rspec --rspec_args "$(rspec_args "${rspec_opts}")" --filter "${RSPEC_TESTS_FILTER_FILE}" | tee "${rspec_log}" || rspec_run_status=$? else - tooling/bin/parallel_rspec --rspec_args "$(rspec_args "${rspec_opts}")" || rspec_run_status=$? + tooling/bin/parallel_rspec --rspec_args "$(rspec_args "${rspec_opts}")" | tee "${rspec_log}" || rspec_run_status=$? fi echoinfo "RSpec exited with ${rspec_run_status}." - # Experiment to retry failed examples in a new RSpec process: https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1148 - if [[ $rspec_run_status -ne 0 ]]; then - if [[ "${RETRY_FAILED_TESTS_IN_NEW_PROCESS}" == "true" ]]; then - retry_failed_rspec_examples - rspec_run_status=$? - fi - else - echosuccess "No examples to retry, congrats!" - fi - - exit $rspec_run_status + handle_retry_rspec_in_new_process $rspec_run_status $rspec_log } function retry_failed_rspec_examples() { diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb index 9625fdc195d..a140011941f 100644 --- a/spec/factories/personal_access_tokens.rb +++ b/spec/factories/personal_access_tokens.rb @@ -27,6 +27,12 @@ FactoryBot.define do token_digest { nil } end + trait :admin_mode do + before(:create) do |personal_access_token| + personal_access_token.scopes.append(Gitlab::Auth::ADMIN_MODE_SCOPE) if personal_access_token.user.admin? + end + end + trait :no_prefix do after(:build) { |personal_access_token| personal_access_token.set_token(Devise.friendly_token) } end diff --git a/spec/frontend/jobs/components/job/manual_variables_form_spec.js b/spec/frontend/jobs/components/job/manual_variables_form_spec.js index 45a1e9dca76..3040570df19 100644 --- a/spec/frontend/jobs/components/job/manual_variables_form_spec.js +++ b/spec/frontend/jobs/components/job/manual_variables_form_spec.js @@ -212,9 +212,30 @@ describe('Manual Variables Form', () => { expect(findDeleteVarBtn().exists()).toBe(true); }); + }); + + describe('variable delete button placeholder', () => { + beforeEach(async () => { + getJobQueryResponse.mockResolvedValue(mockJobResponse); + await createComponentWithApollo(); + }); it('delete variable button placeholder should only exist when a user cannot remove', async () => { expect(findDeleteVarBtnPlaceholder().exists()).toBe(true); }); + + it('does not show the placeholder button', () => { + expect(findDeleteVarBtnPlaceholder().classes('gl-opacity-0')).toBe(true); + }); + + it('placeholder button will not delete the row on click', async () => { + expect(findAllCiVariableKeys()).toHaveLength(1); + expect(findDeleteVarBtnPlaceholder().exists()).toBe(true); + + await findDeleteVarBtnPlaceholder().trigger('click'); + + expect(findAllCiVariableKeys()).toHaveLength(1); + expect(findDeleteVarBtnPlaceholder().exists()).toBe(true); + }); }); }); diff --git a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js index 9e004f98715..67b327217ef 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js @@ -18,6 +18,7 @@ import { } from '~/vue_merge_request_widget/extensions/code_quality/constants'; import { codeQualityResponseNewErrors, + codeQualityResponseResolvedErrors, codeQualityResponseResolvedAndNewErrors, codeQualityResponseNoErrors, } from './mock_data'; @@ -37,6 +38,9 @@ describe('Code Quality extension', () => { const findToggleCollapsedButton = () => wrapper.findByTestId('toggle-button'); const findAllExtensionListItems = () => wrapper.findAllByTestId('extension-list-item'); const isCollapsable = () => wrapper.findByTestId('toggle-button').exists(); + const getNeutralIcon = () => wrapper.findByTestId('status-neutral-icon').exists(); + const getAlertIcon = () => wrapper.findByTestId('status-alert-icon').exists(); + const getSuccessIcon = () => wrapper.findByTestId('status-success-icon').exists(); const createComponent = () => { wrapper = mountExtended(extensionsContainer, { @@ -90,7 +94,7 @@ describe('Code Quality extension', () => { expect(isCollapsable()).toBe(false); }); - it('displays correct single Report', async () => { + it('displays new Errors finding', async () => { mockApi(HTTP_STATUS_OK, codeQualityResponseNewErrors); createComponent(); @@ -104,8 +108,29 @@ describe('Code Quality extension', () => { .replace(/%{strong_start}/g, '') .replace(/%{strong_end}/g, ''), ); + expect(isCollapsable()).toBe(true); + expect(getAlertIcon()).toBe(true); + }); + + it('displays resolved Errors finding', async () => { + mockApi(HTTP_STATUS_OK, codeQualityResponseResolvedErrors); + + createComponent(); + await waitForPromises(); + expect(wrapper.text()).toBe( + i18n + .singularCopy( + i18n.findings( + codeQualityResponseResolvedErrors.resolved_errors, + codeQualityPrefixes.fixed, + ), + ) + .replace(/%{strong_start}/g, '') + .replace(/%{strong_end}/g, ''), + ); expect(isCollapsable()).toBe(true); + expect(getSuccessIcon()).toBe(true); }); it('displays quality improvement and degradation', async () => { @@ -131,6 +156,7 @@ describe('Code Quality extension', () => { .replace(/%{strong_end}/g, ''), ); expect(isCollapsable()).toBe(true); + expect(getAlertIcon()).toBe(true); }); it('displays no detected errors', async () => { @@ -142,6 +168,7 @@ describe('Code Quality extension', () => { expect(wrapper.text()).toBe(i18n.noChanges); expect(isCollapsable()).toBe(false); + expect(getNeutralIcon()).toBe(true); }); }); diff --git a/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js b/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js index 093913075b8..cb23b730a93 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js +++ b/spec/frontend/vue_merge_request_widget/extentions/code_quality/mock_data.js @@ -23,6 +23,31 @@ export const codeQualityResponseNewErrors = { }, }; +export const codeQualityResponseResolvedErrors = { + status: 'success', + new_errors: [], + resolved_errors: [ + { + description: "Parsing error: 'return' outside of function", + severity: 'minor', + file_path: 'index.js', + line: 12, + }, + { + description: 'TODO found', + severity: 'minor', + file_path: '.gitlab-ci.yml', + line: 73, + }, + ], + existing_errors: [], + summary: { + total: 12235, + resolved: 0, + errored: 12235, + }, +}; + export const codeQualityResponseResolvedAndNewErrors = { status: 'failed', new_errors: [ diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_docker_instructions_spec.js.snap b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_docker_instructions_spec.js.snap new file mode 100644 index 00000000000..d14f66df8a1 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_docker_instructions_spec.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RunnerDockerInstructions renders contents 1`] = `"To install Runner in a container follow the instructions described in the GitLab documentation View installation instructions Close"`; diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_kubernetes_instructions_spec.js.snap b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_kubernetes_instructions_spec.js.snap new file mode 100644 index 00000000000..1172bf07dff --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/__snapshots__/runner_kubernetes_instructions_spec.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RunnerKubernetesInstructions renders contents 1`] = `"To install Runner in Kubernetes follow the instructions described in the GitLab documentation. View installation instructions Close"`; diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js new file mode 100644 index 00000000000..f9d700fe67f --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js @@ -0,0 +1,169 @@ +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import getRunnerSetupInstructionsQuery from '~/vue_shared/components/runner_instructions/graphql/get_runner_setup.query.graphql'; +import RunnerCliInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue'; + +import { mockRunnerPlatforms, mockInstructions, mockInstructionsWindows } from '../mock_data'; + +Vue.use(VueApollo); + +jest.mock('@gitlab/ui/dist/utils'); + +const mockPlatforms = mockRunnerPlatforms.data.runnerPlatforms.nodes.map( + ({ name, humanReadableName, architectures }) => ({ + name, + humanReadableName, + architectures: architectures?.nodes || [], + }), +); + +const [mockPlatform, mockPlatform2] = mockPlatforms; +const mockArchitectures = mockPlatform.architectures; + +describe('RunnerCliInstructions component', () => { + let wrapper; + let fakeApollo; + let runnerSetupInstructionsHandler; + + const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAlert = () => wrapper.findComponent(GlAlert); + const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item'); + const findBinaryDownloadButton = () => wrapper.findByTestId('binary-download-button'); + const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions'); + const findRegisterCommand = () => wrapper.findByTestId('register-command'); + + const createComponent = ({ props, ...options } = {}) => { + const requestHandlers = [[getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler]]; + + fakeApollo = createMockApollo(requestHandlers); + + wrapper = extendedWrapper( + shallowMount(RunnerCliInstructions, { + propsData: { + platform: mockPlatform, + registrationToken: 'MY_TOKEN', + ...props, + }, + apolloProvider: fakeApollo, + ...options, + }), + ); + }; + + beforeEach(() => { + runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockInstructions); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when the instructions are shown', () => { + beforeEach(async () => { + createComponent(); + await waitForPromises(); + }); + + it('should not show alert', async () => { + expect(findAlert().exists()).toBe(false); + }); + + it('should contain a number of dropdown items for the architecture options', () => { + expect(findArchitectureDropdownItems()).toHaveLength( + mockRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length, + ); + }); + + describe('should display instructions', () => { + const { installInstructions } = mockInstructions.data.runnerSetup; + + it('runner instructions are requested', () => { + expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({ + platform: 'linux', + architecture: 'amd64', + }); + }); + + it('binary instructions are shown', async () => { + const instructions = findBinaryInstructions().text(); + + expect(instructions).toBe(installInstructions.trim()); + }); + + it('register command is shown with a replaced token', async () => { + const command = findRegisterCommand().text(); + + expect(command).toBe( + 'sudo gitlab-runner register --url http://localhost/ --registration-token MY_TOKEN', + ); + }); + + it('architecture download link is shown', () => { + expect(findBinaryDownloadButton().attributes('href')).toBe( + mockArchitectures[0].downloadLocation, + ); + }); + }); + + describe('after another platform and architecture are selected', () => { + beforeEach(async () => { + runnerSetupInstructionsHandler.mockResolvedValue(mockInstructionsWindows); + + findArchitectureDropdownItems().at(1).vm.$emit('click'); + + wrapper.setProps({ platform: mockPlatform2 }); + await waitForPromises(); + }); + + it('runner instructions are requested', () => { + expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({ + platform: mockPlatform2.name, + architecture: mockPlatform2.architectures[0].name, + }); + }); + }); + }); + + describe('when a register token is not known', () => { + beforeEach(async () => { + createComponent({ props: { registrationToken: undefined } }); + await waitForPromises(); + }); + + it('register command is shown without a defined registration token', () => { + const instructions = findRegisterCommand().text(); + + expect(instructions).toBe(mockInstructions.data.runnerSetup.registerInstructions); + }); + }); + + describe('when apollo is loading', () => { + it('should show a loading icon', async () => { + createComponent(); + + expect(findGlLoadingIcon().exists()).toBe(true); + + await waitForPromises(); + + expect(findGlLoadingIcon().exists()).toBe(false); + }); + }); + + describe('when instructions cannot be loaded', () => { + beforeEach(async () => { + runnerSetupInstructionsHandler.mockRejectedValue(); + + createComponent(); + await waitForPromises(); + }); + + it('should show alert', () => { + expect(wrapper.emitted()).toEqual({ error: [[]] }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_docker_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_docker_instructions_spec.js new file mode 100644 index 00000000000..2922d261b24 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_docker_instructions_spec.js @@ -0,0 +1,28 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlButton } from '@gitlab/ui'; +import RunnerDockerInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue'; + +describe('RunnerDockerInstructions', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMount(RunnerDockerInstructions, {}); + }; + + const findButton = () => wrapper.findComponent(GlButton); + + beforeEach(() => { + createComponent(); + }); + + it('renders contents', () => { + expect(wrapper.text().replace(/\s+/g, ' ')).toMatchSnapshot(); + }); + + it('renders link', () => { + expect(findButton().attributes('href')).toBe( + 'https://docs.gitlab.com/runner/install/docker.html', + ); + }); +}); diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions_spec.js new file mode 100644 index 00000000000..0bfcc0e3d86 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions_spec.js @@ -0,0 +1,28 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlButton } from '@gitlab/ui'; +import RunnerKubernetesInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue'; + +describe('RunnerKubernetesInstructions', () => { + let wrapper; + + const createComponent = () => { + wrapper = shallowMount(RunnerKubernetesInstructions, {}); + }; + + const findButton = () => wrapper.findComponent(GlButton); + + beforeEach(() => { + createComponent(); + }); + + it('renders contents', () => { + expect(wrapper.text().replace(/\s+/g, ' ')).toMatchSnapshot(); + }); + + it('renders link', () => { + expect(findButton().attributes('href')).toBe( + 'https://docs.gitlab.com/runner/install/kubernetes.html', + ); + }); +}); diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js index 4e7ece4c930..19f2dd137ff 100644 --- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js +++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js @@ -1,4 +1,4 @@ -import { GlAlert, GlModal, GlButton, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui'; +import { GlAlert, GlModal, GlButton, GlSkeletonLoader } from '@gitlab/ui'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; @@ -7,10 +7,12 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import getRunnerPlatformsQuery from '~/vue_shared/components/runner_instructions/graphql/get_runner_platforms.query.graphql'; -import getRunnerSetupInstructionsQuery from '~/vue_shared/components/runner_instructions/graphql/get_runner_setup.query.graphql'; import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; +import RunnerCliInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue'; +import RunnerDockerInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue'; +import RunnerKubernetesInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue'; -import { mockRunnerPlatforms, mockInstructions, mockInstructionsWindows } from './mock_data'; +import { mockRunnerPlatforms } from './mock_data'; Vue.use(VueApollo); @@ -36,24 +38,16 @@ describe('RunnerInstructionsModal component', () => { let wrapper; let fakeApollo; let runnerPlatformsHandler; - let runnerSetupInstructionsHandler; const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); - const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findAlert = () => wrapper.findComponent(GlAlert); const findModal = () => wrapper.findComponent(GlModal); const findPlatformButtonGroup = () => wrapper.findByTestId('platform-buttons'); const findPlatformButtons = () => findPlatformButtonGroup().findAllComponents(GlButton); - const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item'); - const findBinaryDownloadButton = () => wrapper.findByTestId('binary-download-button'); - const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions'); - const findRegisterCommand = () => wrapper.findByTestId('register-command'); + const findRunnerCliInstructions = () => wrapper.findComponent(RunnerCliInstructions); const createComponent = ({ props, shown = true, ...options } = {}) => { - const requestHandlers = [ - [getRunnerPlatformsQuery, runnerPlatformsHandler], - [getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler], - ]; + const requestHandlers = [[getRunnerPlatformsQuery, runnerPlatformsHandler]]; fakeApollo = createMockApollo(requestHandlers); @@ -77,7 +71,6 @@ describe('RunnerInstructionsModal component', () => { beforeEach(() => { runnerPlatformsHandler = jest.fn().mockResolvedValue(mockRunnerPlatforms); - runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockInstructions); }); afterEach(() => { @@ -102,87 +95,12 @@ describe('RunnerInstructionsModal component', () => { expect(buttons).toHaveLength(mockRunnerPlatforms.data.runnerPlatforms.nodes.length); }); - it('should contain a number of dropdown items for the architecture options', () => { - expect(findArchitectureDropdownItems()).toHaveLength( - mockRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length, - ); - }); - - describe('should display default instructions', () => { - const { installInstructions } = mockInstructions.data.runnerSetup; - - it('runner instructions are requested', () => { - expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({ - platform: 'linux', - architecture: 'amd64', - }); - }); - - it('binary instructions are shown', async () => { - const instructions = findBinaryInstructions().text(); - - expect(instructions).toBe(installInstructions.trim()); - }); - - it('register command is shown with a replaced token', async () => { - const command = findRegisterCommand().text(); - - expect(command).toBe( - 'sudo gitlab-runner register --url http://localhost/ --registration-token MY_TOKEN', - ); - }); - }); - - describe('after a platform and architecture are selected', () => { - const windowsIndex = 2; - const { installInstructions } = mockInstructionsWindows.data.runnerSetup; - - beforeEach(async () => { - runnerSetupInstructionsHandler.mockResolvedValue(mockInstructionsWindows); - - findPlatformButtons().at(windowsIndex).vm.$emit('click'); - await waitForPromises(); - }); + it('should display architecture options', () => { + const { architectures } = findRunnerCliInstructions().props('platform'); - it('runner instructions are requested', () => { - expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({ - platform: 'windows', - architecture: 'amd64', - }); - }); - - it('architecture download link is updated', () => { - const architectures = - mockRunnerPlatforms.data.runnerPlatforms.nodes[windowsIndex].architectures.nodes; - - expect(findBinaryDownloadButton().attributes('href')).toBe( - architectures[0].downloadLocation, - ); - }); - - it('other binary instructions are shown', () => { - const instructions = findBinaryInstructions().text(); - - expect(instructions).toBe(installInstructions.trim()); - }); - - it('register command is shown', () => { - const command = findRegisterCommand().text(); - - expect(command).toBe( - './gitlab-runner.exe register --url http://localhost/ --registration-token MY_TOKEN', - ); - }); - - it('runner instructions are requested with another architecture', async () => { - findArchitectureDropdownItems().at(1).vm.$emit('click'); - await waitForPromises(); - - expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({ - platform: 'windows', - architecture: '386', - }); - }); + expect(architectures).toEqual( + mockRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes, + ); }); describe('when the modal resizes', () => { @@ -202,16 +120,14 @@ describe('RunnerInstructionsModal component', () => { }); }); - describe('when a register token is not known', () => { + describe.each([null, 'DEFINED'])('when registration token is %p', (token) => { beforeEach(async () => { - createComponent({ props: { registrationToken: undefined } }); + createComponent({ props: { registrationToken: token } }); await waitForPromises(); }); it('register command is shown without a defined registration token', () => { - const instructions = findRegisterCommand().text(); - - expect(instructions).toBe(mockInstructions.data.runnerSetup.registerInstructions); + expect(findRunnerCliInstructions().props('registrationToken')).toBe(token); }); }); @@ -221,21 +137,33 @@ describe('RunnerInstructionsModal component', () => { await waitForPromises(); }); - it('runner instructions for the default selected platform are requested', () => { - expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({ - platform: 'osx', - architecture: 'amd64', - }); + it('should preselect', () => { + const selected = findPlatformButtons() + .filter((btn) => btn.props('selected')) + .at(0); + + expect(selected.text()).toBe('macOS'); }); - it('sets the focus on the default selected platform', () => { - const findOsxPlatformButton = () => wrapper.findComponent({ ref: 'osx' }); + it('runner instructions for the default selected platform are requested', () => { + const { name } = findRunnerCliInstructions().props('platform'); - findOsxPlatformButton().element.focus = jest.fn(); + expect(name).toBe('osx'); + }); + }); - findModal().vm.$emit('shown'); + describe.each` + platform | component + ${'docker'} | ${RunnerDockerInstructions} + ${'kubernetes'} | ${RunnerKubernetesInstructions} + `('with platform "$platform"', ({ platform, component }) => { + beforeEach(async () => { + createComponent({ props: { defaultPlatformName: platform } }); + await waitForPromises(); + }); - expect(findOsxPlatformButton().element.focus).toHaveBeenCalled(); + it(`runner instructions for ${platform} are shown`, () => { + expect(wrapper.findComponent(component).exists()).toBe(true); }); }); @@ -247,7 +175,6 @@ describe('RunnerInstructionsModal component', () => { it('does not fetch instructions', () => { expect(runnerPlatformsHandler).not.toHaveBeenCalled(); - expect(runnerSetupInstructionsHandler).not.toHaveBeenCalled(); }); }); @@ -255,43 +182,41 @@ describe('RunnerInstructionsModal component', () => { it('should show a skeleton loader', async () => { createComponent(); await nextTick(); - await nextTick(); expect(findSkeletonLoader().exists()).toBe(true); - expect(findGlLoadingIcon().exists()).toBe(false); - - // wait on fetch of both `platforms` and `instructions` - await nextTick(); - await nextTick(); - - expect(findGlLoadingIcon().exists()).toBe(true); }); it('once loaded, should not show a loading state', async () => { createComponent(); - await waitForPromises(); expect(findSkeletonLoader().exists()).toBe(false); - expect(findGlLoadingIcon().exists()).toBe(false); }); }); - describe('when instructions cannot be loaded', () => { - beforeEach(async () => { - runnerSetupInstructionsHandler.mockRejectedValue(); + describe('errors', () => { + it('should show an alert when platforms cannot be loaded', async () => { + runnerPlatformsHandler.mockRejectedValue(); createComponent(); await waitForPromises(); - }); - it('should show alert', () => { expect(findAlert().exists()).toBe(true); }); - it('should not show instructions', () => { - expect(findBinaryInstructions().exists()).toBe(false); - expect(findRegisterCommand().exists()).toBe(false); + it('should show alert when instructions cannot be loaded', async () => { + createComponent(); + await waitForPromises(); + + findRunnerCliInstructions().vm.$emit('error'); + await waitForPromises(); + + expect(findAlert().exists()).toBe(true); + + findAlert().vm.$emit('dismiss'); + await nextTick(); + + expect(findAlert().exists()).toBe(false); }); }); @@ -308,14 +233,16 @@ describe('RunnerInstructionsModal component', () => { describe('show()', () => { let mockShow; + let mockClose; beforeEach(() => { mockShow = jest.fn(); + mockClose = jest.fn(); createComponent({ shown: false, stubs: { - GlModal: getGlModalStub({ show: mockShow }), + GlModal: getGlModalStub({ show: mockShow, close: mockClose }), }, }); }); @@ -325,6 +252,12 @@ describe('RunnerInstructionsModal component', () => { expect(mockShow).toHaveBeenCalledTimes(1); }); + + it('delegates close()', () => { + wrapper.vm.close(); + + expect(mockClose).toHaveBeenCalledTimes(1); + }); }); }); }); diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index 2bb85e7b6b8..c76eb08820a 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -49,19 +49,26 @@ RSpec.describe VersionCheckHelper do describe '#show_security_patch_upgrade_alert?' do describe 'return conditions' do - where(:show_version_check, :gitlab_version_check, :result) do + where(:feature_enabled, :show_version_check, :gitlab_version_check, :result) do [ - [false, nil, false], - [false, { "severity" => "success" }, false], - [false, { "severity" => "danger" }, false], - [true, nil, false], - [true, { "severity" => "success" }, false], - [true, { "severity" => "danger" }, true] + [false, false, nil, false], + [false, false, { "severity" => "success" }, false], + [false, false, { "severity" => "danger" }, false], + [false, true, nil, false], + [false, true, { "severity" => "success" }, false], + [false, true, { "severity" => "danger" }, false], + [true, false, nil, false], + [true, false, { "severity" => "success" }, false], + [true, false, { "severity" => "danger" }, false], + [true, true, nil, false], + [true, true, { "severity" => "success" }, false], + [true, true, { "severity" => "danger" }, true] ] end with_them do before do + stub_feature_flags(critical_security_alert: feature_enabled) allow(helper).to receive(:show_version_check?).and_return(show_version_check) allow(helper).to receive(:gitlab_version_check).and_return(gitlab_version_check) end diff --git a/spec/migrations/20210804150320_create_base_work_item_types_spec.rb b/spec/migrations/20210804150320_create_base_work_item_types_spec.rb index 5626b885626..e7f76eb0ae0 100644 --- a/spec/migrations/20210804150320_create_base_work_item_types_spec.rb +++ b/spec/migrations/20210804150320_create_base_work_item_types_spec.rb @@ -17,7 +17,8 @@ RSpec.describe CreateBaseWorkItemTypes, :migration, feature_category: :team_plan } end - after(:all) do + # We use append_after to make sure this runs after the schema was reset to its latest state + append_after(:all) do # Make sure base types are recreated after running the migration # because migration specs are not run in a transaction reset_work_item_types diff --git a/spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb b/spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb index 2a19dc025a7..4c7ef9ac1e8 100644 --- a/spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb +++ b/spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb @@ -17,7 +17,7 @@ RSpec.describe UpsertBaseWorkItemTypes, :migration, feature_category: :team_plan } end - after(:all) do + append_after(:all) do # Make sure base types are recreated after running the migration # because migration specs are not run in a transaction reset_work_item_types diff --git a/spec/migrations/20211126204445_add_task_to_work_item_types_spec.rb b/spec/migrations/20211126204445_add_task_to_work_item_types_spec.rb index 32edd3615ff..db68e895b61 100644 --- a/spec/migrations/20211126204445_add_task_to_work_item_types_spec.rb +++ b/spec/migrations/20211126204445_add_task_to_work_item_types_spec.rb @@ -18,7 +18,7 @@ RSpec.describe AddTaskToWorkItemTypes, :migration, feature_category: :team_plann } end - after(:all) do + append_after(:all) do # Make sure base types are recreated after running the migration # because migration specs are not run in a transaction reset_work_item_types diff --git a/spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb b/spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb index 3ab33367303..6284608becb 100644 --- a/spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb +++ b/spec/migrations/20221018050323_add_objective_and_keyresult_to_work_item_types_spec.rb @@ -20,7 +20,7 @@ RSpec.describe AddObjectiveAndKeyresultToWorkItemTypes, :migration, feature_cate } end - after(:all) do + append_after(:all) do # Make sure base types are recreated after running the migration # because migration specs are not run in a transaction reset_work_item_types diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb index 62bb9576695..a9d7c6af959 100644 --- a/spec/support/helpers/api_helpers.rb +++ b/spec/support/helpers/api_helpers.rb @@ -19,7 +19,7 @@ module ApiHelpers # => "/api/v2/issues?foo=bar&private_token=..." # # Returns the relative path to the requested API resource - def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil, job_token: nil, access_token: nil) + def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil, job_token: nil, access_token: nil, admin_mode: false) full_path = "/api/#{version}#{path}" if oauth_access_token @@ -31,7 +31,12 @@ module ApiHelpers elsif access_token query_string = "access_token=#{access_token.token}" elsif user - personal_access_token = create(:personal_access_token, user: user) + personal_access_token = if admin_mode && user.admin? + create(:personal_access_token, :admin_mode, user: user) + else + create(:personal_access_token, user: user) + end + query_string = "private_token=#{personal_access_token.token}" end diff --git a/spec/tooling/danger/specs_spec.rb b/spec/tooling/danger/specs_spec.rb index dcc1f592062..422923827a8 100644 --- a/spec/tooling/danger/specs_spec.rb +++ b/spec/tooling/danger/specs_spec.rb @@ -245,15 +245,16 @@ RSpec.describe Tooling::Danger::Specs, feature_category: :tooling do " let_it_be(:user) { create(:user) }", " end", " describe 'GET \"time_summary\"' do", - " end" - ] - end - - let(:matching_lines) do - [ - "+ RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController, feature_category: :planning_analytics do", - "+RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do", - "+ RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do" + " end", + " \n", + "RSpec.describe Projects :aggregate_failures,", + " feature_category: planning_analytics do", + " \n", + "RSpec.describe Epics :aggregate_failures,", + " ee: true do", + "\n", + "RSpec.describe Issues :aggregate_failures,", + " feature_category: :team_planning do" ] end @@ -264,14 +265,24 @@ RSpec.describe Tooling::Danger::Specs, feature_category: :tooling do "+ let_it_be(:user) { create(:user) }", "- end", "+ describe 'GET \"time_summary\"' do", - "+ RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do" + "+ RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do", + "+RSpec.describe Projects :aggregate_failures,", + "+ feature_category: planning_analytics do", + "+RSpec.describe Epics :aggregate_failures,", + "+ ee: true do", + "+RSpec.describe Issues :aggregate_failures," ] end + before do + allow(specs.helper).to receive(:changed_lines).with(filename).and_return(changed_lines) + end + it 'adds suggestions at the correct lines', :aggregate_failures do [ { suggested_line: "RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do", number: 5 }, - { suggested_line: " RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do", number: 10 } + { suggested_line: " RSpec.describe Projects::Analytics::CycleAnalytics::SummaryController do", number: 10 }, + { suggested_line: "RSpec.describe Epics :aggregate_failures,", number: 19 } ].each do |test_case| comment = format(template, suggested_line: test_case[:suggested_line]) diff --git a/tooling/danger/specs.rb b/tooling/danger/specs.rb index c7baf920314..6c0459a4344 100644 --- a/tooling/danger/specs.rb +++ b/tooling/danger/specs.rb @@ -45,12 +45,12 @@ module Tooling for background information and alternative options. SUGGEST_COMMENT - FEATURE_CATEGORY_REGEX = /^\+.?RSpec\.describe(.+)(?!feature_category)/.freeze + RSPEC_TOP_LEVEL_DESCRIBE_REGEX = /^\+.?RSpec\.describe(.+)/.freeze FEATURE_CATEGORY_SUGGESTION = <<~SUGGESTION_MARKDOWN Consider adding `feature_category: <feature_category_name>` for this example if it is not set already. See [testing best practices](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#feature-category-metadata). SUGGESTION_MARKDOWN - FEATURE_CATEGORY_EXCLUDE = 'feature_category' + FEATURE_CATEGORY_KEYWORD = 'feature_category' def changed_specs_files(ee: :include) changed_files = helper.all_changed_files @@ -86,13 +86,26 @@ module Tooling end def add_suggestions_for_feature_category(filename) - add_suggestion( - filename, - FEATURE_CATEGORY_REGEX, - FEATURE_CATEGORY_SUGGESTION, - nil, - FEATURE_CATEGORY_EXCLUDE - ) + file_lines = project_helper.file_lines(filename) + changed_lines = helper.changed_lines(filename) + + changed_lines.each_with_index do |changed_line, i| + next unless changed_line =~ RSPEC_TOP_LEVEL_DESCRIBE_REGEX + + next_line_in_file = file_lines[file_lines.find_index(changed_line.delete_prefix('+')) + 1] + + if changed_line.include?(FEATURE_CATEGORY_KEYWORD) || next_line_in_file.to_s.include?(FEATURE_CATEGORY_KEYWORD) + next + end + + line_number = file_lines.find_index(changed_line.delete_prefix('+')) + next unless line_number + + suggested_line = file_lines[line_number] + + text = format(comment(FEATURE_CATEGORY_SUGGESTION), suggested_line: suggested_line) + markdown(text, file: filename, line: line_number + 1) + end end private |