Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7e7619d2c9
commit
c07666f925
|
|
@ -83,7 +83,6 @@ Database/AvoidUsingPluckWithoutLimit:
|
|||
- 'app/services/merge_requests/pushed_branches_service.rb'
|
||||
- 'app/services/projects/unlink_fork_service.rb'
|
||||
- 'ee/app/finders/ee/issuables/label_filter.rb'
|
||||
- 'ee/app/finders/ee/merge_requests_finder.rb'
|
||||
- 'ee/app/finders/groups_with_templates_finder.rb'
|
||||
- 'ee/app/finders/namespaces/billed_users_finder.rb'
|
||||
- 'ee/app/finders/namespaces/free_user_cap/users_without_added_members_finder.rb'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ Gitlab/FeatureFlagWithoutActor:
|
|||
- 'app/controllers/projects/settings/integrations_controller.rb'
|
||||
- 'app/controllers/repositories/git_http_controller.rb'
|
||||
- 'app/finders/abuse_reports_finder.rb'
|
||||
- 'app/finders/merge_requests_finder.rb'
|
||||
- 'app/graphql/types/namespace_type.rb'
|
||||
- 'app/graphql/types/project_type.rb'
|
||||
- 'app/helpers/auto_devops_helper.rb'
|
||||
|
|
@ -65,7 +64,6 @@ Gitlab/FeatureFlagWithoutActor:
|
|||
- 'ee/app/controllers/ee/admin/application_settings_controller.rb'
|
||||
- 'ee/app/controllers/ee/omniauth_callbacks_controller.rb'
|
||||
- 'ee/app/controllers/groups/billings_controller.rb'
|
||||
- 'ee/app/finders/ee/merge_requests_finder.rb'
|
||||
- 'ee/app/graphql/resolvers/ai/code_suggestions_access_resolver.rb'
|
||||
- 'ee/app/graphql/types/epic_type.rb'
|
||||
- 'ee/app/helpers/billing_plans_helper.rb'
|
||||
|
|
|
|||
|
|
@ -38,9 +38,7 @@ Gitlab/NoFindInWorkers:
|
|||
- 'app/workers/issuable_export_csv_worker.rb'
|
||||
- 'app/workers/issues/placement_worker.rb'
|
||||
- 'app/workers/members_destroyer/unassign_issuables_worker.rb'
|
||||
- 'app/workers/merge_requests/delete_source_branch_worker.rb'
|
||||
- 'app/workers/merge_requests/handle_assignees_change_worker.rb'
|
||||
- 'app/workers/merge_requests/resolve_todos_worker.rb'
|
||||
- 'app/workers/merge_worker.rb'
|
||||
- 'app/workers/namespaces/root_statistics_worker.rb'
|
||||
- 'app/workers/namespaces/schedule_aggregation_worker.rb'
|
||||
|
|
|
|||
|
|
@ -856,7 +856,6 @@ Layout/LineLength:
|
|||
- 'ee/lib/gitlab/ci/reports/security/locations/cluster_image_scanning.rb'
|
||||
- 'ee/lib/gitlab/contribution_analytics/data_collector.rb'
|
||||
- 'ee/lib/gitlab/elastic/group_search_results.rb'
|
||||
- 'ee/lib/gitlab/elastic/project_search_results.rb'
|
||||
- 'ee/lib/gitlab/expiring_subscription_message.rb'
|
||||
- 'ee/lib/gitlab/geo.rb'
|
||||
- 'ee/lib/gitlab/geo/git_ssh_proxy.rb'
|
||||
|
|
@ -2117,7 +2116,6 @@ Layout/LineLength:
|
|||
- 'lib/gitlab/grape_logging/formatters/lograge_with_timestamp.rb'
|
||||
- 'lib/gitlab/grape_logging/loggers/client_env_logger.rb'
|
||||
- 'lib/gitlab/graphql/timeout.rb'
|
||||
- 'lib/gitlab/group_search_results.rb'
|
||||
- 'lib/gitlab/hook_data/key_builder.rb'
|
||||
- 'lib/gitlab/hotlinking_detector.rb'
|
||||
- 'lib/gitlab/http_io.rb'
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
---
|
||||
Migration/PreventEnablingLockRetriesForTransactionalMigrations:
|
||||
Exclude:
|
||||
- 'db/migrate/20240221145450_create_audit_events_instance_streaming_event_type_filters.rb'
|
||||
- 'db/migrate/20240304184128_create_ci_build_names_table.rb'
|
||||
- 'db/migrate/20240304195555_add_search_vector_to_p_ci_build_names.rb'
|
||||
- 'db/migrate/20240304195852_create_partitions_for_p_ci_build_names.rb'
|
||||
- 'db/migrate/20240305201830_add_custom_headers_to_web_hook.rb'
|
||||
- 'db/migrate/20240320060913_add_container_scanning_for_registry_toggle_to_security_project_settings.rb'
|
||||
- 'db/migrate/20240320062459_add_trial_to_subscription_add_on_purchases.rb'
|
||||
- 'db/migrate/20240325115147_create_project_saved_replies_table.rb'
|
||||
- 'db/migrate/20240325150539_add_pre_receive_secret_detection_enabled_to_project_security_settings.rb'
|
||||
- 'db/migrate/20240326144116_add_zoekt_settings_to_application_settings.rb'
|
||||
- 'db/migrate/20240327114933_add_override_changes_requested_to_merge_request.rb'
|
||||
- 'db/migrate/20240402181020_create_audit_events_streaming_instance_namespace_filters.rb'
|
||||
- 'db/migrate/20240403000000_add_fallback_behavior_to_scan_result_policy_reads.rb'
|
||||
- 'db/migrate/20240404191440_add_early_access_program_participant_to_user_preferences.rb'
|
||||
- 'db/migrate/20240404192955_create_early_access_program_tracking_events.rb'
|
||||
- 'db/migrate/20240408105626_add_send_bot_message_to_policies.rb'
|
||||
- 'db/migrate/20240409013009_add_importers_to_application_settings.rb'
|
||||
- 'db/migrate/20240418135657_add_tickets_confidential_by_default_to_service_desk_settings.rb'
|
||||
- 'db/migrate/20240419071412_create_audit_events_streaming_group_namespace_filters.rb'
|
||||
- 'db/migrate/20240419124207_add_runner_owner_namespace_id_column_to_ci_running_builds.rb'
|
||||
- 'db/migrate/20240429182325_create_custom_software_licenses_table.rb'
|
||||
- 'db/migrate/20240503173159_create_user_audit_events.rb'
|
||||
- 'db/migrate/20240503174905_create_group_audit_events.rb'
|
||||
- 'db/migrate/20240503175347_create_project_audit_events.rb'
|
||||
- 'db/migrate/20240505153633_create_instance_audit_events.rb'
|
||||
- 'db/migrate/20240519141301_add_metadata_to_member_approvals.rb'
|
||||
- 'db/migrate/20240528142856_add_organization_id_to_subscription_add_on_purchases.rb'
|
||||
- 'db/migrate/20240604051641_create_partitions_for_p_ci_build_sources.rb'
|
||||
- 'db/migrate/20240604081941_add_approval_policy_rule_id_to_approval_group_rules.rb'
|
||||
- 'db/migrate/20240604082005_add_approval_policy_rule_id_to_approval_project_rules.rb'
|
||||
- 'db/migrate/20240604082023_add_approval_policy_rule_id_to_approval_merge_request_rules.rb'
|
||||
- 'db/migrate/20240604082113_add_approval_policy_rule_id_to_software_license_policies.rb'
|
||||
- 'db/migrate/20240604082344_add_approval_policy_rule_id_to_scan_result_policy_violations.rb'
|
||||
- 'db/migrate/20240606124806_add_organization_id_to_snippets.rb'
|
||||
- 'db/migrate/20240607035355_add_member_role_id_to_ldap_group_links.rb'
|
||||
- 'db/migrate/20240612034702_create_import_source_user_placeholder_reference.rb'
|
||||
- 'db/migrate/20240625050115_add_member_role_id_to_group_group_links.rb'
|
||||
- 'db/migrate/20240627055916_add_uploaded_by_user_id_to_uploads.rb'
|
||||
- 'db/migrate/20240628203616_update_scheduled_scans_max_concurrency_in_application_settings_for_self_managed.rb'
|
||||
- 'db/migrate/20240701153843_add_work_items_dates_sources_sync_to_issues_trigger.rb'
|
||||
- 'db/migrate/20240703043908_create_table_p_ci_build_tags.rb'
|
||||
- 'db/migrate/20240703054001_ensure_unique_id_for_p_ci_build_tags.rb'
|
||||
- 'db/migrate/20240703082453_create_partitions_for_p_ci_build_tags.rb'
|
||||
- 'db/migrate/20240705020837_add_last_access_from_pipl_country_at_to_users.rb'
|
||||
- 'db/migrate/20240717140800_add_encrypted_shared_secret_to_external_status_checks.rb'
|
||||
- 'db/migrate/20240719090901_add_plan_id_to_ci_pending_builds.rb'
|
||||
- 'db/migrate/20240719090902_add_allowed_plan_ids_to_ci_runners.rb'
|
||||
- 'db/migrate/20240725154651_add_project_id_to_packages_dependencies.rb'
|
||||
- 'db/migrate/20240813095256_add_disable_password_authentication_for_enterprise_users_to_saml_providers.rb'
|
||||
- 'db/migrate/20240813170304_allow_null_for_upstream_id_in_virtual_registries_packages_maven_cached_responses.rb'
|
||||
- 'db/migrate/20240816040224_create_import_member_placeholder_references.rb'
|
||||
- 'db/migrate/20240816061320_allow_top_level_group_owners_to_create_service_accounts.rb'
|
||||
- 'db/migrate/20240819104531_add_observability_backend_ssl_verification_enabled_to_application_settings.rb'
|
||||
- 'db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb'
|
||||
- 'db/migrate/20240829125828_change_time_estimate_default_from_null_to_zero_on_issues.rb'
|
||||
- 'db/migrate/20240829125928_change_time_estimate_default_from_null_to_zero_on_merge_requests.rb'
|
||||
- 'db/migrate/20240904133620_add_spp_repository_pipeline_access_cascading_setting.rb'
|
||||
- 'db/migrate/20240905124106_add_policy_action_limit_application_setting.rb'
|
||||
- 'db/migrate/20240917054235_create_wiki_page_user_mentions.rb'
|
||||
- 'db/migrate/20240923130542_add_sharding_key_id_to_ci_runners.rb'
|
||||
- 'db/migrate/20241008101731_create_catalog_resource_component_last_usages_table.rb'
|
||||
- 'db/migrate/20241016125024_add_metadata_to_zoekt_indices.rb'
|
||||
- 'db/migrate/20241022141656_add_policy_tuning_to_policies.rb'
|
||||
- 'db/migrate/20241030031829_add_resource_usage_limits_to_application_settings.rb'
|
||||
- 'db/migrate/20241104224549_add_allow_list_integrations_settings_to_application_settings.rb'
|
||||
- 'db/migrate/20241107131541_add_user_seat_management_to_application_settings.rb'
|
||||
- 'db/migrate/20241112123436_update_seat_control_in_application_settings.rb'
|
||||
- 'db/migrate/20241115075017_add_member_role_id_to_project_group_links.rb'
|
||||
- 'db/migrate/20241122121328_add_approval_policy_action_idx_to_approval_project_rules.rb'
|
||||
- 'db/migrate/20241122121350_add_approval_policy_action_idx_to_approval_merge_request_rules.rb'
|
||||
- 'db/migrate/20241122121652_add_action_idx_to_scan_result_policies.rb'
|
||||
- 'db/migrate/20241201162318_add_custom_roles_to_scan_result_policies.rb'
|
||||
- 'db/migrate/20241202054640_add_vulnerability_events_to_web_hooks.rb'
|
||||
- 'db/migrate/20250108062227_add_extended_grat_expiry_webhook_execute_to_namespace_settings.rb'
|
||||
- 'db/migrate/20250108062256_add_extended_prat_expiry_webhook_execute_to_project_settings.rb'
|
||||
- 'db/migrate/20250109102301_add_o11y_settings_to_application_settings.rb'
|
||||
- 'db/migrate/20250210225045_add_vscode_extension_marketplace_to_application_setting.rb'
|
||||
- 'db/migrate/20250227095537_add_organization_id_to_fork_networks.rb'
|
||||
- 'db/migrate/20250313142550_add_job_token_policies_enabled_column_to_namespace_settings.rb'
|
||||
- 'db/migrate/20250320072111_add_security_policies_namespace_setting.rb'
|
||||
- 'db/migrate/20250320103054_add_organization_id_to_subscription_seat_assignments.rb'
|
||||
- 'db/migrate/20250325071830_add_member_approval_events_to_web_hooks.rb'
|
||||
- 'db/post_migrate/20240205170838_change_approval_merge_request_rules_vulnerability_states_default.rb'
|
||||
- 'db/post_migrate/20240205171942_change_approval_project_rules_vulnerability_states_default.rb'
|
||||
- 'db/post_migrate/20240318180554_drop_promote_ultimate_features_at_column.rb'
|
||||
- 'db/post_migrate/20240408135652_drop_external_approval_rules_protected_branches_table.rb'
|
||||
- 'db/post_migrate/20240513042657_cleanup_bigint_conversions_for_ci_pipelines.rb'
|
||||
- 'db/post_migrate/20240513065051_ensure_id_uniqueness_for_p_ci_builds_execution_configs.rb'
|
||||
- 'db/post_migrate/20240528115140_change_projects_organization_id_default.rb'
|
||||
- 'db/post_migrate/20240627165253_drop_token_with_ivs_table.rb'
|
||||
- 'db/post_migrate/20240709014310_cleanup_bigint_conversions_for_p_ci_builds_attempt2.rb'
|
||||
- 'db/post_migrate/20240715055251_cleanup_bigint_conversion_for_merge_requests_head_pipeline_id.rb'
|
||||
- 'db/post_migrate/20240715055311_cleanup_bigint_conversion_for_merge_request_metrics_pipeline_id.rb'
|
||||
- 'db/post_migrate/20240715055356_cleanup_bigint_conversion_for_merge_trains_pipeline_id.rb'
|
||||
- 'db/post_migrate/20240715055415_cleanup_bigint_conversion_for_vulnerability_feedback_pipeline_id.rb'
|
||||
- 'db/post_migrate/20240715055432_cleanup_bigint_conversion_for_vulnerability_occurrence_pipelines_pipeline_id.rb'
|
||||
- 'db/post_migrate/20240719090903_track_plan_deletions.rb'
|
||||
- 'db/post_migrate/20240807063339_remove_application_settings_required_instance_ci_template_column.rb'
|
||||
- 'db/post_migrate/20240814052751_cleanup_bigint_conversion_for_packages_build_infos_pipeline_id.rb'
|
||||
- 'db/post_migrate/20240903062554_change_raw_usage_data_org_default.rb'
|
||||
- 'db/post_migrate/20240920131119_drop_p_ci_finished_build_ch_sync_event_project_id_default.rb'
|
||||
- 'db/post_migrate/20250204181430_cleanup_bigint_conversion_for_geo_event_log_geo_event_id.rb'
|
||||
- 'db/post_migrate/20250311113123_ensure_id_uniqueness_for_p_ci_runners.rb'
|
||||
- 'db/post_migrate/20250311113127_ensure_id_uniqueness_for_p_ci_runner_machines.rb'
|
||||
- 'ee/db/embedding/migrate/20230420103900_create_tanuki_bot_mvc.rb'
|
||||
- 'ee/db/embedding/migrate/20230501095300_add_version_to_tanuki_bot_mvc.rb'
|
||||
- 'ee/db/embedding/migrate/20230821103900_create_vertex_gitlab_docs.rb'
|
||||
- 'ee/db/embedding/post_migrate/20231030093125_drop_tanuki_bot_mvc_table.rb'
|
||||
- 'ee/db/geo/post_migrate/20240214235202_remove_snippet_repository_registry_force_to_redownload_column.rb'
|
||||
- 'ee/db/geo/post_migrate/20240214235239_remove_project_wiki_repository_registry_force_to_redownload_column.rb'
|
||||
- 'ee/db/geo/post_migrate/20240214235323_remove_project_repository_registry_force_to_redownload_column.rb'
|
||||
- 'ee/db/geo/post_migrate/20240214235349_remove_group_wiki_repository_registry_force_to_redownload_column.rb'
|
||||
- 'ee/db/geo/post_migrate/20240214235418_remove_design_management_repository_registry_force_to_redownload_column.rb'
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
# Cop supports --autocorrect.
|
||||
RSpec/AnyInstanceOf:
|
||||
Exclude:
|
||||
- 'ee/spec/features/issues/new/form_spec.rb'
|
||||
- 'ee/spec/features/projects/new_project_spec.rb'
|
||||
- 'ee/spec/features/security/project/internal_access_spec.rb'
|
||||
- 'ee/spec/features/security/project/private_access_spec.rb'
|
||||
|
|
|
|||
|
|
@ -146,7 +146,6 @@ RSpec/BeEq:
|
|||
- 'ee/spec/lib/gitlab/insights/finders/projects_finder_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/insights/project_insights_config_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/licenses/submit_license_usage_data_banner_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/llm/ai_gateway/docs_client_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/llm/chain/answer_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/llm/chain/requests/ai_gateway_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/llm/chain/tools/gitlab_documentation/executor_spec.rb'
|
||||
|
|
|
|||
|
|
@ -146,7 +146,6 @@ RSpec/BeforeAllRoleAssignment:
|
|||
- 'ee/spec/finders/incident_management/oncall_schedules_finder_spec.rb'
|
||||
- 'ee/spec/finders/iterations/cadences_finder_spec.rb'
|
||||
- 'ee/spec/finders/iterations_finder_spec.rb'
|
||||
- 'ee/spec/finders/merge_requests_finder_spec.rb'
|
||||
- 'ee/spec/finders/snippets_finder_spec.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/charts.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics/contributions_spec.rb'
|
||||
|
|
|
|||
|
|
@ -2708,7 +2708,6 @@ RSpec/ContextWording:
|
|||
- 'spec/tasks/gitlab/dependency_proxy/migrate_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/gitaly_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/lfs/migrate_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/packages/migrate_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/terraform/migrate_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/workhorse_rake_spec.rb'
|
||||
- 'spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1960,7 +1960,6 @@ RSpec/FeatureCategory:
|
|||
- 'spec/lib/gitlab/ci/artifacts/logger_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/artifacts/metrics_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/badge/coverage/metadata_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/badge/coverage/report_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/badge/coverage/template_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/badge/pipeline/metadata_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/badge/pipeline/template_spec.rb'
|
||||
|
|
@ -3586,7 +3585,6 @@ RSpec/FeatureCategory:
|
|||
- 'spec/serializers/fork_namespace_serializer_spec.rb'
|
||||
- 'spec/serializers/group_access_token_entity_spec.rb'
|
||||
- 'spec/serializers/group_access_token_serializer_spec.rb'
|
||||
- 'spec/serializers/group_child_entity_spec.rb'
|
||||
- 'spec/serializers/group_child_serializer_spec.rb'
|
||||
- 'spec/serializers/group_deploy_key_entity_spec.rb'
|
||||
- 'spec/serializers/group_link/group_group_link_serializer_spec.rb'
|
||||
|
|
@ -3689,7 +3687,6 @@ RSpec/FeatureCategory:
|
|||
- 'spec/tasks/gitlab/ldap_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/lfs/check_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/lfs/migrate_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/packages/migrate_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/pages_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/password_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/praefect_rake_spec.rb'
|
||||
|
|
|
|||
|
|
@ -188,7 +188,6 @@ Sidekiq/EnforceDatabaseHealthSignalDeferral:
|
|||
- 'ee/app/workers/onboarding/progress_tracking_worker.rb'
|
||||
- 'ee/app/workers/package_metadata/advisories_sync_worker.rb'
|
||||
- 'ee/app/workers/package_metadata/cve_enrichment_sync_worker.rb'
|
||||
- 'ee/app/workers/package_metadata/global_advisory_scan_worker.rb'
|
||||
- 'ee/app/workers/package_metadata/licenses_sync_worker.rb'
|
||||
- 'ee/app/workers/projects/deregister_suggested_reviewers_project_worker.rb'
|
||||
- 'ee/app/workers/projects/disable_legacy_open_source_license_for_inactive_projects_worker.rb'
|
||||
|
|
|
|||
|
|
@ -182,7 +182,6 @@ Style/FormatString:
|
|||
- 'spec/controllers/graphql_controller_spec.rb'
|
||||
- 'spec/factories/lfs_objects.rb'
|
||||
- 'spec/features/admin/admin_users_spec.rb'
|
||||
- 'spec/features/groups/import_export/connect_instance_spec.rb'
|
||||
- 'spec/features/issues/new/form_spec.rb'
|
||||
- 'spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb'
|
||||
- 'spec/helpers/profiles_helper_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1014,7 +1014,6 @@ Style/InlineDisableAnnotation:
|
|||
- 'ee/app/finders/ee/fork_targets_finder.rb'
|
||||
- 'ee/app/finders/ee/issuables/label_filter.rb'
|
||||
- 'ee/app/finders/ee/issues_finder.rb'
|
||||
- 'ee/app/finders/ee/merge_requests_finder.rb'
|
||||
- 'ee/app/finders/ee/notes_finder.rb'
|
||||
- 'ee/app/finders/epics/with_issues_finder.rb'
|
||||
- 'ee/app/finders/geo/framework_registry_finder.rb'
|
||||
|
|
@ -1219,7 +1218,6 @@ Style/InlineDisableAnnotation:
|
|||
- 'ee/app/services/ee/groups/autocomplete_service.rb'
|
||||
- 'ee/app/services/ee/groups/destroy_service.rb'
|
||||
- 'ee/app/services/ee/groups/update_service.rb'
|
||||
- 'ee/app/services/ee/keys/create_service.rb'
|
||||
- 'ee/app/services/ee/labels/promote_service.rb'
|
||||
- 'ee/app/services/ee/members/create_service.rb'
|
||||
- 'ee/app/services/ee/members/creator_service.rb'
|
||||
|
|
@ -1235,7 +1233,6 @@ Style/InlineDisableAnnotation:
|
|||
- 'ee/app/services/ee/quick_actions/target_service.rb'
|
||||
- 'ee/app/services/ee/resource_events/synthetic_iteration_notes_builder_service.rb'
|
||||
- 'ee/app/services/ee/resource_events/synthetic_weight_notes_builder_service.rb'
|
||||
- 'ee/app/services/ee/search_service.rb'
|
||||
- 'ee/app/services/ee/system_note_service.rb'
|
||||
- 'ee/app/services/ee/users/build_service.rb'
|
||||
- 'ee/app/services/ee/users/destroy_service.rb'
|
||||
|
|
@ -1462,7 +1459,6 @@ Style/InlineDisableAnnotation:
|
|||
- 'ee/lib/gitlab/contribution_analytics/postgresql_data_collector.rb'
|
||||
- 'ee/lib/gitlab/cube_js/data_transformer.rb'
|
||||
- 'ee/lib/gitlab/elastic/elasticsearch_enabled_cache.rb'
|
||||
- 'ee/lib/gitlab/elastic/group_search_results.rb'
|
||||
- 'ee/lib/gitlab/elastic/helper.rb'
|
||||
- 'ee/lib/gitlab/elastic/search_results.rb'
|
||||
- 'ee/lib/gitlab/geo/event_gap_tracking.rb'
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ Style/RedundantReturn:
|
|||
- 'ee/app/controllers/groups/analytics/cycle_analytics/stages_controller.rb'
|
||||
- 'ee/app/controllers/groups/analytics/cycle_analytics/summary_controller.rb'
|
||||
- 'ee/app/controllers/groups/analytics/tasks_by_type_controller.rb'
|
||||
- 'ee/app/controllers/groups/epics_controller.rb'
|
||||
- 'ee/app/controllers/projects/integrations/jira/issues_controller.rb'
|
||||
- 'ee/app/controllers/projects/integrations/zentao/issues_controller.rb'
|
||||
- 'ee/app/controllers/projects/on_demand_scans_controller.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3d9b3113d69fbe1c51b3a2b8eb95b08601798801
|
||||
e800fc20f2954c64f42f560e7255db5bf189010b
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="gl-contents">
|
||||
<dt class="gl-mb-5 gl-mr-6 gl-max-w-26" data-testid="label-slot">
|
||||
<dt class="gl-max-w-26" data-testid="label-slot">
|
||||
<template v-if="label || $scopedSlots.label">
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</template>
|
||||
</dt>
|
||||
<dd class="gl-mb-5" data-testid="value-slot">
|
||||
<dd class="md:gl-mb-0" data-testid="value-slot">
|
||||
<template v-if="value || $scopedSlots.value">
|
||||
<slot name="value">{{ value }}</slot>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import RunnerGroups from './runner_groups.vue';
|
|||
import RunnerProjects from './runner_projects.vue';
|
||||
import RunnerTags from './runner_tags.vue';
|
||||
import RunnerManagers from './runner_managers.vue';
|
||||
import RunnerJobs from './runner_jobs.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -30,14 +31,24 @@ export default {
|
|||
RunnerProjects,
|
||||
RunnerTags,
|
||||
RunnerManagers,
|
||||
RunnerJobs,
|
||||
TimeAgo,
|
||||
},
|
||||
props: {
|
||||
runnerId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
runner: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
showAccessHelp: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
maximumTimeout() {
|
||||
|
|
@ -87,8 +98,10 @@ export default {
|
|||
|
||||
<template>
|
||||
<div v-if="runner">
|
||||
<div class="gl-pt-4">
|
||||
<dl class="gl-mb-0 gl-grid gl-grid-cols-[auto_1fr]">
|
||||
<div class="md:gl-columns-2">
|
||||
<dl
|
||||
class="gl-mb-0 gl-flex gl-flex-col gl-gap-x-5 gl-gap-y-1 md:gl-grid md:gl-grid-cols-[auto_1fr] md:gl-gap-y-3"
|
||||
>
|
||||
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />
|
||||
<runner-detail
|
||||
:label="s__('Runners|Last contact')"
|
||||
|
|
@ -143,10 +156,11 @@ export default {
|
|||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="gl-mt-3 gl-flex gl-flex-col gl-gap-5">
|
||||
<runner-managers :runner="runner" />
|
||||
<div class="gl-mt-6 gl-flex gl-flex-col gl-gap-5">
|
||||
<runner-groups v-if="isGroupRunner" :runner="runner" />
|
||||
<runner-projects v-if="isProjectRunner" :runner="runner" />
|
||||
<runner-managers :runner="runner" />
|
||||
<runner-jobs :runner-id="runnerId" :show-access-help="showAccessHelp" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import PageHeading from '~/vue_shared/components/page_heading.vue';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants';
|
||||
import { formatRunnerName } from '../utils';
|
||||
import RunnerCreatedAt from './runner_created_at.vue';
|
||||
|
|
@ -28,7 +29,9 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
name() {
|
||||
return formatRunnerName(this.runner);
|
||||
return sprintf(s__('Runners|Runner %{name}'), {
|
||||
name: formatRunnerName(this.runner),
|
||||
});
|
||||
},
|
||||
},
|
||||
I18N_LOCKED_RUNNER_DESCRIPTION,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ export default {
|
|||
icon="pipeline"
|
||||
:count="jobs.count"
|
||||
:is-loading="loading"
|
||||
class="gl-mt-5"
|
||||
>
|
||||
<template v-if="showAccessHelp" #count>
|
||||
<help-popover>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default {
|
|||
<template>
|
||||
<gl-empty-state
|
||||
:svg-path="$options.EMPTY_STATE_SVG_URL"
|
||||
:svg-height="150"
|
||||
:svg-height="96"
|
||||
:title="$options.i18n.title"
|
||||
>
|
||||
<template #description>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<script>
|
||||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-pipeline-md.svg?url';
|
||||
|
||||
import { GlLink, GlSprintf, GlEmptyState } from '@gitlab/ui';
|
||||
import { s__, formatNumber } from '~/locale';
|
||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||
import HelpPopover from '~/vue_shared/components/help_popover.vue';
|
||||
import { tableField } from '../utils';
|
||||
import { RUNNER_MANAGERS_HELP_URL } from '../constants';
|
||||
import RunnerManagersTable from './runner_managers_table.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -11,6 +14,7 @@ export default {
|
|||
components: {
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
GlEmptyState,
|
||||
RunnerManagersTable,
|
||||
CrudComponent,
|
||||
HelpPopover,
|
||||
|
|
@ -44,12 +48,13 @@ export default {
|
|||
thClasses: ['gl-text-right'],
|
||||
}),
|
||||
],
|
||||
RUNNER_MANAGERS_HELP_URL,
|
||||
EMPTY_STATE_SVG_URL,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<crud-component
|
||||
v-if="count > 0"
|
||||
:title="s__('Runners|Runners')"
|
||||
icon="container-image"
|
||||
:count="formattedCount"
|
||||
|
|
@ -78,6 +83,22 @@ export default {
|
|||
</help-popover>
|
||||
</template>
|
||||
|
||||
<runner-managers-table :items="items" />
|
||||
<runner-managers-table v-if="count > 0" :items="items" />
|
||||
<gl-empty-state
|
||||
v-else
|
||||
:svg-path="$options.EMPTY_STATE_SVG_URL"
|
||||
:svg-height="96"
|
||||
:title="s__('Runners|No runners managers found')"
|
||||
>
|
||||
<template #description>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'Runners|Runner managers registered under this configuration are listed here. Register and start at least one runner manager.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</template>
|
||||
</gl-empty-state>
|
||||
</crud-component>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_lo
|
|||
import RunnerHeader from './runner_header.vue';
|
||||
import RunnerHeaderActions from './runner_header_actions.vue';
|
||||
import RunnerDetails from './runner_details.vue';
|
||||
import RunnerJobs from './runner_jobs.vue';
|
||||
|
||||
export default {
|
||||
name: 'RunnerShow',
|
||||
|
|
@ -21,7 +20,6 @@ export default {
|
|||
RunnerHeader,
|
||||
RunnerHeaderActions,
|
||||
RunnerDetails,
|
||||
RunnerJobs,
|
||||
},
|
||||
props: {
|
||||
runnerId: {
|
||||
|
|
@ -83,7 +81,6 @@ export default {
|
|||
</template>
|
||||
</runner-header>
|
||||
|
||||
<runner-details :runner="runner" />
|
||||
<runner-jobs :runner-id="runnerId" :show-access-help="showAccessHelp" />
|
||||
<runner-details :runner-id="runnerId" :runner="runner" :show-access-help="showAccessHelp" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,13 @@
|
|||
<script>
|
||||
import { GlCollapsibleListbox, GlBadge, GlPopover } from '@gitlab/ui';
|
||||
import { GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import { ACCESS_LEVEL_PLANNER_STRING } from '~/access_level/constants';
|
||||
|
||||
export default {
|
||||
components: { GlCollapsibleListbox, GlBadge, GlPopover },
|
||||
components: { GlCollapsibleListbox },
|
||||
inject: {
|
||||
manageMemberRolesPath: { default: null },
|
||||
},
|
||||
i18n: {
|
||||
plannerRoleDescription: s__(
|
||||
'MemberRole|The Planner role is a hybrid of the existing Guest and Reporter roles but designed for users who need access to planning workflows.',
|
||||
),
|
||||
},
|
||||
plannerRole: ACCESS_LEVEL_PLANNER_STRING,
|
||||
badgeId: 'planner-role-badge',
|
||||
props: {
|
||||
roles: {
|
||||
type: Object,
|
||||
|
|
@ -72,14 +64,6 @@ export default {
|
|||
data-testid="role-data"
|
||||
>
|
||||
<span data-testid="role-name">{{ item.text }}</span>
|
||||
<template v-if="$options.plannerRole === item.value">
|
||||
<gl-badge :id="$options.badgeId" variant="info" class="gl-float-right gl-ml-2">
|
||||
{{ __('New') }}
|
||||
</gl-badge>
|
||||
<gl-popover :target="$options.badgeId">
|
||||
{{ $options.i18n.plannerRoleDescription }}
|
||||
</gl-popover>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.memberRoleId"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { groupMemberRequestFormatter } from '~/groups/members/utils';
|
||||
import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger';
|
||||
import initInviteGroupsModal from '~/invite_members/init_invite_groups_modal';
|
||||
import { initPlannerRoleBanner } from '~/planner_role_banner';
|
||||
import { s__ } from '~/locale';
|
||||
import { initMembersApp } from '~/members';
|
||||
import { CONTEXT_TYPE, GROUPS_APP_OPTIONS, MEMBERS_TAB_TYPES } from 'ee_else_ce/members/constants';
|
||||
|
|
@ -60,7 +59,6 @@ const APP_OPTIONS = {
|
|||
...GROUPS_APP_OPTIONS,
|
||||
};
|
||||
|
||||
initPlannerRoleBanner();
|
||||
initMembersApp(
|
||||
document.querySelector('.js-group-members-list-app'),
|
||||
CONTEXT_TYPE.GROUP,
|
||||
|
|
|
|||
|
|
@ -7,13 +7,11 @@ import initImportProjectMembersTrigger from '~/invite_members/init_import_projec
|
|||
import initImportProjectMembersModal from '~/invite_members/init_import_project_members_modal';
|
||||
import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger';
|
||||
import initInviteGroupsModal from '~/invite_members/init_invite_groups_modal';
|
||||
import { initPlannerRoleBanner } from '~/planner_role_banner';
|
||||
import { s__ } from '~/locale';
|
||||
import { initMembersApp } from '~/members';
|
||||
import { groupLinkRequestFormatter } from '~/members/utils';
|
||||
import { projectMemberRequestFormatter } from '~/projects/members/utils';
|
||||
|
||||
initPlannerRoleBanner();
|
||||
initImportProjectMembersModal();
|
||||
initInviteGroupsModal();
|
||||
initInviteGroupTrigger();
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
<script>
|
||||
import { GlBanner, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import ADD_USER_SVG_URL from '@gitlab/svgs/dist/illustrations/add-user-sm.svg';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { s__ } from '~/locale';
|
||||
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
|
||||
|
||||
export default {
|
||||
name: 'PlannerRoleBanner',
|
||||
i18n: {
|
||||
title: s__('MemberRole|New Planner role'),
|
||||
buttonText: s__('MemberRole|Learn more about roles and permissions'), // This is hidden but it is required prop
|
||||
description:
|
||||
s__(`MemberRole|The Planner role is a hybrid of the existing Guest and Reporter roles but designed for users who need access to planning workflows. For more information about the new role, see %{blogLinkStart}our blog%{blogLinkEnd} or %{learnMoreStart}learn more about roles and permissions%{learnMoreEnd}.
|
||||
`),
|
||||
dismissLabel: s__('MemberRole|Dismiss Planner role promotion'),
|
||||
},
|
||||
blogURL:
|
||||
'https://about.gitlab.com/blog/2024/11/25/introducing-gitlabs-new-planner-role-for-agile-planning-teams/',
|
||||
docsUrl: helpPagePath('user/permissions'),
|
||||
ADD_USER_SVG_URL,
|
||||
buttonAttributes: {
|
||||
class: 'planner-role-banner-button',
|
||||
},
|
||||
components: {
|
||||
GlBanner,
|
||||
UserCalloutDismisser,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<user-callout-dismisser feature-name="planner_role_callout">
|
||||
<template #default="{ dismiss, shouldShowCallout }">
|
||||
<div v-if="shouldShowCallout" class="gl-pt-5">
|
||||
<gl-banner
|
||||
:title="$options.i18n.title"
|
||||
:button-text="$options.i18n.buttonText"
|
||||
:button-link="$options.docsUrl"
|
||||
:svg-path="$options.ADD_USER_SVG_URL"
|
||||
:button-attributes="$options.buttonAttributes"
|
||||
:dismiss-label="$options.i18n.dismissLabel"
|
||||
data-testid="planner-role-banner"
|
||||
variant="promotion"
|
||||
@close="dismiss"
|
||||
>
|
||||
<span>
|
||||
<gl-sprintf :message="$options.i18n.description">
|
||||
<template #blogLink="{ content }">
|
||||
<gl-link :href="$options.blogURL" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
<template #learnMore="{ content }">
|
||||
<gl-link :href="$options.docsUrl">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</gl-banner>
|
||||
</div>
|
||||
</template>
|
||||
</user-callout-dismisser>
|
||||
</template>
|
||||
<!-- As per the requirement, the link button is not required and banner is not support hiding the link button -->
|
||||
<style>
|
||||
.planner-role-banner-button {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import PlannerRoleBanner from './components/planner_role_banner.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
const EXPIRY_DATE = '2025-06-14';
|
||||
|
||||
export const initPlannerRoleBanner = () => {
|
||||
const expiryDate = new Date(EXPIRY_DATE);
|
||||
if (Date.now() > expiryDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const el = document.getElementById('js-planner-role-banner');
|
||||
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'PlannerRoleBannerRoot',
|
||||
apolloProvider,
|
||||
render(h) {
|
||||
return h(PlannerRoleBanner);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -11,10 +11,26 @@ module Mutations
|
|||
|
||||
description 'Allows updating several properties for a set of work items. '
|
||||
|
||||
argument :assignees_widget,
|
||||
::Types::WorkItems::Widgets::AssigneesInputType,
|
||||
required: false,
|
||||
description: 'Input for assignees widget.',
|
||||
experiment: { milestone: '18.2' }
|
||||
argument :confidential,
|
||||
GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: 'Sets the work item confidentiality.',
|
||||
experiment: { milestone: '18.2' }
|
||||
argument :ids, [::Types::GlobalIDType[::WorkItem]],
|
||||
required: true,
|
||||
description: 'Global ID array of the issues that will be updated. ' \
|
||||
"IDs that the user can\'t update will be ignored. A max of #{MAX_WORK_ITEMS} can be provided."
|
||||
argument :milestone_widget,
|
||||
::Types::WorkItems::Widgets::MilestoneInputType,
|
||||
required: false,
|
||||
description: 'Input for milestone widget.',
|
||||
experiment: { milestone: '18.2' }
|
||||
|
||||
argument :parent_id, ::Types::GlobalIDType[::WorkItems::Parent],
|
||||
required: true,
|
||||
description: 'Global ID of the parent to which the bulk update will be scoped. ' \
|
||||
|
|
@ -24,8 +40,7 @@ module Mutations
|
|||
argument :labels_widget,
|
||||
::Types::WorkItems::Widgets::LabelsUpdateInputType,
|
||||
required: false,
|
||||
description: 'Input for labels widget.',
|
||||
prepare: ->(input, _) { input.to_h }
|
||||
description: 'Input for labels widget.'
|
||||
|
||||
field :updated_work_item_count, GraphQL::Types::Int,
|
||||
null: true,
|
||||
|
|
@ -50,7 +65,7 @@ module Mutations
|
|||
parent: parent,
|
||||
current_user: current_user,
|
||||
work_item_ids: ids.map(&:model_id),
|
||||
widget_params: attributes
|
||||
attributes: attributes
|
||||
).execute
|
||||
|
||||
if result.success?
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ module Users
|
|||
openssl_callout: 94,
|
||||
# 95 removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170868
|
||||
new_mr_dashboard_banner: 96,
|
||||
planner_role_callout: 97,
|
||||
# 97 removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/196130
|
||||
# EE-only
|
||||
pipl_compliance_alert: 98,
|
||||
new_merge_request_dashboard_welcome: 99,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
module WorkItems
|
||||
class BulkUpdateService
|
||||
def initialize(parent:, current_user:, work_item_ids:, widget_params: {})
|
||||
def initialize(parent:, current_user:, work_item_ids:, attributes: {})
|
||||
@parent = parent
|
||||
@work_item_ids = work_item_ids
|
||||
@current_user = current_user
|
||||
@widget_params = widget_params.dup
|
||||
@attributes = attributes
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
@ -14,19 +14,30 @@ module WorkItems
|
|||
return ServiceResponse.error(message: "User can't read parent", reason: :authorization)
|
||||
end
|
||||
|
||||
updated_work_items = scoped_work_items.find_each(batch_size: 100) # rubocop:disable CodeReuse/ActiveRecord -- Implementation would be identical in model
|
||||
.filter_map do |work_item|
|
||||
next unless @current_user.can?(:update_work_item, work_item)
|
||||
non_widget_attributes = @attributes.except(*all_widget_keys)
|
||||
|
||||
update_result = WorkItems::UpdateService.new(
|
||||
container: work_item.resource_parent,
|
||||
widget_params: @widget_params,
|
||||
current_user: @current_user
|
||||
).execute(work_item)
|
||||
updated_work_items = scoped_work_items
|
||||
.find_each(batch_size: 100) # rubocop:disable CodeReuse/ActiveRecord -- Implementation would be identical in model
|
||||
.filter_map do |work_item|
|
||||
next unless @current_user.can?(:update_work_item, work_item)
|
||||
|
||||
work_item if update_result[:status] == :success
|
||||
end
|
||||
widget_params = extract_supported_widget_params(
|
||||
work_item.work_item_type,
|
||||
@attributes,
|
||||
work_item.resource_parent
|
||||
)
|
||||
# Skip if no applicable widgets for this work item type
|
||||
next if widget_params.blank? && non_widget_attributes.blank?
|
||||
|
||||
update_result = WorkItems::UpdateService.new(
|
||||
container: work_item.resource_parent,
|
||||
widget_params: widget_params,
|
||||
params: non_widget_attributes,
|
||||
current_user: @current_user
|
||||
).execute(work_item)
|
||||
|
||||
work_item if update_result[:status] == :success
|
||||
end
|
||||
ServiceResponse.success(payload: { updated_work_item_count: updated_work_items.count })
|
||||
end
|
||||
|
||||
|
|
@ -36,7 +47,10 @@ module WorkItems
|
|||
ids = WorkItem.id_in(@work_item_ids)
|
||||
cte = Gitlab::SQL::CTE.new(:work_item_ids_cte, ids)
|
||||
work_item_scope = WorkItem.all
|
||||
cte.apply_to(work_item_scope).in_namespaces_with_cte(namespaces)
|
||||
cte
|
||||
.apply_to(work_item_scope)
|
||||
.in_namespaces_with_cte(namespaces)
|
||||
.includes(:work_item_type) # rubocop:disable CodeReuse/ActiveRecord -- Implementation would be identical in model
|
||||
end
|
||||
|
||||
def namespaces
|
||||
|
|
@ -58,5 +72,22 @@ module WorkItems
|
|||
Project.in_namespace(@parent.self_and_descendant_ids)
|
||||
end.select('projects.project_namespace_id as id')
|
||||
end
|
||||
|
||||
def all_widget_keys
|
||||
@all_widget_keys ||= ::WorkItems::WidgetDefinition.available_widgets.map(&:api_symbol)
|
||||
end
|
||||
|
||||
def extract_supported_widget_params(work_item_type, attributes, resource_parent)
|
||||
supported_widget_keys = work_item_type.widget_classes(resource_parent).map(&:api_symbol)
|
||||
keys_to_extract = all_widget_keys & attributes.keys & supported_widget_keys
|
||||
|
||||
return {} if keys_to_extract.empty?
|
||||
|
||||
widget_params = attributes.slice(*keys_to_extract)
|
||||
|
||||
widget_params.transform_values do |input|
|
||||
input.is_a?(Array) ? input.map(&:to_h) : input.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
- content_for :hide_invite_members_button, true
|
||||
|
||||
- if can_admin_group_member?(@group)
|
||||
= render_if_exists 'shared/promotions/promote_planner_role'
|
||||
= render_if_exists 'groups/group_members/link_to_pending_members'
|
||||
= render ::Layouts::PageHeadingComponent.new(_('Group members')) do |c|
|
||||
- c.with_description do
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
- c.with_description do
|
||||
= s_('Preferences|Customize the behavior of the system layout and default views.')
|
||||
= succeed '.' do
|
||||
= link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'behavior'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'behavior'), target: '_blank', rel: 'noopener noreferrer', class: 'gl-link-inline'
|
||||
- c.with_body do
|
||||
.form-group
|
||||
= f.label :keyboard_shortcuts_enabled, class: 'label-bold' do
|
||||
|
|
@ -147,11 +147,11 @@
|
|||
- c.with_description do
|
||||
= _('Customize language and region related settings.')
|
||||
= succeed '.' do
|
||||
= link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'localization'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'localization'), target: '_blank', rel: 'noopener noreferrer', class: 'gl-link-inline'
|
||||
- c.with_body do
|
||||
.js-listbox-input{ data: { label: _('Language'), description: s_('Preferences|This feature is experimental and translations are not yet complete.'), name: 'user[preferred_language]', items: language_choices.to_json, value: current_user.preferred_language, block: true.to_s, toggle_class: 'gl-form-input-xl' } }
|
||||
%p.-gl-mt-5
|
||||
= link_to help_page_url('development/i18n/translation.md'), class: 'text-nowrap', target: '_blank', rel: 'noopener noreferrer' do
|
||||
= link_to help_page_url('development/i18n/translation.md'), class: 'text-nowrap gl-link-inline', target: '_blank', rel: 'noopener noreferrer' do
|
||||
= _("Help translate GitLab into your language")
|
||||
%span{ aria: { label: _('Open new window') } }
|
||||
= sprite_icon('external-link')
|
||||
|
|
@ -166,7 +166,7 @@
|
|||
- c.with_description do
|
||||
= s_('Preferences|Configure how dates and times display for you.')
|
||||
= succeed '.' do
|
||||
= link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'show-exact-times-instead-of-relative-times'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Learn more'), help_page_path('user/profile/preferences.md', anchor: 'show-exact-times-instead-of-relative-times'), target: '_blank', rel: 'noopener noreferrer', class: 'gl-link-inline'
|
||||
- c.with_body do
|
||||
.form-group
|
||||
= f.gitlab_ui_checkbox_component :time_display_relative,
|
||||
|
|
@ -185,7 +185,7 @@
|
|||
- c.with_description do
|
||||
= s_('Preferences|Turns on or off the ability to follow or be followed by other users.')
|
||||
= succeed '.' do
|
||||
= link_to _('Learn more'), help_page_path('user/profile/_index.md', anchor: 'follow-users'), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to _('Learn more'), help_page_path('user/profile/_index.md', anchor: 'follow-users'), target: '_blank', rel: 'noopener noreferrer', class: 'gl-link-inline'
|
||||
- c.with_body do
|
||||
.form-group
|
||||
= f.gitlab_ui_checkbox_component :enabled_following,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
- content_for :reload_on_member_invite_success, true
|
||||
- content_for :hide_invite_members_button, true
|
||||
|
||||
= render_if_exists 'shared/promotions/promote_planner_role'
|
||||
- if show_the_header
|
||||
= render ::Layouts::PageHeadingComponent.new(_("Project members")) do |c|
|
||||
- c.with_description do
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
#js-planner-role-banner
|
||||
|
|
@ -12,8 +12,10 @@ class MergeRequests::DeleteSourceBranchWorker
|
|||
idempotent!
|
||||
|
||||
def perform(merge_request_id, source_branch_sha, user_id)
|
||||
merge_request = MergeRequest.find(merge_request_id)
|
||||
user = User.find(user_id)
|
||||
merge_request = MergeRequest.find_by_id(merge_request_id)
|
||||
user = User.find_by_id(user_id)
|
||||
|
||||
return unless merge_request && user
|
||||
|
||||
# Source branch changed while it's being removed
|
||||
return if merge_request.source_branch_sha != source_branch_sha
|
||||
|
|
@ -22,6 +24,5 @@ class MergeRequests::DeleteSourceBranchWorker
|
|||
.execute(merge_request)
|
||||
|
||||
::Projects::DeleteBranchWorker.new.perform(merge_request.source_project.id, user_id, merge_request.source_branch)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ class MergeRequests::ResolveTodosWorker
|
|||
idempotent!
|
||||
|
||||
def perform(merge_request_id, user_id)
|
||||
merge_request = MergeRequest.find(merge_request_id)
|
||||
user = User.find(user_id)
|
||||
merge_request = MergeRequest.find_by_id(merge_request_id)
|
||||
user = User.find_by_id(user_id)
|
||||
|
||||
return unless merge_request && user
|
||||
|
||||
MergeRequests::ResolveTodosService.new(merge_request, user).execute
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ description: >
|
|||
This is the second attempt to initialize the values that were missed in a time window just after
|
||||
BackfillOrganizationIdOnCiRunnerMachines was started.
|
||||
feature_category: fleet_visibility
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195033
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195123
|
||||
milestone: '18.2'
|
||||
queued_migration_version: 20250620094653
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
|
|||
|
|
@ -8,14 +8,6 @@ description: Helm package file metadata
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57017
|
||||
milestone: '13.12'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: package_file_id
|
||||
table: packages_package_files
|
||||
sharding_key: project_id
|
||||
belongs_to: package_file
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
table_size: small
|
||||
desired_sharding_key_migration_job_name: BackfillPackagesHelmFileMetadataProjectId
|
||||
|
|
|
|||
|
|
@ -8,14 +8,6 @@ description: Join table between nuget target frameworks and packages_dependency_
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30618
|
||||
milestone: '13.0'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: dependency_link_id
|
||||
table: packages_dependency_links
|
||||
sharding_key: project_id
|
||||
belongs_to: dependency_link
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
table_size: small
|
||||
desired_sharding_key_migration_job_name: BackfillPackagesNugetDependencyLinkMetadataProjectId
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ class CreateAiCatalogItems < Gitlab::Database::Migration[2.3]
|
|||
milestone '18.2'
|
||||
|
||||
def change
|
||||
create_table :ai_catalog_items do |t| # rubocop:disable Migration/EnsureFactoryForTable -- False Positive, does exist in ee/spec/factories/ai/catalog/item.rb
|
||||
create_table :ai_catalog_items do |t|
|
||||
t.bigint :organization_id, index: true, null: false
|
||||
t.bigint :project_id, index: true
|
||||
t.timestamps_with_timezone null: false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPackagesHelmFileMetadataProjectIdNotNull < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.2'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_not_null_constraint :packages_helm_file_metadata, :project_id
|
||||
end
|
||||
|
||||
def down
|
||||
remove_not_null_constraint :packages_helm_file_metadata, :project_id
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPackagesNugetDependencyLinkMetadataProjectIdNotNull < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.2'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_not_null_constraint :packages_nuget_dependency_link_metadata, :project_id
|
||||
end
|
||||
|
||||
def down
|
||||
remove_not_null_constraint :packages_nuget_dependency_link_metadata, :project_id
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTextLimitToOauthApplicationsScopes < Gitlab::Database::Migration[2.3]
|
||||
disable_ddl_transaction!
|
||||
milestone '18.2'
|
||||
|
||||
def up
|
||||
# Add constraint without validation to ensure consistency while maintaining compatibility
|
||||
# Validation will be enabled in a follow-up migration in 18.3
|
||||
add_text_limit :oauth_applications, :scopes, 2048, validate: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_text_limit :oauth_applications, :scopes
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
01a17dc8570de3cda39ec70f85d8631d78d4532244b9f39600896c24ac6cb912
|
||||
|
|
@ -0,0 +1 @@
|
|||
31a2ae1ff707859c660ce4e011ea40b06a2d64c922343e388f904b9c5d6f01f0
|
||||
|
|
@ -0,0 +1 @@
|
|||
dc0151b8d870ed1be417504681f73f1ec6234c94890ca3e0fe584de175f01d7b
|
||||
|
|
@ -19795,7 +19795,8 @@ CREATE TABLE packages_helm_file_metadata (
|
|||
channel text NOT NULL,
|
||||
metadata jsonb,
|
||||
project_id bigint,
|
||||
CONSTRAINT check_06e8d100af CHECK ((char_length(channel) <= 255))
|
||||
CONSTRAINT check_06e8d100af CHECK ((char_length(channel) <= 255)),
|
||||
CONSTRAINT check_109d878e47 CHECK ((project_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE TABLE packages_maven_metadata (
|
||||
|
|
@ -19858,6 +19859,7 @@ CREATE TABLE packages_nuget_dependency_link_metadata (
|
|||
dependency_link_id bigint NOT NULL,
|
||||
target_framework text NOT NULL,
|
||||
project_id bigint,
|
||||
CONSTRAINT check_1c3e07cfff CHECK ((project_id IS NOT NULL)),
|
||||
CONSTRAINT packages_nuget_dependency_link_metadata_target_framework_constr CHECK ((char_length(target_framework) <= 255))
|
||||
);
|
||||
|
||||
|
|
@ -29751,6 +29753,9 @@ ALTER TABLE ONLY instance_type_ci_runners
|
|||
ALTER TABLE ONLY project_type_ci_runners
|
||||
ADD CONSTRAINT check_619c71f3a2 UNIQUE (id);
|
||||
|
||||
ALTER TABLE oauth_applications
|
||||
ADD CONSTRAINT check_75750847b8 CHECK ((char_length(scopes) <= 2048)) NOT VALID;
|
||||
|
||||
ALTER TABLE ONLY group_type_ci_runners
|
||||
ADD CONSTRAINT check_81b90172a6 UNIQUE (id);
|
||||
|
||||
|
|
|
|||
|
|
@ -12953,9 +12953,12 @@ Input type: `WorkItemBulkUpdateInput`
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationworkitembulkupdateassigneeswidget"></a>`assigneesWidget` {{< icon name="warning-solid" >}} | [`WorkItemWidgetAssigneesInput`](#workitemwidgetassigneesinput) | **Deprecated**: **Status**: Experiment. Introduced in GitLab 18.2. |
|
||||
| <a id="mutationworkitembulkupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationworkitembulkupdateconfidential"></a>`confidential` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Deprecated**: **Status**: Experiment. Introduced in GitLab 18.2. |
|
||||
| <a id="mutationworkitembulkupdateids"></a>`ids` | [`[WorkItemID!]!`](#workitemid) | Global ID array of the issues that will be updated. IDs that the user can't update will be ignored. A max of 100 can be provided. |
|
||||
| <a id="mutationworkitembulkupdatelabelswidget"></a>`labelsWidget` | [`WorkItemWidgetLabelsUpdateInput`](#workitemwidgetlabelsupdateinput) | Input for labels widget. |
|
||||
| <a id="mutationworkitembulkupdatemilestonewidget"></a>`milestoneWidget` {{< icon name="warning-solid" >}} | [`WorkItemWidgetMilestoneInput`](#workitemwidgetmilestoneinput) | **Deprecated**: **Status**: Experiment. Introduced in GitLab 18.2. |
|
||||
| <a id="mutationworkitembulkupdateparentid"></a>`parentId` | [`WorkItemsParentID!`](#workitemsparentid) | Global ID of the parent to which the bulk update will be scoped. The parent can be a project. The parent can also be a group (Premium and Ultimate only). Example `WorkItemsParentID` are `"gid://gitlab/Project/1"` and `"gid://gitlab/Group/1"`. |
|
||||
|
||||
#### Fields
|
||||
|
|
@ -47294,7 +47297,6 @@ Name of the feature that the callout is for.
|
|||
| <a id="usercalloutfeaturenameenumpipeline_new_inputs_adoption_banner"></a>`PIPELINE_NEW_INPUTS_ADOPTION_BANNER` | Callout feature name for pipeline_new_inputs_adoption_banner. |
|
||||
| <a id="usercalloutfeaturenameenumpipeline_schedules_inputs_adoption_banner"></a>`PIPELINE_SCHEDULES_INPUTS_ADOPTION_BANNER` | Callout feature name for pipeline_schedules_inputs_adoption_banner. |
|
||||
| <a id="usercalloutfeaturenameenumpipl_compliance_alert"></a>`PIPL_COMPLIANCE_ALERT` | Callout feature name for pipl_compliance_alert. |
|
||||
| <a id="usercalloutfeaturenameenumplanner_role_callout"></a>`PLANNER_ROLE_CALLOUT` | Callout feature name for planner_role_callout. |
|
||||
| <a id="usercalloutfeaturenameenumpreview_user_over_limit_free_plan_alert"></a>`PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT` | Callout feature name for preview_user_over_limit_free_plan_alert. |
|
||||
| <a id="usercalloutfeaturenameenumproduct_analytics_dashboard_feedback"></a>`PRODUCT_ANALYTICS_DASHBOARD_FEEDBACK` | Callout feature name for product_analytics_dashboard_feedback. |
|
||||
| <a id="usercalloutfeaturenameenumproduct_usage_data_collection_changes"></a>`PRODUCT_USAGE_DATA_COLLECTION_CHANGES` | Callout feature name for product_usage_data_collection_changes. |
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ title: Repository submodules API
|
|||
|
||||
{{< /details >}}
|
||||
|
||||
Use this API to update a
|
||||
[Git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) reference in a specific
|
||||
branch of your Git repository.
|
||||
|
||||
## Update existing submodule reference in repository
|
||||
|
||||
In some workflows, especially automated ones, you can update a
|
||||
submodule's reference to keep up to date other projects that use it.
|
||||
This endpoint allows you to update a [Git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) reference in a
|
||||
specific branch.
|
||||
|
||||
```plaintext
|
||||
PUT /projects/:id/repository/submodules/:submodule
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ description: Settings and commands in the GitLab Workflow extension for VS Code.
|
|||
title: GitLab Workflow extension settings and commands
|
||||
---
|
||||
|
||||
The GitLab Workflow extension for VS Code integrates with the VS Code Command Palette, extends existing
|
||||
VS Code integrations with Git, and provides configuration options.
|
||||
|
||||
## Command Palette commands
|
||||
|
||||
This extension provides several sets of commands that you can trigger in the
|
||||
|
|
@ -88,15 +91,15 @@ If you use self-signed certificates to connect to your GitLab instance, read the
|
|||
| Setting | Default | Information |
|
||||
| ------- | ------- | ----------- |
|
||||
| `gitlab.customQueries` | Not applicable | Defines the search queries that retrieves the items shown on the GitLab Panel. For more information, see [Custom Queries documentation](custom_queries.md). |
|
||||
| `gitlab.debug` | false | Set to `true` to enable debug mode. Debug mode improves error stack traces because the extension uses source maps to understand minified code. Debug mode also shows debug log messages in the [extension logs](troubleshooting.md#view-log-files). |
|
||||
| `gitlab.duo.enabledWithoutGitlabProject` | true | Set to `true` to keep GitLab Duo features enabled if the extension can't retrieve the project's `duoFeaturesEnabledForProject` setting. When `false`, all GitLab Duo features are disabled if the extension can't retrieve the project's `duoFeaturesEnabledForProject` setting. See [`duoFeaturesEnabledForProject` setting](#duofeaturesenabledforproject). |
|
||||
| `gitlab.debug` | false | When `true`, enables debug mode. Debug mode improves error stack traces because the extension uses source maps to understand minified code. Debug mode also shows debug log messages in the [extension logs](troubleshooting.md#view-log-files). |
|
||||
| `gitlab.duo.enabledWithoutGitlabProject` | true | When `true`, keeps GitLab Duo features enabled if the extension can't retrieve the project's `duoFeaturesEnabledForProject` setting. When `false`, disables all GitLab Duo features if the extension can't retrieve the project's `duoFeaturesEnabledForProject` setting. See [`duoFeaturesEnabledForProject` setting](#duofeaturesenabledforproject). |
|
||||
| `gitlab.duoCodeSuggestions.additionalLanguages` | Not applicable | (Experimental.) To expand the list of [officially supported languages](../../user/project/repository/code_suggestions/supported_extensions.md#supported-languages-by-ide) for Code Suggestions, provide an array of the [language identifiers](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers). Code suggestions quality for the added languages might not be optimal. |
|
||||
| `gitlab.duoCodeSuggestions.enabled` | true | Toggle to enable or disable AI-assisted code suggestions. |
|
||||
| `gitlab.duoCodeSuggestions.enabled` | true | When `true`, enables AI-assisted code suggestions. |
|
||||
| `gitlab.duoCodeSuggestions.enabledSupportedLanguages` | Not applicable | The [supported languages](../../user/project/repository/code_suggestions/supported_extensions.md#supported-languages-by-ide) for which to enable Code Suggestions. By default, all supported languages are enabled. |
|
||||
| `gitlab.duoCodeSuggestions.openTabsContext` | true | Toggle to enable or disable sending of context across open tabs to improve Code Suggestions. |
|
||||
| `gitlab.keybindingHints.enabled"` | true | Enable keybinding hints for GitLab Duo. |
|
||||
| `gitlab.pipelineGitRemoteName` | null | The name of the Git remote name corresponding to the GitLab repository with your pipelines. If set to `null` or missing, then the extension uses the same remote as for the non-pipeline features. |
|
||||
| `gitlab.showPipelineUpdateNotifications` | false | Set to `true` to show an alert when a pipeline completes. |
|
||||
| `gitlab.duoCodeSuggestions.openTabsContext` | true | When `true`, enables sending of context across open tabs to improve Code Suggestions. |
|
||||
| `gitlab.keybindingHints.enabled` | true | Enables keybinding hints for GitLab Duo. |
|
||||
| `gitlab.pipelineGitRemoteName` | null | The name of the Git remote name corresponding to the GitLab repository with your pipelines. When `null` or empty, then the extension uses the same remote as for the non-pipeline features. |
|
||||
| `gitlab.showPipelineUpdateNotifications` | false | When `true`, shows an alert when a pipeline completes. |
|
||||
|
||||
### `duoFeaturesEnabledForProject`
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ These variables tell the scanner how to authenticate with your application.
|
|||
| `DAST_AUTH_PASSWORD` | String | `P@55w0rd!` | The password to authenticate to in the website. |
|
||||
| `DAST_AUTH_PASSWORD_FIELD` | [selector](authentication.md#finding-an-elements-selector) | `name:password` | A selector describing the element used to enter the password on the login form. |
|
||||
| `DAST_AUTH_SUBMIT_FIELD` | [selector](authentication.md#finding-an-elements-selector) | `css:input[type=submit]` | A selector describing the element clicked on to submit the login form for a single-page login form, or the password form for a multi-page login form. |
|
||||
| `DAST_AUTH_SUCCESS_IF_AT_URL` | URL | `https://www.site.com/welcome` | A URL that is compared to the URL in the browser to determine if authentication has succeeded after the login form is submitted. |
|
||||
| `DAST_AUTH_SUCCESS_IF_AT_URL` | URL | `https://www.site.com/welcome*` | A URL that is compared to the URL in the browser to determine if authentication has succeeded after the login form is submitted. Wildcard `*` can be used to match a dynamic URL. |
|
||||
| `DAST_AUTH_SUCCESS_IF_ELEMENT_FOUND` | [selector](authentication.md#finding-an-elements-selector) | `css:.user-avatar` | A selector describing an element whose presence is used to determine if authentication has succeeded after the login form is submitted. |
|
||||
| `DAST_AUTH_SUCCESS_IF_NO_LOGIN_FORM` | boolean | `true` | Verifies successful authentication by checking for the absence of a login form after the login form has been submitted. This success check is enabled by default. |
|
||||
| `DAST_AUTH_TYPE` | string | `basic-digest` | The authentication type to use. |
|
||||
|
|
|
|||
|
|
@ -860,6 +860,35 @@ policy::container-security:
|
|||
- echo "CS_IMAGE:$CS_IMAGE"
|
||||
```
|
||||
|
||||
### Customize enforced jobs using `.gitlab-ci.yml` and artifacts
|
||||
|
||||
Because policy pipelines run in isolation, pipeline execution policies cannot read variables from `.gitlab-ci.yml` directly.
|
||||
If you want to use the variables in `.gitlab-ci.yml` instead of defining them in the project's CI/CD configuration,
|
||||
you can use artifacts to pass variables from the `.gitlab-ci.yml` configuration to the pipeline execution policy's pipeline.
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml
|
||||
|
||||
build-job:
|
||||
stage: build
|
||||
script:
|
||||
- echo "BUILD_VARIABLE=value_from_build_job" >> build.env
|
||||
artifacts:
|
||||
reports:
|
||||
dotenv: build.env
|
||||
```
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
|
||||
test-job:
|
||||
stage: test
|
||||
script:
|
||||
- echo "$BUILD_VARIABLE" # Prints "value_from_build_job"
|
||||
```
|
||||
|
||||
### Customize security scanner's behavior with `before_script` in project configurations
|
||||
|
||||
To customize the behavior of a security job enforced by a policy in the project's `.gitlab-ci.yml`, you can override `before_script`.
|
||||
|
|
@ -902,6 +931,55 @@ secret_detection:
|
|||
|
||||
By using `override_project_ci` and including the project's configuration, it allows for YAML configurations to be merged.
|
||||
|
||||
### Configure resource-specific variable control
|
||||
|
||||
You can allow teams to set global variables that can override pipeline execution policy variables, while still permitting job-specific overrides. This allows teams to set appropriate defaults for security scans, but use appropriate resources for other jobs.
|
||||
|
||||
Include in your `resource-optimized-scans.yml`:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
# Default resource settings for all jobs
|
||||
KUBERNETES_MEMORY_REQUEST: 4Gi
|
||||
KUBERNETES_MEMORY_LIMIT: 4Gi
|
||||
# Default values that teams can override via project variables
|
||||
SAST_KUBERNETES_MEMORY_REQUEST: 4Gi
|
||||
|
||||
sast:
|
||||
variables:
|
||||
SAST_EXCLUDED_ANALYZERS: 'spotbugs'
|
||||
KUBERNETES_MEMORY_REQUEST: $SAST_KUBERNETES_MEMORY_REQUEST
|
||||
KUBERNETES_MEMORY_LIMIT: $SAST_KUBERNETES_MEMORY_REQUEST
|
||||
```
|
||||
|
||||
Include in your `policy.yml`:
|
||||
|
||||
```yaml
|
||||
pipeline_execution_policy:
|
||||
- name: Resource-Optimized Security Policy
|
||||
description: Enforces security scans with efficient resource management
|
||||
enabled: true
|
||||
pipeline_config_strategy: inject_ci
|
||||
content:
|
||||
include:
|
||||
- project: security/policy-templates
|
||||
file: resource-optimized-scans.yml
|
||||
ref: main
|
||||
|
||||
variables_override:
|
||||
allowed: false
|
||||
exceptions:
|
||||
# Allow scan-specific resource overrides
|
||||
- SAST_KUBERNETES_MEMORY_REQUEST
|
||||
- SECRET_DETECTION_KUBERNETES_MEMORY_REQUEST
|
||||
- CS_KUBERNETES_MEMORY_REQUEST
|
||||
# Allow necessary scan customization
|
||||
- CS_IMAGE
|
||||
- SAST_EXCLUDED_PATHS
|
||||
```
|
||||
|
||||
This approach allows teams to set scan-specific resource variables (like `SAST_KUBERNETES_MEMORY_REQUEST`) using variable overrides without affecting all jobs in their pipeline, which provides better resource management for large projects. This example also shows the use of other common scan customization options that you can extend to developers. Make sure you document the available variables so your development teams can leverage them.
|
||||
|
||||
### Use group or project variables in a pipeline execution policy
|
||||
|
||||
You can use group or project variables in a pipeline execution policy.
|
||||
|
|
@ -993,3 +1071,24 @@ include:
|
|||
- 'Dockerfile'
|
||||
project: '$CI_PROJECT_PATH'
|
||||
```
|
||||
|
||||
To use this approach, the group or project must use the `override_project_ci` strategy.
|
||||
|
||||
### Enforce a container scanning `component` using a pipeline execution policy
|
||||
|
||||
You can use security scan components to improve the handling and enforcement of versioning.
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- component: gitlab.com/components/container-scanning/container-scanning@main
|
||||
inputs:
|
||||
cs_image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
|
||||
container_scanning: # override component with additional configuration
|
||||
variables:
|
||||
CS_REGISTRY_USER: $CI_REGISTRY_USER
|
||||
CS_REGISTRY_PASSWORD: $CI_REGISTRY_PASSWORD
|
||||
SECURE_LOG_LEVEL: debug # add for verbose debugging of the container scanner
|
||||
before_script:
|
||||
- echo $CS_IMAGE # optionally add a before_script for additional debugging
|
||||
```
|
||||
|
|
|
|||
|
|
@ -236,17 +236,10 @@ To show the sidebar again:
|
|||
{{< history >}}
|
||||
|
||||
- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4231) in GitLab 17.4 [with a flag](../../../administration/feature_flags/_index.md) named `work_items_beta`. Disabled by default. This feature is in [beta](../../../policy/development_stages_support.md#beta).
|
||||
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/551805) in GitLab 18.2.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
{{< alert type="flag" >}}
|
||||
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
An epic can be assigned to one or more users.
|
||||
|
||||
The assignees can be changed as often as needed.
|
||||
|
|
@ -255,10 +248,6 @@ The idea is that the assignees are people responsible for the epic.
|
|||
If a user is not a member of a group, an epic can only be assigned to them if another group member
|
||||
assigns them.
|
||||
|
||||
This feature is in [beta](../../../policy/development_stages_support.md).
|
||||
If you find a bug, use the
|
||||
[feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/463598) to provide more details.
|
||||
|
||||
### Change assignee on an epic
|
||||
|
||||
{{< history >}}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ Your `.gitlab-ci.yml` should look similar to this:
|
|||
|
||||
```yaml
|
||||
build:
|
||||
image: $CI_REGISTRY/group/project/docker:20.10.16
|
||||
image: $CI_REGISTRY/group/project/docker:24.0.5
|
||||
services:
|
||||
- name: $CI_REGISTRY/group/project/docker:20.10.16-dind
|
||||
- name: $CI_REGISTRY/group/project/docker:24.0.5-dind
|
||||
alias: docker
|
||||
stage: build
|
||||
script:
|
||||
|
|
@ -97,9 +97,9 @@ Your `.gitlab-ci.yml` should look similar to this:
|
|||
|
||||
```yaml
|
||||
build:
|
||||
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:20.10.16
|
||||
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:24.0.5
|
||||
services:
|
||||
- name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:18.09.7-dind
|
||||
- name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:24.0.5-dind
|
||||
alias: docker
|
||||
stage: build
|
||||
script:
|
||||
|
|
@ -120,10 +120,10 @@ If you're using Docker-in-Docker on your runners, your `.gitlab-ci.yml` file sho
|
|||
|
||||
```yaml
|
||||
build:
|
||||
image: docker:20.10.16
|
||||
image: docker:24.0.5
|
||||
stage: build
|
||||
services:
|
||||
- docker:20.10.16-dind
|
||||
- docker:24.0.5-dind
|
||||
script:
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
|
||||
- docker build -t $CI_REGISTRY/group/project/image:latest .
|
||||
|
|
@ -134,10 +134,10 @@ You can use [CI/CD variables](../../../ci/variables/_index.md) in your `.gitlab-
|
|||
|
||||
```yaml
|
||||
build:
|
||||
image: docker:20.10.16
|
||||
image: docker:24.0.5
|
||||
stage: build
|
||||
services:
|
||||
- docker:20.10.16-dind
|
||||
- docker:24.0.5-dind
|
||||
variables:
|
||||
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
script:
|
||||
|
|
@ -159,9 +159,9 @@ registry and used by subsequent stages, downloading the container image when nee
|
|||
|
||||
```yaml
|
||||
default:
|
||||
image: docker:20.10.16
|
||||
image: docker:24.0.5
|
||||
services:
|
||||
- docker:20.10.16-dind
|
||||
- docker:24.0.5-dind
|
||||
before_script:
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ when you view that file in your project's Git repository:
|
|||
|
||||
When you cherry-pick a merge commit in the GitLab UI or API, GitLab adds a [system note](../system_notes.md)
|
||||
to the related merge request thread. The format is {{< icon name="cherry-pick-commit" >}}
|
||||
`[USER]` **picked the changes into the branch** `[BRANCHNAME]` with commit** `[SHA]` `[DATE]`:
|
||||
`[USER]` **picked the changes into the branch** `[BRANCHNAME]` with commit `[SHA]` `[DATE]`:
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
class CollationChecker
|
||||
COLLATION_VERSION_MISMATCH_QUERY = <<~SQL
|
||||
SELECT
|
||||
collname AS collation_name,
|
||||
collprovider AS provider,
|
||||
collversion AS stored_version,
|
||||
pg_collation_actual_version(oid) AS actual_version,
|
||||
collversion <> pg_collation_actual_version(oid) AS has_mismatch
|
||||
FROM
|
||||
pg_collation
|
||||
WHERE
|
||||
collprovider = 'c'
|
||||
AND collversion IS NOT NULL
|
||||
AND pg_collation_actual_version(oid) IS NOT NULL
|
||||
AND collversion <> pg_collation_actual_version(oid);
|
||||
SQL
|
||||
|
||||
def self.run(database_name: nil, logger: Gitlab::AppLogger)
|
||||
Gitlab::Database::EachDatabase.each_connection(only: database_name) do |connection, database|
|
||||
new(connection, database, logger).run
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :connection, :database_name, :logger
|
||||
|
||||
def initialize(connection, database_name, logger)
|
||||
@connection = connection
|
||||
@database_name = database_name
|
||||
@logger = logger
|
||||
end
|
||||
|
||||
def run
|
||||
result = { mismatches_found: false, affected_indexes: [] }
|
||||
|
||||
logger.info("Checking for PostgreSQL collation mismatches on #{database_name} database...")
|
||||
|
||||
mismatched = mismatched_collations
|
||||
|
||||
if mismatched.empty?
|
||||
logger.info("No collation mismatches detected on #{database_name}.")
|
||||
return result
|
||||
end
|
||||
|
||||
result[:mismatches_found] = true
|
||||
|
||||
logger.warn("⚠️ COLLATION MISMATCHES DETECTED on #{database_name} database!")
|
||||
logger.warn("#{mismatched.count} collation(s) have version mismatches:")
|
||||
|
||||
mismatched.each do |row|
|
||||
logger.warn(
|
||||
" - #{row['collation_name']}: stored=#{row['stored_version']}, actual=#{row['actual_version']}"
|
||||
)
|
||||
end
|
||||
|
||||
affected_indexes = find_affected_indexes(mismatched)
|
||||
|
||||
if affected_indexes.empty?
|
||||
logger.info("No indexes appear to be affected by the collation mismatches.")
|
||||
return result
|
||||
end
|
||||
|
||||
result[:affected_indexes] = affected_indexes
|
||||
|
||||
logger.warn("Affected indexes that need to be rebuilt:")
|
||||
affected_indexes.each do |row|
|
||||
logger.warn(" - #{row['index_name']} (#{row['index_type']}) on table #{row['table_name']}")
|
||||
logger.warn(" • Affected columns: #{row['affected_columns']}")
|
||||
logger.warn(" • Type: #{unique?(row) ? 'UNIQUE' : 'NON-UNIQUE'}")
|
||||
end
|
||||
|
||||
# Provide remediation guidance
|
||||
provide_remediation_guidance(affected_indexes)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Helper method to check if an index is unique, handling both string and boolean values
|
||||
def unique?(index)
|
||||
unique = index['is_unique']
|
||||
unique == 't' || unique == true || unique == 'true'
|
||||
end
|
||||
|
||||
def mismatched_collations
|
||||
connection.select_all(COLLATION_VERSION_MISMATCH_QUERY).to_a
|
||||
rescue ActiveRecord::StatementInvalid => e
|
||||
logger.error("Error checking collation mismatches: #{e.message}")
|
||||
[]
|
||||
end
|
||||
|
||||
def find_affected_indexes(mismatched_collations)
|
||||
return [] if mismatched_collations.empty?
|
||||
|
||||
collation_names = mismatched_collations.map { |row| connection.quote(row['collation_name']) }.join(',')
|
||||
|
||||
# Using a more comprehensive query based on PostgreSQL wiki
|
||||
# Link: https://wiki.postgresql.org/wiki/Locale_data_changes#What_indexes_are_affected
|
||||
query = <<~SQL
|
||||
SELECT DISTINCT
|
||||
indrelid::regclass::text AS table_name,
|
||||
indexrelid::regclass::text AS index_name,
|
||||
string_agg(a.attname, ', ' ORDER BY s.attnum) AS affected_columns,
|
||||
am.amname AS index_type,
|
||||
s.indisunique AS is_unique
|
||||
FROM
|
||||
(SELECT
|
||||
indexrelid,
|
||||
indrelid,
|
||||
indcollation[j] coll,
|
||||
indkey[j] attnum,
|
||||
indisunique
|
||||
FROM
|
||||
pg_index i,
|
||||
generate_subscripts(indcollation, 1) g(j)
|
||||
) s
|
||||
JOIN
|
||||
pg_collation c ON coll=c.oid
|
||||
JOIN
|
||||
pg_class idx ON idx.oid = s.indexrelid
|
||||
JOIN
|
||||
pg_am am ON idx.relam = am.oid
|
||||
JOIN
|
||||
pg_attribute a ON a.attrelid = s.indrelid AND a.attnum = s.attnum
|
||||
WHERE
|
||||
c.collname IN (#{collation_names})
|
||||
GROUP BY
|
||||
s.indexrelid, s.indrelid, s.indisunique, index_name, table_name, am.amname
|
||||
ORDER BY
|
||||
table_name,
|
||||
index_name;
|
||||
SQL
|
||||
|
||||
connection.select_all(query).to_a
|
||||
rescue ActiveRecord::StatementInvalid => e
|
||||
logger.error("Error finding affected indexes: #{e.message}")
|
||||
[]
|
||||
end
|
||||
|
||||
def provide_remediation_guidance(affected_indexes)
|
||||
log_remediation_header
|
||||
log_duplicate_entry_checks(affected_indexes)
|
||||
log_index_rebuild_commands(affected_indexes)
|
||||
log_collation_refresh_commands
|
||||
log_conclusion
|
||||
end
|
||||
|
||||
def log_remediation_header
|
||||
logger.warn("\nREMEDIATION STEPS:")
|
||||
logger.warn("1. Put GitLab into maintenance mode")
|
||||
logger.warn("2. Run the following SQL commands:")
|
||||
end
|
||||
|
||||
def log_duplicate_entry_checks(affected_indexes)
|
||||
# Use the unique? helper method for consistency
|
||||
unique_indexes = affected_indexes.select { |idx| unique?(idx) }
|
||||
return unless unique_indexes.any?
|
||||
|
||||
logger.warn("\n# Step 1: Check for duplicate entries in unique indexes")
|
||||
unique_indexes.each do |idx|
|
||||
logger.warn("-- Check for duplicates in #{idx['table_name']} (unique index: #{idx['index_name']})")
|
||||
columns = idx['affected_columns'].split(', ')
|
||||
cols_str = columns.join(', ')
|
||||
|
||||
logger.warn(
|
||||
"SELECT #{cols_str}, COUNT(*), ARRAY_AGG(id) " \
|
||||
"FROM #{idx['table_name']} " \
|
||||
"GROUP BY #{cols_str} HAVING COUNT(*) > 1 LIMIT 1;"
|
||||
)
|
||||
end
|
||||
|
||||
logger.warn("\n# If duplicates exist, you may need to use gitlab:db:deduplicate_tags or similar tasks")
|
||||
logger.warn("# to fix duplicate entries before rebuilding unique indexes.")
|
||||
end
|
||||
|
||||
def log_index_rebuild_commands(affected_indexes)
|
||||
return unless affected_indexes.any?
|
||||
|
||||
logger.warn("\n# Step 2: Rebuild affected indexes")
|
||||
logger.warn("# Option A: Rebuild individual indexes with minimal downtime:")
|
||||
affected_indexes.each do |row|
|
||||
logger.warn("REINDEX INDEX #{row['index_name']} CONCURRENTLY;")
|
||||
end
|
||||
|
||||
logger.warn("\n# Option B: Alternatively, rebuild all indexes at once (requires downtime):")
|
||||
logger.warn("REINDEX DATABASE #{database_name};")
|
||||
end
|
||||
|
||||
def log_collation_refresh_commands
|
||||
# Customer reported this command as working: https://gitlab.com/groups/gitlab-org/-/epics/8573#note_2513370623
|
||||
logger.warn("\n# Step 3: Refresh collation versions")
|
||||
logger.warn("ALTER DATABASE #{database_name} REFRESH COLLATION VERSION;")
|
||||
logger.warn("-- This updates all collation versions in the database to match the current OS")
|
||||
end
|
||||
|
||||
def log_conclusion
|
||||
logger.warn("\n3. Take GitLab out of maintenance mode")
|
||||
logger.warn("\nFor more information, see: https://docs.gitlab.com/administration/postgresql/upgrading_os/")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -150,11 +150,11 @@ module Gitlab
|
|||
additional: true,
|
||||
auth: true,
|
||||
type: "URL",
|
||||
example: "https://www.site.com/welcome",
|
||||
example: "https://www.site.com/welcome*",
|
||||
name: s_("DastProfiles|Success URL"),
|
||||
description: s_(
|
||||
"DastProfiles|A URL that is compared to the URL in the browser to determine if authentication " \
|
||||
"has succeeded after the login form is submitted."
|
||||
"has succeeded after the login form is submitted. Wildcard `*` can be used to match a dynamic URL."
|
||||
)
|
||||
},
|
||||
DAST_AUTH_SUCCESS_IF_ELEMENT_FOUND: {
|
||||
|
|
|
|||
|
|
@ -598,6 +598,20 @@ namespace :gitlab do
|
|||
end
|
||||
end
|
||||
|
||||
desc 'GitLab | DB | Check for PostgreSQL collation mismatches and list affected indexes'
|
||||
task collation_checker: :environment do
|
||||
Gitlab::Database::CollationChecker.run(logger: Logger.new($stdout))
|
||||
end
|
||||
|
||||
namespace :collation_checker do
|
||||
each_database(databases) do |database_name|
|
||||
desc "GitLab | DB | Check for PostgreSQL collation mismatches on the #{database_name} database"
|
||||
task database_name => :environment do
|
||||
Gitlab::Database::CollationChecker.run(database_name: database_name, logger: Logger.new($stdout))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :dictionary do
|
||||
desc 'Generate database docs yaml'
|
||||
task generate: :environment do
|
||||
|
|
|
|||
|
|
@ -20082,7 +20082,7 @@ msgstr ""
|
|||
msgid "DastProfiles|/graphql"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|A URL that is compared to the URL in the browser to determine if authentication has succeeded after the login form is submitted."
|
||||
msgid "DastProfiles|A URL that is compared to the URL in the browser to determine if authentication has succeeded after the login form is submitted. Wildcard `*` can be used to match a dynamic URL."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|A comma-separated list of actions to take after login but before login verification. Supports `click` and `select` actions. See [Taking additional actions after submitting the login form](%{documentation_link})."
|
||||
|
|
@ -38104,9 +38104,6 @@ msgstr ""
|
|||
msgid "MemberRole|Direct users assigned"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Dismiss Planner role promotion"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Edit admin role"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38158,9 +38155,6 @@ msgstr ""
|
|||
msgid "MemberRole|Learn more about %{linkStart}available custom permissions%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Learn more about roles and permissions"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|Manage roles"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38173,9 +38167,6 @@ msgstr ""
|
|||
msgid "MemberRole|Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|New Planner role"
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|New role"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38281,12 +38272,6 @@ msgstr ""
|
|||
msgid "MemberRole|The Owner role is typically assigned to the individual or team responsible for managing and maintaining the group or creating the project. This role has the highest level of administrative control, and can manage all aspects of the group or project, including managing other Owners."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|The Planner role is a hybrid of the existing Guest and Reporter roles but designed for users who need access to planning workflows."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|The Planner role is a hybrid of the existing Guest and Reporter roles but designed for users who need access to planning workflows. For more information about the new role, see %{blogLinkStart}our blog%{blogLinkEnd} or %{learnMoreStart}learn more about roles and permissions%{learnMoreEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "MemberRole|The Planner role is suitable for team members who need to manage projects and track work items but do not need to contribute code, such as project managers and scrum masters."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -53238,6 +53223,9 @@ msgstr ""
|
|||
msgid "Runners|No project runners found"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|No runners managers found"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|No spot. Default choice for Windows Shell executor."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -53419,6 +53407,9 @@ msgstr ""
|
|||
msgid "Runners|Runner"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Runner %{name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Runner %{name} was assigned to this project."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -53485,6 +53476,9 @@ msgstr ""
|
|||
msgid "Runners|Runner is stale; last contact was %{runner_contact} ago"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Runner managers registered under this configuration are listed here. Register and start at least one runner manager."
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Runner performance insights"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ spec/frontend/projects/settings/components/branch_rule_modal_spec.js
|
|||
spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
|
||||
spec/frontend/ref/init_ambiguous_ref_modal_spec.js
|
||||
spec/frontend/releases/components/asset_links_form_spec.js
|
||||
spec/frontend/repository/components/table/index_spec.js
|
||||
spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js
|
||||
spec/frontend/sidebar/components/confidential/confidentiality_dropdown_spec.js
|
||||
spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import RunnerGroups from '~/ci/runner/components/runner_groups.vue';
|
|||
import RunnerTags from '~/ci/runner/components/runner_tags.vue';
|
||||
import RunnerTag from '~/ci/runner/components/runner_tag.vue';
|
||||
import RunnerManagers from '~/ci/runner/components/runner_managers.vue';
|
||||
import RunnerJobs from '~/ci/runner/components/runner_jobs.vue';
|
||||
|
||||
import { runnerData, runnerWithGroupData } from '../mock_data';
|
||||
|
||||
|
|
@ -26,12 +27,14 @@ describe('RunnerDetails', () => {
|
|||
|
||||
const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
|
||||
const findRunnerManagers = () => wrapper.findComponent(RunnerManagers);
|
||||
const findRunnerJobs = () => wrapper.findComponent(RunnerJobs);
|
||||
|
||||
const findDdContent = (label) => findDd(label, wrapper).text().replace(/\s+/g, ' ');
|
||||
|
||||
const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
|
||||
wrapper = mountFn(RunnerDetails, {
|
||||
propsData: {
|
||||
runnerId: mockRunner.id,
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
|
|
@ -113,8 +116,8 @@ describe('RunnerDetails', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('"Runners" field', () => {
|
||||
it('displays runner managers count of $count', () => {
|
||||
describe('Status', () => {
|
||||
it('displays runner managers', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
runner: mockRunner,
|
||||
|
|
@ -123,6 +126,18 @@ describe('RunnerDetails', () => {
|
|||
|
||||
expect(findRunnerManagers().props('runner')).toEqual(mockRunner);
|
||||
});
|
||||
|
||||
it('displays runner jobs', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
runner: mockRunner,
|
||||
showAccessHelp: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findRunnerJobs().props('runnerId')).toEqual(mockRunner.id);
|
||||
expect(findRunnerJobs().props('showAccessHelp')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Group runners', () => {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ describe('RunnerHeader', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(findPageHeading().props('heading')).toBe(`#99 (${mockRunnerSha})`);
|
||||
expect(findPageHeading().props('heading')).toBe(`Runner #99 (${mockRunnerSha})`);
|
||||
});
|
||||
|
||||
it('displays the runner locked icon', () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlTableLite } from '@gitlab/ui';
|
||||
import { GlTableLite, GlEmptyState } from '@gitlab/ui';
|
||||
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ describe('RunnerJobs', () => {
|
|||
const findCrudComponent = () => wrapper.findComponent(CrudComponent);
|
||||
const findCrudExpandToggle = () => wrapper.findByTestId('crud-collapse-toggle');
|
||||
const findRunnerManagersTable = () => wrapper.findComponent(RunnerManagersTable);
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
|
||||
const createComponent = ({ props, mountFn = shallowMountExtended } = {}) => {
|
||||
wrapper = mountFn(RunnerManagers, {
|
||||
|
|
@ -34,7 +35,7 @@ describe('RunnerJobs', () => {
|
|||
});
|
||||
};
|
||||
|
||||
it('hides if no runners', () => {
|
||||
it('shows an empty state if no runners', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
runner: {
|
||||
|
|
@ -46,7 +47,12 @@ describe('RunnerJobs', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(findCrudComponent().exists()).toBe(false);
|
||||
expect(findEmptyState().props('title')).toBe('No runners managers found');
|
||||
expect(findEmptyState().text()).toBe(
|
||||
'Runner managers registered under this configuration are listed here. Register and start at least one runner manager.',
|
||||
);
|
||||
|
||||
expect(findRunnerManagersTable().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('Runners count', () => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import { captureException } from '~/ci/runner/sentry_utils';
|
|||
import RunnerHeader from '~/ci/runner/components/runner_header.vue';
|
||||
import RunnerHeaderActions from '~/ci/runner/components/runner_header_actions.vue';
|
||||
import RunnerDetails from '~/ci/runner/components/runner_details.vue';
|
||||
import RunnerJobs from '~/ci/runner/components/runner_jobs.vue';
|
||||
|
||||
import RunnerShow from '~/ci/runner/components/runner_show.vue';
|
||||
|
||||
|
|
@ -41,7 +40,6 @@ describe('RunnerShow', () => {
|
|||
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
|
||||
const findRunnerHeaderActions = () => wrapper.findComponent(RunnerHeaderActions);
|
||||
const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
|
||||
const findRunnerJobs = () => wrapper.findComponent(RunnerJobs);
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
mockApollo = createMockApollo([[runnerQuery, runnerQueryHandler]]);
|
||||
|
|
@ -134,10 +132,4 @@ describe('RunnerShow', () => {
|
|||
|
||||
expect(findRunnerDetails().props('runner')).toEqual(mockRunner);
|
||||
});
|
||||
|
||||
it('shows runner jobs', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findRunnerJobs().props('runnerId')).toBe(mockRunnerId);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import { GlCollapsibleListbox, GlBadge, GlPopover } from '@gitlab/ui';
|
||||
import { GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { roleDropdownItems } from '~/members/utils';
|
||||
import RoleSelector from '~/members/components/role_selector.vue';
|
||||
import {
|
||||
ACCESS_LEVEL_PLANNER_STRING,
|
||||
ACCESS_LEVEL_MAINTAINER_STRING,
|
||||
} from '~/access_level/constants';
|
||||
import { member } from '../mock_data';
|
||||
|
||||
describe('Role selector', () => {
|
||||
|
|
@ -25,7 +21,6 @@ describe('Role selector', () => {
|
|||
const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
const getDropdownItem = (id) => wrapper.findByTestId(`listbox-item-${id}`);
|
||||
const findRoleName = (id) => getDropdownItem(id).find('[data-testid="role-name"]');
|
||||
const findGlPopover = () => wrapper.findComponent(GlPopover);
|
||||
|
||||
describe('dropdown component', () => {
|
||||
it('shows the dropdown with the expected props', () => {
|
||||
|
|
@ -74,21 +69,5 @@ describe('Role selector', () => {
|
|||
it.each(dropdownItems.flatten)('shows the role name for $text', ({ value, text }) => {
|
||||
expect(findRoleName(value).text()).toBe(text);
|
||||
});
|
||||
|
||||
it('shows badge only on `Planner role` with a popover', () => {
|
||||
// Show on planner dropdown item
|
||||
expect(getDropdownItem(ACCESS_LEVEL_PLANNER_STRING).findComponent(GlBadge).exists()).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
// Do not show on maintainer or any other dropdown item
|
||||
expect(getDropdownItem(ACCESS_LEVEL_MAINTAINER_STRING).findComponent(GlBadge).exists()).toBe(
|
||||
false,
|
||||
);
|
||||
|
||||
expect(findGlPopover().text()).toBe(
|
||||
'The Planner role is a hybrid of the existing Guest and Reporter roles but designed for users who need access to planning workflows.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import { GlBanner, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
|
||||
import PlanRoleBanner from '~/planner_role_banner/components/planner_role_banner.vue';
|
||||
|
||||
describe('Planner role banner', () => {
|
||||
let wrapper;
|
||||
const userCalloutDismissSpy = jest.fn();
|
||||
|
||||
const createWrapper = (shouldShowCallout) => {
|
||||
wrapper = shallowMount(PlanRoleBanner, {
|
||||
stubs: {
|
||||
UserCalloutDismisser: makeMockUserCalloutDismisser({
|
||||
dismiss: userCalloutDismissSpy,
|
||||
shouldShowCallout,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findGlBanner = () => wrapper.findComponent(GlBanner);
|
||||
const findGlSprintf = () => wrapper.findComponent(GlSprintf);
|
||||
|
||||
it('renders the banner', () => {
|
||||
createWrapper(true);
|
||||
|
||||
expect(findGlBanner().props('title')).toBe('New Planner role');
|
||||
expect(findGlSprintf().attributes('message')).toBe(
|
||||
'The Planner role is a hybrid of the existing Guest and Reporter roles but designed for users who need access to planning workflows. For more information about the new role, see %{blogLinkStart}our blog%{blogLinkEnd} or %{learnMoreStart}learn more about roles and permissions%{learnMoreEnd}.',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not render the banner', () => {
|
||||
createWrapper(false);
|
||||
|
||||
expect(findGlBanner().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -100,6 +100,11 @@ function factory({
|
|||
commits,
|
||||
},
|
||||
apolloProvider: createMockApolloProvider(ref),
|
||||
data() {
|
||||
return {
|
||||
ref,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ describe('TooltipOnTruncate directive', () => {
|
|||
});
|
||||
|
||||
it('unbinds when destroyed', () => {
|
||||
wrapper.vm.$destroy();
|
||||
wrapper.destroy();
|
||||
|
||||
expect(getBinding(wrapper.element, 'gl-tooltip')).toBeUndefined();
|
||||
expect(getBinding(wrapper.element, 'gl-resize-observer')).toBeUndefined();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,333 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::CollationChecker, feature_category: :database do
|
||||
describe '.run' do
|
||||
let(:connection) { instance_double(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) }
|
||||
let(:database_name) { 'main' }
|
||||
let(:logger) { instance_double(Gitlab::AppLogger, info: nil, warn: nil, error: nil) }
|
||||
|
||||
it 'instantiates the class and calls run' do
|
||||
instance = instance_double(described_class)
|
||||
|
||||
expect(Gitlab::Database::EachDatabase).to receive(:each_connection)
|
||||
.with(only: database_name)
|
||||
.and_yield(connection, database_name)
|
||||
|
||||
expect(described_class).to receive(:new)
|
||||
.with(connection, database_name, logger)
|
||||
.and_return(instance)
|
||||
expect(instance).to receive(:run)
|
||||
|
||||
described_class.run(database_name: database_name, logger: logger)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
# Mock-based tests for edge cases and error handling
|
||||
context 'with mocked database connection' do
|
||||
let(:connection) { instance_double(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) }
|
||||
let(:database_name) { 'main' }
|
||||
let(:logger) { instance_double(Gitlab::AppLogger, info: nil, warn: nil, error: nil) }
|
||||
let(:checker) { described_class.new(connection, database_name, logger) }
|
||||
|
||||
context 'when no collation mismatches are found' do
|
||||
let(:empty_results) { instance_double(ActiveRecord::Result, to_a: []) }
|
||||
|
||||
before do
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(described_class::COLLATION_VERSION_MISMATCH_QUERY)
|
||||
.and_return(empty_results)
|
||||
end
|
||||
|
||||
it 'logs a success message and returns no mismatches' do
|
||||
expect(logger).to receive(:info).with("Checking for PostgreSQL collation mismatches on main database...")
|
||||
expect(logger).to receive(:info).with("No collation mismatches detected on main.")
|
||||
|
||||
result = checker.run
|
||||
|
||||
expect(result).to eq({ mismatches_found: false, affected_indexes: [] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when collation mismatches exist but no indexes are affected' do
|
||||
let(:mismatches) do
|
||||
instance_double(
|
||||
ActiveRecord::Result,
|
||||
to_a: [{ 'collation_name' => 'en_US.utf8', 'stored_version' => '1.2.3', 'actual_version' => '1.2.4' }]
|
||||
)
|
||||
end
|
||||
|
||||
let(:empty_affected) { instance_double(ActiveRecord::Result, to_a: []) }
|
||||
|
||||
before do
|
||||
allow(connection).to receive(:quote)
|
||||
.with('en_US.utf8')
|
||||
.and_return("'en_US.utf8'")
|
||||
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(described_class::COLLATION_VERSION_MISMATCH_QUERY)
|
||||
.and_return(mismatches)
|
||||
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(/SELECT DISTINCT.*FROM.*pg_collation.*WHERE.*collname IN \('en_US.utf8'\)/m)
|
||||
.and_return(empty_affected)
|
||||
end
|
||||
|
||||
it 'logs warnings about mismatches but reports no affected indexes' do
|
||||
expect(logger).to receive(:info).with("Checking for PostgreSQL collation mismatches on main database...")
|
||||
expect(logger).to receive(:warn).with("⚠️ COLLATION MISMATCHES DETECTED on main database!")
|
||||
expect(logger).to receive(:warn).with("1 collation(s) have version mismatches:")
|
||||
expect(logger).to receive(:warn).with(" - en_US.utf8: stored=1.2.3, actual=1.2.4")
|
||||
expect(logger).to receive(:info).with("No indexes appear to be affected by the collation mismatches.")
|
||||
|
||||
result = checker.run
|
||||
|
||||
expect(result).to eq({ mismatches_found: true, affected_indexes: [] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when collation mismatches exist and indexes are affected (mock version)' do
|
||||
let(:mismatches) do
|
||||
instance_double(
|
||||
ActiveRecord::Result,
|
||||
to_a: [{ 'collation_name' => 'en_US.utf8', 'stored_version' => '1.2.3', 'actual_version' => '1.2.4' }]
|
||||
)
|
||||
end
|
||||
|
||||
let(:affected_indexes) do
|
||||
instance_double(
|
||||
ActiveRecord::Result,
|
||||
to_a: [
|
||||
{
|
||||
'table_name' => 'projects',
|
||||
'index_name' => 'index_projects_on_name',
|
||||
'affected_columns' => 'name',
|
||||
'index_type' => 'btree',
|
||||
'is_unique' => 't'
|
||||
},
|
||||
{
|
||||
'table_name' => 'users',
|
||||
'index_name' => 'index_users_on_username',
|
||||
'affected_columns' => 'username',
|
||||
'index_type' => 'btree',
|
||||
'is_unique' => 'f'
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(described_class::COLLATION_VERSION_MISMATCH_QUERY)
|
||||
.and_return(mismatches)
|
||||
|
||||
allow(connection).to receive(:quote)
|
||||
.with('en_US.utf8')
|
||||
.and_return("'en_US.utf8'")
|
||||
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(/SELECT DISTINCT.*FROM.*pg_collation.*WHERE.*collname IN \('en_US.utf8'\)/m)
|
||||
.and_return(affected_indexes)
|
||||
end
|
||||
|
||||
it 'logs warnings and provides remediation guidance' do
|
||||
# Test basic detection
|
||||
expect(logger).to receive(:info).with("Checking for PostgreSQL collation mismatches on main database...")
|
||||
expect(logger).to receive(:warn).with("⚠️ COLLATION MISMATCHES DETECTED on main database!")
|
||||
expect(logger).to receive(:warn).with("1 collation(s) have version mismatches:")
|
||||
expect(logger).to receive(:warn).with(" - en_US.utf8: stored=1.2.3, actual=1.2.4")
|
||||
|
||||
# Test affected indexes are listed
|
||||
expect(logger).to receive(:warn).with("Affected indexes that need to be rebuilt:")
|
||||
expect(logger).to receive(:warn).with(" - index_projects_on_name (btree) on table projects")
|
||||
expect(logger).to receive(:warn).with(" • Affected columns: name")
|
||||
expect(logger).to receive(:warn).with(" • Type: UNIQUE")
|
||||
expect(logger).to receive(:warn).with(" - index_users_on_username (btree) on table users")
|
||||
expect(logger).to receive(:warn).with(" • Affected columns: username")
|
||||
expect(logger).to receive(:warn).with(" • Type: NON-UNIQUE")
|
||||
|
||||
# Test remediation header
|
||||
expect(logger).to receive(:warn).with("\nREMEDIATION STEPS:")
|
||||
expect(logger).to receive(:warn).with("1. Put GitLab into maintenance mode")
|
||||
expect(logger).to receive(:warn).with("2. Run the following SQL commands:")
|
||||
|
||||
# Test duplicate entry checks
|
||||
expect(logger).to receive(:warn).with("\n# Step 1: Check for duplicate entries in unique indexes")
|
||||
expect(logger).to receive(:warn).with(
|
||||
"-- Check for duplicates in projects (unique index: index_projects_on_name)"
|
||||
)
|
||||
expect(logger).to receive(:warn).with(
|
||||
/SELECT name, COUNT\(\*\), ARRAY_AGG\(id\) FROM projects GROUP BY name HAVING COUNT\(\*\) > 1 LIMIT 1;/
|
||||
)
|
||||
expect(logger).to receive(:warn).with(/\n# If duplicates exist/)
|
||||
|
||||
# Test index rebuild commands
|
||||
expect(logger).to receive(:warn).with("\n# Step 2: Rebuild affected indexes")
|
||||
expect(logger).to receive(:warn).with("# Option A: Rebuild individual indexes with minimal downtime:")
|
||||
expect(logger).to receive(:warn).with("REINDEX INDEX index_projects_on_name CONCURRENTLY;")
|
||||
expect(logger).to receive(:warn).with("REINDEX INDEX index_users_on_username CONCURRENTLY;")
|
||||
expect(logger).to receive(:warn).with(
|
||||
"\n# Option B: Alternatively, rebuild all indexes at once (requires downtime):"
|
||||
)
|
||||
expect(logger).to receive(:warn).with("REINDEX DATABASE main;")
|
||||
|
||||
# Test collation refresh commands
|
||||
expect(logger).to receive(:warn).with("\n# Step 3: Refresh collation versions")
|
||||
expect(logger).to receive(:warn).with("ALTER DATABASE main REFRESH COLLATION VERSION;")
|
||||
expect(logger).to receive(:warn).with(
|
||||
"-- This updates all collation versions in the database to match the current OS"
|
||||
)
|
||||
|
||||
# Test conclusion
|
||||
expect(logger).to receive(:warn).with("\n3. Take GitLab out of maintenance mode")
|
||||
expect(logger).to receive(:warn).with("\nFor more information, see: https://docs.gitlab.com/administration/postgresql/upgrading_os/")
|
||||
|
||||
result = checker.run
|
||||
|
||||
expect(result).to include(mismatches_found: true)
|
||||
expect(result[:affected_indexes]).to eq(affected_indexes.to_a)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is an error checking for mismatches' do
|
||||
before do
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(described_class::COLLATION_VERSION_MISMATCH_QUERY)
|
||||
.and_raise(ActiveRecord::StatementInvalid, 'test error')
|
||||
end
|
||||
|
||||
it 'logs the error and returns no mismatches' do
|
||||
expect(logger).to receive(:info).with("Checking for PostgreSQL collation mismatches on main database...")
|
||||
expect(logger).to receive(:error).with("Error checking collation mismatches: test error")
|
||||
|
||||
result = checker.run
|
||||
|
||||
expect(result).to eq({ mismatches_found: false, affected_indexes: [] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is an error finding affected indexes' do
|
||||
let(:mismatches) do
|
||||
instance_double(
|
||||
ActiveRecord::Result,
|
||||
to_a: [{ 'collation_name' => 'en_US.utf8', 'stored_version' => '1.2.3', 'actual_version' => '1.2.4' }]
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(described_class::COLLATION_VERSION_MISMATCH_QUERY)
|
||||
.and_return(mismatches)
|
||||
|
||||
allow(connection).to receive(:quote)
|
||||
.with('en_US.utf8')
|
||||
.and_return("'en_US.utf8'")
|
||||
|
||||
allow(connection).to receive(:select_all)
|
||||
.with(/SELECT DISTINCT.*FROM.*pg_collation.*WHERE.*collname IN \('en_US.utf8'\)/m)
|
||||
.and_raise(ActiveRecord::StatementInvalid, 'test error')
|
||||
end
|
||||
|
||||
it 'logs the error and returns only mismatches' do
|
||||
expect(logger).to receive(:info).with("Checking for PostgreSQL collation mismatches on main database...")
|
||||
expect(logger).to receive(:warn).with("⚠️ COLLATION MISMATCHES DETECTED on main database!")
|
||||
expect(logger).to receive(:warn).with("1 collation(s) have version mismatches:")
|
||||
expect(logger).to receive(:warn).with(" - en_US.utf8: stored=1.2.3, actual=1.2.4")
|
||||
expect(logger).to receive(:error).with("Error finding affected indexes: test error")
|
||||
|
||||
result = checker.run
|
||||
|
||||
expect(result).to include(mismatches_found: true)
|
||||
expect(result[:affected_indexes]).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Real database test for the happy path
|
||||
context 'with real database connection' do
|
||||
let(:connection) { ActiveRecord::Base.connection }
|
||||
let(:database_name) { connection.current_database }
|
||||
let(:logger) { instance_double(Logger, info: nil, warn: nil, error: nil) }
|
||||
let(:checker) { described_class.new(connection, database_name, logger) }
|
||||
|
||||
let(:table_name) { '_test_c_collation_table' }
|
||||
let(:index_name) { '_test_c_collation_index' }
|
||||
let(:c_collation) { 'C' } # Use standard C collation which should be available
|
||||
|
||||
# Find the real OID of the C collation for our test
|
||||
let!(:c_collation_info) do
|
||||
connection.select_all(
|
||||
"SELECT oid FROM pg_collation WHERE collname = '#{c_collation}' AND collprovider = 'c' LIMIT 1"
|
||||
).first
|
||||
end
|
||||
|
||||
let!(:c_collation_oid) { c_collation_info&.[]('oid') }
|
||||
|
||||
before do
|
||||
skip 'C collation not found in database' unless c_collation_info
|
||||
|
||||
# Create test table with a column using C collation
|
||||
connection.execute(<<~SQL)
|
||||
CREATE TABLE #{table_name} (
|
||||
id serial PRIMARY KEY,
|
||||
test_col varchar(255) COLLATE "#{c_collation}" NOT NULL
|
||||
);
|
||||
SQL
|
||||
|
||||
# Create an index on the collated column
|
||||
connection.execute(<<~SQL)
|
||||
CREATE INDEX #{index_name} ON #{table_name} (test_col);
|
||||
SQL
|
||||
|
||||
# Insert test data
|
||||
connection.execute(<<~SQL)
|
||||
INSERT INTO #{table_name} (test_col) VALUES ('value1');
|
||||
SQL
|
||||
end
|
||||
|
||||
after do
|
||||
connection.execute("DROP TABLE IF EXISTS #{table_name} CASCADE;")
|
||||
end
|
||||
|
||||
it 'detects C collation mismatch and finds affected index' do
|
||||
allow(checker).to receive(:mismatched_collations) do
|
||||
# Create a modified query to simulate actual version being different
|
||||
modified_query = described_class::COLLATION_VERSION_MISMATCH_QUERY
|
||||
.gsub('collversion', "'123.456'")
|
||||
.gsub('pg_collation_actual_version(oid)', "'987.654.321'")
|
||||
|
||||
connection.select_all(modified_query).to_a
|
||||
end
|
||||
|
||||
# Run the checker with our mocked version mismatch
|
||||
result = checker.run
|
||||
|
||||
# Verify we found mismatches
|
||||
expect(result[:mismatches_found]).to be true
|
||||
|
||||
# Verify we found affected indexes
|
||||
expect(result[:affected_indexes]).not_to be_empty
|
||||
|
||||
# Verify we found our test table index
|
||||
test_indexes = result[:affected_indexes].select { |idx| idx['table_name'] == table_name }
|
||||
|
||||
expect(test_indexes).not_to be_empty, "Expected to find test table index but found none"
|
||||
expect(test_indexes.first['index_name']).to eq(index_name), "Expected to find our specific test index"
|
||||
|
||||
# Verify remediation SQL includes our test index
|
||||
rebuild_commands = []
|
||||
allow(logger).to receive(:warn) do |message|
|
||||
rebuild_commands << message if message.include?('REINDEX INDEX')
|
||||
end
|
||||
|
||||
# Run again to capture remediation SQL
|
||||
checker.run
|
||||
|
||||
# Verify rebuild command for our test index
|
||||
expect(rebuild_commands.any? { |cmd| cmd.include?(index_name) }).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -12,7 +12,6 @@ RSpec.describe 'Bulk update work items', feature_category: :team_planning do
|
|||
let_it_be(:label2) { create(:group_label, group: group) }
|
||||
let_it_be_with_reload(:updatable_work_items) { create_list(:work_item, 2, project: project, label_ids: [label1.id]) }
|
||||
let_it_be(:private_project) { create(:project, :private) }
|
||||
|
||||
let(:parent) { project }
|
||||
let(:mutation) { graphql_mutation(:work_item_bulk_update, base_arguments.merge(additional_arguments)) }
|
||||
let(:mutation_response) { graphql_mutation_response(:work_item_bulk_update) }
|
||||
|
|
@ -120,4 +119,162 @@ RSpec.describe 'Bulk update work items', feature_category: :team_planning do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating confidential attribute' do
|
||||
let(:additional_arguments) do
|
||||
{
|
||||
'confidential' => true
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates the confidential attribute for all work items' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
updatable_work_items.each(&:reload)
|
||||
end.to change { updatable_work_items.map(&:confidential) }.from([false, false]).to([true, true])
|
||||
|
||||
expect(mutation_response).to include(
|
||||
'updatedWorkItemCount' => updatable_work_items.count
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating multiple attributes simultaneously' do
|
||||
let_it_be(:assignee) { create(:user, developer_of: group) }
|
||||
let_it_be(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
let(:additional_arguments) do
|
||||
{
|
||||
'confidential' => true,
|
||||
'assigneesWidget' => {
|
||||
'assigneeIds' => [assignee.to_gid.to_s]
|
||||
},
|
||||
'milestoneWidget' => {
|
||||
'milestoneId' => milestone.to_gid.to_s
|
||||
},
|
||||
'labelsWidget' => {
|
||||
'addLabelIds' => [label2.to_gid.to_s]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates all specified attributes' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
updatable_work_items.each(&:reload)
|
||||
end.to change { updatable_work_items.map(&:confidential) }.from([false, false]).to([true, true])
|
||||
.and change { updatable_work_items.flat_map(&:assignee_ids) }.from([]).to([assignee.id] * 2)
|
||||
.and change { updatable_work_items.map(&:milestone_id) }.from([nil, nil]).to([milestone.id] * 2)
|
||||
.and change { updatable_work_items.flat_map(&:label_ids) }.from([label1.id] * 2).to([label1.id, label2.id] * 2)
|
||||
|
||||
expect(mutation_response).to include(
|
||||
'updatedWorkItemCount' => updatable_work_items.count
|
||||
)
|
||||
end
|
||||
|
||||
context 'when updating work items that do not support requested widgets' do
|
||||
let_it_be(:key_result) { create(:work_item, :key_result, project: project) }
|
||||
let_it_be(:issue) { create(:work_item, :issue, project: project) }
|
||||
|
||||
let(:updatable_work_item_ids) { [key_result.to_gid.to_s, issue.to_gid.to_s] }
|
||||
|
||||
context 'when updating milestone widget' do
|
||||
let(:additional_arguments) do
|
||||
{
|
||||
'milestoneWidget' => {
|
||||
'milestoneId' => milestone.to_gid.to_s
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates only work items that support the milestone widget' do
|
||||
# Key Results don't support milestones, but Issues do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change { issue.reload.milestone }.from(nil).to(milestone)
|
||||
.and not_change { key_result.reload.attributes['milestone_id'] }
|
||||
|
||||
expect(mutation_response).to include(
|
||||
'updatedWorkItemCount' => 1
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when work items have different types' do
|
||||
let_it_be(:task) { create(:work_item, :task, project: project) }
|
||||
let_it_be(:issue) { create(:work_item, :issue, project: project) }
|
||||
let(:updatable_work_item_ids) { [task.to_gid.to_s, issue.to_gid.to_s] }
|
||||
|
||||
let(:additional_arguments) do
|
||||
{
|
||||
'labelsWidget' => {
|
||||
'addLabelIds' => [label2.to_gid.to_s]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates work items of different types' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
task.reload
|
||||
issue.reload
|
||||
end.to change { task.label_ids + issue.label_ids }.from([]).to([label2.id, label2.id])
|
||||
|
||||
expect(mutation_response).to include(
|
||||
'updatedWorkItemCount' => 2
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when some updates fail' do
|
||||
let_it_be(:work_item_with_validation) { create(:work_item, project: project) }
|
||||
let(:updatable_work_item_ids) do
|
||||
updatable_work_items.map do |i|
|
||||
i.to_gid.to_s
|
||||
end + [work_item_with_validation.to_gid.to_s]
|
||||
end
|
||||
|
||||
let(:additional_arguments) do
|
||||
{
|
||||
'confidential' => true
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
# Simulate a validation error for one work item
|
||||
allow_next_instance_of(WorkItems::UpdateService) do |service|
|
||||
allow(service).to receive(:execute).and_call_original
|
||||
allow(service).to receive(:execute).with(work_item_with_validation).and_return(
|
||||
{ status: :error, message: 'Validation failed' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates only the successful work items' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
updatable_work_items.each(&:reload)
|
||||
work_item_with_validation.reload
|
||||
end.to change { updatable_work_items.map(&:confidential) }.from([false, false]).to([true, true])
|
||||
.and not_change { work_item_with_validation.confidential }.from(false)
|
||||
|
||||
expect(mutation_response).to include(
|
||||
'updatedWorkItemCount' => updatable_work_items.count
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no work items are provided' do
|
||||
let(:updatable_work_item_ids) { [] }
|
||||
|
||||
it 'returns 0 updated work items' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response).to include(
|
||||
'updatedWorkItemCount' => 0
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ RSpec.describe WorkItems::BulkUpdateService, feature_category: :team_planning do
|
|||
let_it_be(:label1) { create(:group_label, group: parent_group) }
|
||||
let_it_be(:label2) { create(:group_label, group: parent_group) }
|
||||
let_it_be(:label3) { create(:group_label, group: private_group) }
|
||||
let_it_be(:assignee) { create(:user, developer_of: group) }
|
||||
let_it_be(:milestone) { create(:milestone, group: group) }
|
||||
let_it_be_with_reload(:work_item1) { create(:work_item, :group_level, namespace: group, labels: [label1]) }
|
||||
let_it_be_with_reload(:work_item2) { create(:work_item, project: project, labels: [label1]) }
|
||||
let_it_be_with_reload(:work_item3) { create(:work_item, :group_level, namespace: parent_group, labels: [label1]) }
|
||||
|
|
@ -21,7 +23,7 @@ RSpec.describe WorkItems::BulkUpdateService, feature_category: :team_planning do
|
|||
|
||||
let(:updatable_work_items) { [work_item1, work_item2, work_item3, work_item4] }
|
||||
let(:updatable_work_item_ids) { updatable_work_items.map(&:id) }
|
||||
let(:widget_params) do
|
||||
let(:attributes) do
|
||||
{
|
||||
labels_widget: {
|
||||
add_label_ids: [label2.id],
|
||||
|
|
@ -35,7 +37,7 @@ RSpec.describe WorkItems::BulkUpdateService, feature_category: :team_planning do
|
|||
parent: parent,
|
||||
current_user: current_user,
|
||||
work_item_ids: updatable_work_item_ids,
|
||||
widget_params: widget_params
|
||||
attributes: attributes
|
||||
).execute
|
||||
end
|
||||
|
||||
|
|
@ -62,6 +64,42 @@ RSpec.describe WorkItems::BulkUpdateService, feature_category: :team_planning do
|
|||
expect(service_result[:updated_work_item_count]).to eq(1)
|
||||
end
|
||||
|
||||
context 'when updating non-widget attributes' do
|
||||
let(:attributes) { { confidential: true } }
|
||||
|
||||
it 'updates confidential attribute' do
|
||||
expect do
|
||||
service_result
|
||||
end.to change { work_item2.reload.confidential }.from(false).to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating multiple attributes' do
|
||||
let(:attributes) do
|
||||
{
|
||||
confidential: true,
|
||||
assignees_widget: {
|
||||
assignee_ids: [assignee.id]
|
||||
},
|
||||
milestone_widget: {
|
||||
milestone_id: milestone.id
|
||||
},
|
||||
labels_widget: {
|
||||
add_label_ids: [label2.id]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates all attributes correctly' do
|
||||
expect do
|
||||
service_result
|
||||
end.to change { work_item2.reload.confidential }.from(false).to(true)
|
||||
.and change { work_item2.assignee_ids }.from([]).to([assignee.id])
|
||||
.and change { work_item2.milestone_id }.from(nil).to(milestone.id)
|
||||
.and change { work_item2.label_ids }.from([label1.id]).to([label1.id, label2.id])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with EE license', if: Gitlab.ee? do
|
||||
before do
|
||||
stub_licensed_features(epics: true)
|
||||
|
|
|
|||
|
|
@ -575,6 +575,72 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor
|
|||
end
|
||||
end
|
||||
|
||||
describe 'collation_checker' do
|
||||
context 'with a single database' do
|
||||
before do
|
||||
skip_if_multiple_databases_are_setup
|
||||
end
|
||||
|
||||
it 'calls Gitlab::Database::CollationChecker with correct arguments' do
|
||||
logger_double = instance_double(Logger, level: nil, info: nil, warn: nil, error: nil)
|
||||
allow(Logger).to receive(:new).with($stdout).and_return(logger_double)
|
||||
|
||||
expect(Gitlab::Database::CollationChecker).to receive(:run)
|
||||
.with(logger: logger_double)
|
||||
|
||||
run_rake_task('gitlab:db:collation_checker')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple databases' do
|
||||
let(:logger_double) { instance_double(Logger, level: nil, info: nil, warn: nil, error: nil) }
|
||||
|
||||
before do
|
||||
skip_if_multiple_databases_not_setup(:ci)
|
||||
|
||||
allow(Logger).to receive(:new).with($stdout).and_return(logger_double)
|
||||
end
|
||||
|
||||
it 'calls Gitlab::Database::CollationChecker with correct arguments' do
|
||||
expect(Gitlab::Database::CollationChecker).to receive(:run)
|
||||
.with(logger: logger_double)
|
||||
|
||||
run_rake_task('gitlab:db:collation_checker')
|
||||
end
|
||||
|
||||
context 'when the single database task is used' do
|
||||
before do
|
||||
skip_if_shared_database(:ci)
|
||||
end
|
||||
|
||||
it 'calls Gitlab::Database::CollationChecker with the main database' do
|
||||
expect(Gitlab::Database::CollationChecker).to receive(:run)
|
||||
.with(database_name: 'main', logger: logger_double)
|
||||
|
||||
run_rake_task('gitlab:db:collation_checker:main')
|
||||
end
|
||||
|
||||
it 'calls Gitlab::Database::CollationChecker with the ci database' do
|
||||
expect(Gitlab::Database::CollationChecker).to receive(:run)
|
||||
.with(database_name: 'ci', logger: logger_double)
|
||||
|
||||
run_rake_task('gitlab:db:collation_checker:ci')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with geo configured' do
|
||||
before do
|
||||
skip_unless_geo_configured
|
||||
end
|
||||
|
||||
it 'does not create a task for the geo database' do
|
||||
expect { run_rake_task('gitlab:db:collation_checker:geo') }
|
||||
.to raise_error(/Don't know how to build task 'gitlab:db:collation_checker:geo'/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dictionary generate' do
|
||||
let(:db_config) { instance_double(ActiveRecord::DatabaseConfigurations::HashConfig, name: 'fake_db') }
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker, feature_category: :sourc
|
|||
|
||||
context 'with a non-existing merge request' do
|
||||
it 'does nothing' do
|
||||
expect(::MergeRequests::RetargetChainService).not_to receive(:new)
|
||||
expect(::Projects::DeleteBranchWorker).not_to receive(:new)
|
||||
|
||||
worker.perform(non_existing_record_id, sha, user.id)
|
||||
|
|
@ -30,6 +31,7 @@ RSpec.describe MergeRequests::DeleteSourceBranchWorker, feature_category: :sourc
|
|||
|
||||
context 'with a non-existing user' do
|
||||
it 'does nothing' do
|
||||
expect(::MergeRequests::RetargetChainService).not_to receive(:new)
|
||||
expect(::Projects::DeleteBranchWorker).not_to receive(:new)
|
||||
|
||||
worker.perform(merge_request.id, sha, non_existing_record_id)
|
||||
|
|
|
|||
Loading…
Reference in New Issue