From 1db959cb596423bfa05e6d96cc1b9f40f95d7138 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 10 Jul 2025 18:07:41 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab-ci.yml | 1 + .gitlab/CODEOWNERS | 1 + .gitlab/ci/rules.gitlab-ci.yml | 1 + .rubocop.yml | 7 - .../cop_description_with_example.yml | 1 - .../node_matcher_directive.yml | 1 - .../redundant_message_argument.yml | 1 - .../useless_message_assertion.yml | 1 - .rubocop_todo/layout/line_length.yml | 1 - .rubocop_todo/rspec/feature_category.yml | 1 - Gemfile.checksum | 2 +- Gemfile.lock | 3 +- Gemfile.next.checksum | 2 +- Gemfile.next.lock | 3 +- .../javascripts/lib/utils/text_markdown.js | 1 + .../members/components/modals/leave_modal.vue | 2 +- .../todos/components/todo_item.vue | 32 +- .../todos/components/todo_item_body.vue | 52 ++- .../todos/components/todo_item_timestamp.vue | 4 +- .../todos/components/todo_item_title.vue | 6 +- app/controllers/concerns/wiki_actions.rb | 2 - app/controllers/groups/boards_controller.rb | 1 - .../groups/work_items_controller.rb | 1 - .../profiles/preferences_controller.rb | 1 + app/controllers/projects/boards_controller.rb | 1 - app/controllers/projects/issues_controller.rb | 1 - .../merge_requests/application_controller.rb | 1 - .../projects/work_items_controller.rb | 1 - app/models/ci/bridge.rb | 4 +- app/models/ci/pipeline.rb | 1 - app/models/commit_status.rb | 2 +- app/models/concerns/ci/has_status.rb | 2 +- app/models/group.rb | 26 +- app/models/issue.rb | 6 +- app/models/members/group_member.rb | 2 +- .../members/last_group_owner_assigner.rb | 2 +- app/models/project.rb | 4 - app/models/user.rb | 1 + app/models/work_item.rb | 6 +- app/services/groups/transfer_service.rb | 4 + app/views/profiles/preferences/show.html.haml | 6 +- config/events/api_request_from_runner.yml | 1 + .../click_create_group_runner_button.yml | 2 + .../click_create_project_runner_button.yml | 2 + ...ck_view_all_link_in_pipeline_analytics.yml | 2 + ...unners_button_in_new_group_runner_form.yml | 2 + ...ners_button_in_new_project_runner_form.yml | 2 + config/events/set_runner_maintenance_note.yml | 2 + config/events/view_admin_runners_pageload.yml | 2 + .../beta/directory_code_dropdown_updates.yml | 2 +- ..._audit_events_remote_ip_proxy_protocol.yml | 10 - ...eyset_paginate_exported_merge_requests.yml | 10 - .../wip/continue_indented_text.yml | 2 +- .../wip/extensible_reference_filters.yml | 9 - config/initializers/sprockets_patch.rb | 2 +- ...ick_create_group_runner_button_monthly.yml | 2 + ...k_create_project_runner_button_monthly.yml | 2 + ...all_link_in_pipeline_analytics_monthly.yml | 2 + ...utton_in_new_group_runner_form_monthly.yml | 2 + ...ton_in_new_project_runner_form_monthly.yml | 2 + ...om_view_admin_runners_pageload_monthly.yml | 2 + ..._created_with_maintenance_note_monthly.yml | 2 + ...al_view_admin_runners_pageload_monthly.yml | 2 + ...lick_create_group_runner_button_weekly.yml | 2 + ...ck_create_project_runner_button_weekly.yml | 2 + ..._all_link_in_pipeline_analytics_weekly.yml | 2 + ...button_in_new_group_runner_form_weekly.yml | 2 + ...tton_in_new_project_runner_form_weekly.yml | 2 + ...rom_view_admin_runners_pageload_weekly.yml | 2 + ...r_created_with_maintenance_note_weekly.yml | 2 + ...tal_view_admin_runners_pageload_weekly.yml | 2 + .../counts_all/20210216175520_ci_runners.yml | 1 + ...045402_ci_runners_instance_type_active.yml | 1 + ...502050341_ci_runners_group_type_active.yml | 1 + ...2050834_ci_runners_project_type_active.yml | 1 + .../20210502050942_ci_runners_online.yml | 1 + ...ci_runners_instance_type_active_online.yml | 1 + ...22_ci_runners_group_type_active_online.yml | 1 + ..._ci_runners_project_type_active_online.yml | 1 + ...ance_runner_registration_token_allowed.yml | 2 + ...stance_runner_token_expiration_enabled.yml | 2 + ..._group_runner_token_expiration_enabled.yml | 2 + ...roject_runner_token_expiration_enabled.yml | 2 + .../backfill_issue_assignees_namespace_id.yml | 2 +- ...20725_add_markdown_maintain_indentation.rb | 13 + ...e_backfill_issue_assignees_namespace_id.rb | 20 + db/schema_migrations/20250604220725 | 1 + db/schema_migrations/20250708204640 | 1 + db/structure.sql | 1 + .../settings/vscode_extension_marketplace.md | 4 +- doc/api/groups.md | 40 ++ doc/api/pipelines.md | 3 +- doc/ci/jobs/job_control.md | 1 + doc/ci/review_apps/_index.md | 54 ++- .../best_practices/feature_flags.md | 2 +- doc/subscriptions/subscription-add-ons.md | 2 +- doc/tutorials/scrum_events/_index.md | 2 +- .../coverage_fuzzing/_index.md | 218 +++++----- doc/user/gitlab_duo/troubleshooting.md | 2 +- doc/user/gitlab_duo/turn_on_off.md | 369 +++++++---------- doc/user/gitlab_duo/turn_on_off_earlier.md | 240 +++++++++++ doc/user/group/_index.md | 2 +- doc/user/profile/preferences.md | 12 + .../repository/code_suggestions/_index.md | 5 +- .../repository/code_suggestions/set_up.md | 2 +- doc/user/project/repository/files/_index.md | 3 +- doc/user/project/web_ide/_index.md | 6 +- doc/user/workspace/_index.md | 3 +- doc/user/workspace/create_image.md | 2 + .../workspace/gitlab_agent_configuration.md | 3 +- .../set_up_gitlab_agent_and_proxies.md | 2 + doc/user/workspace/settings.md | 5 +- lib/api/helpers/internal_helpers.rb | 4 +- lib/gitlab/gon_helper.rb | 38 +- .../json/streaming_serializer.rb | 5 +- .../topology_service_client/health_service.rb | 36 ++ lib/tasks/gitlab/db.rake | 16 + locale/gitlab.pot | 119 ++++-- ...ck_retries_for_transactional_migrations.rb | 51 --- ...ck_retries_with_disable_ddl_transaction.rb | 41 -- .../frontend/find_jest_predictive_tests.js | 175 ++++++++ scripts/frontend/jest_ci.js | 23 +- .../frontend/jest_vue3_quarantine_utils.js | 26 ++ ...oken_in_vue_compat_fixture_ci_sequencer.js | 30 +- .../cli/flows/metric_definer.rb | 141 +++---- scripts/internal_events/cli/global_state.rb | 1 + .../internal_events/cli/helpers/formatting.rb | 2 +- .../cli/helpers/metric_options.rb | 131 +++--- scripts/internal_events/cli/metric.rb | 64 ++- spec/factories/work_items.rb | 5 + spec/features/projects/active_tabs_spec.rb | 8 +- .../ee_total_7d_single_event_array_metric.yml | 21 + spec/frontend/lib/utils/text_markdown_spec.js | 24 +- .../find_jest_predictive_tests.spec.js | 381 ++++++++++++++++++ .../todos/components/todo_item_body_spec.js | 13 + .../todos/components/todo_item_spec.js | 14 +- .../references/issue_reference_filter_spec.rb | 40 +- .../work_item_reference_filter_spec.rb | 55 +-- .../json/streaming_serializer_spec.rb | 18 - .../health_service_spec.rb | 71 ++++ spec/models/ci/bridge_spec.rb | 4 +- spec/models/ci/pipeline_spec.rb | 31 +- spec/models/commit_status_spec.rb | 4 +- spec/models/concerns/ci/has_status_spec.rb | 4 +- spec/models/group_spec.rb | 33 +- .../members/last_group_owner_assigner_spec.rb | 19 +- spec/models/project_spec.rb | 10 - spec/models/user_spec.rb | 3 + ...tries_for_transactional_migrations_spec.rb | 33 -- ...tries_with_disable_ddl_transaction_spec.rb | 56 --- .../cli/flows/metric_definer_spec.rb | 18 +- .../cli/flows/usage_viewer_spec.rb | 91 +++-- .../cli/helpers/metric_options_spec.rb | 46 +-- .../internal_events/cli/metric_spec.rb | 4 +- .../ci/abort_pipelines_service_spec.rb | 4 +- spec/tasks/gitlab/db_rake_spec.rb | 57 +++ .../danger/change_column_default_spec.rb | 2 +- .../change_column_default_migration.txt | 2 - 158 files changed, 2066 insertions(+), 1224 deletions(-) delete mode 100644 config/feature_flags/beta/stream_audit_events_remote_ip_proxy_protocol.yml delete mode 100644 config/feature_flags/gitlab_com_derisk/keyset_paginate_exported_merge_requests.yml delete mode 100644 config/feature_flags/wip/extensible_reference_filters.yml create mode 100644 db/migrate/20250604220725_add_markdown_maintain_indentation.rb create mode 100644 db/post_migrate/20250708204640_finalize_backfill_issue_assignees_namespace_id.rb create mode 100644 db/schema_migrations/20250604220725 create mode 100644 db/schema_migrations/20250708204640 create mode 100644 doc/user/gitlab_duo/turn_on_off_earlier.md create mode 100644 lib/gitlab/topology_service_client/health_service.rb delete mode 100644 rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations.rb delete mode 100644 rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb create mode 100755 scripts/frontend/find_jest_predictive_tests.js create mode 100644 spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event_array_metric.yml create mode 100644 spec/frontend/scripts/frontend/find_jest_predictive_tests.spec.js create mode 100644 spec/lib/gitlab/topology_service_client/health_service_spec.rb delete mode 100644 spec/rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations_spec.rb delete mode 100644 spec/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction_spec.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index edad7ef758e..b17533df2e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -235,6 +235,7 @@ variables: FLAKY_RSPEC_SUITE_REPORT_PATH: rspec/flaky/report-suite.json FRONTEND_FIXTURES_MAPPING_PATH: crystalball/frontend_fixtures_mapping.json + JEST_MATCHING_TEST_FILES_PATH: jest/matching_test_files.txt GITLAB_WORKHORSE_FOLDER: "gitlab-workhorse" JOB_METRICS_FILE_PATH: "${CI_PROJECT_DIR}/tmp/job-metrics.json" KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.json diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index c495e416ad8..a2c98df39a1 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -1412,6 +1412,7 @@ lib/gitlab/checks/ /app/views/user_settings/passwords/ /app/views/user_settings/personal_access_tokens/index.html.haml /app/views/user_settings/user_settings/authentication_log.haml +/app/workers/authn/ /app/workers/personal_access_tokens/ /app/workers/resource_access_tokens/ /config/initializers/01_secret_token.rb diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 5ac339bdce0..60a2d817683 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -641,6 +641,7 @@ .code-rest-api-patterns: &code-rest-api-patterns - "{,ee/,jh/}lib/{,ee/,jh/}api/**/*" + - "{,ee/,jh/}app/policies/**/*" # Auto-generated files - "doc/api/openapi/openapi_v2.yaml" diff --git a/.rubocop.yml b/.rubocop.yml index e67a5022d1d..a3b7c182fe1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1061,13 +1061,6 @@ Migration/AsyncPostMigrateOnly: - db/**/*.rb EnforcedSince: 20240115115029 -Migration/PreventEnablingLockRetriesForTransactionalMigrations: - Enabled: true - Include: - - '{,ee/,jh/}db/**/*.rb' - Exclude: - - '{,ee/,jh/}db/click_house/**/*.rb' - Gitlab/RailsLogger: Exclude: - 'spec/**/*.rb' diff --git a/.rubocop_todo/internal_affairs/cop_description_with_example.yml b/.rubocop_todo/internal_affairs/cop_description_with_example.yml index c086b299bcc..4efc2d271ef 100644 --- a/.rubocop_todo/internal_affairs/cop_description_with_example.yml +++ b/.rubocop_todo/internal_affairs/cop_description_with_example.yml @@ -92,7 +92,6 @@ InternalAffairs/CopDescriptionWithExample: - 'rubocop/cop/migration/migration_with_milestone.rb' - 'rubocop/cop/migration/prevent_adding_columns.rb' - 'rubocop/cop/migration/prevent_feature_flags_usage.rb' - - 'rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb' - 'rubocop/cop/migration/prevent_index_creation.rb' - 'rubocop/cop/migration/prevent_strings.rb' - 'rubocop/cop/migration/refer_to_index_by_name.rb' diff --git a/.rubocop_todo/internal_affairs/node_matcher_directive.yml b/.rubocop_todo/internal_affairs/node_matcher_directive.yml index 12f1ee15f1d..c81442b06e5 100644 --- a/.rubocop_todo/internal_affairs/node_matcher_directive.yml +++ b/.rubocop_todo/internal_affairs/node_matcher_directive.yml @@ -14,7 +14,6 @@ InternalAffairs/NodeMatcherDirective: - 'rubocop/cop/migration/ensure_factory_for_table.rb' - 'rubocop/cop/migration/migration_record.rb' - 'rubocop/cop/migration/migration_with_milestone.rb' - - 'rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb' - 'rubocop/cop/migration/prevent_index_creation.rb' - 'rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction.rb' - 'rubocop/cop/migration/prevent_strings.rb' diff --git a/.rubocop_todo/internal_affairs/redundant_message_argument.yml b/.rubocop_todo/internal_affairs/redundant_message_argument.yml index 424762d8e49..4f69368ced6 100644 --- a/.rubocop_todo/internal_affairs/redundant_message_argument.yml +++ b/.rubocop_todo/internal_affairs/redundant_message_argument.yml @@ -11,7 +11,6 @@ InternalAffairs/RedundantMessageArgument: - 'rubocop/cop/migration/async_post_migrate_only.rb' - 'rubocop/cop/migration/batch_migrations_post_only.rb' - 'rubocop/cop/migration/migration_with_milestone.rb' - - 'rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb' - 'rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction.rb' - 'rubocop/cop/migration/schema_addition_methods_no_post.rb' - 'rubocop/cop/redis_queue_usage.rb' diff --git a/.rubocop_todo/internal_affairs/useless_message_assertion.yml b/.rubocop_todo/internal_affairs/useless_message_assertion.yml index 43ef31a850a..75c5b8353ee 100644 --- a/.rubocop_todo/internal_affairs/useless_message_assertion.yml +++ b/.rubocop_todo/internal_affairs/useless_message_assertion.yml @@ -23,7 +23,6 @@ InternalAffairs/UselessMessageAssertion: - 'spec/rubocop/cop/migration/avoid_finalize_background_migration_spec.rb' - 'spec/rubocop/cop/migration/batched_migration_base_class_spec.rb' - 'spec/rubocop/cop/migration/migration_with_milestone_spec.rb' - - 'spec/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction_spec.rb' - 'spec/rubocop/cop/migration/prevent_single_statement_with_disable_ddl_transaction_spec.rb' - 'spec/rubocop/cop/migration/schema_addition_methods_no_post_spec.rb' - 'spec/rubocop/cop/performance/active_record_subtransaction_methods_spec.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 9aa833e1a09..1e4660f846c 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -2336,7 +2336,6 @@ Layout/LineLength: - 'rubocop/cop/inject_enterprise_edition_module.rb' - 'rubocop/cop/migration/add_limit_to_text_columns.rb' - 'rubocop/cop/migration/add_reference.rb' - - 'rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb' - 'rubocop/cop/migration/with_lock_retries_disallowed_method.rb' - 'rubocop/cop/qa/selector_usage.rb' - 'rubocop/cop/rspec/top_level_describe_path.rb' diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index 1864fa72c70..0b353212f81 100644 --- a/.rubocop_todo/rspec/feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -3462,7 +3462,6 @@ RSpec/FeatureCategory: - 'spec/rubocop/cop/migration/datetime_spec.rb' - 'spec/rubocop/cop/migration/drop_table_spec.rb' - 'spec/rubocop/cop/migration/migration_record_spec.rb' - - 'spec/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction_spec.rb' - 'spec/rubocop/cop/migration/prevent_strings_spec.rb' - 'spec/rubocop/cop/migration/refer_to_index_by_name_spec.rb' - 'spec/rubocop/cop/migration/remove_column_spec.rb' diff --git a/Gemfile.checksum b/Gemfile.checksum index 1a4f45f24db..136e370c058 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -718,7 +718,7 @@ {"name":"spring","version":"4.3.0","platform":"ruby","checksum":"0aaaf3bcce38e8528275854881d1922660d76cbd19a9a3af4a419d95b7fe7122"}, {"name":"spring-commands-rspec","version":"1.0.4","platform":"ruby","checksum":"6202e54fa4767452e3641461a83347645af478bf45dddcca9737b43af0dd1a2c"}, {"name":"sprite-factory","version":"1.7.1","platform":"ruby","checksum":"5586524a1aec003241f1abc6852b61433e988aba5ee2b55f906387bf49b01ba2"}, -{"name":"sprockets","version":"3.7.2","platform":"ruby","checksum":"5ea1d7facd09203c1aa196afd6178208cd25abdbcc2a9978810a2f0754e152a0"}, +{"name":"sprockets","version":"3.7.5","platform":"ruby","checksum":"72c20f256548f8a37fe7db41d96be86c3262fddaf4ebe9d69ec8317394fed383"}, {"name":"sprockets-rails","version":"3.5.2","platform":"ruby","checksum":"a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e"}, {"name":"ssh_data","version":"1.3.0","platform":"ruby","checksum":"ec7c1e95a3aebeee412147998f4c147b4b05da6ed0aafda6083f9449318eaac0"}, {"name":"ssrf_filter","version":"1.0.8","platform":"ruby","checksum":"03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db"}, diff --git a/Gemfile.lock b/Gemfile.lock index b65917a5f97..45d4ebc5980 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1849,7 +1849,8 @@ GEM spring-commands-rspec (1.0.4) spring (>= 0.9.1) sprite-factory (1.7.1) - sprockets (3.7.2) + sprockets (3.7.5) + base64 concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.5.2) diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index 98483921c7f..8bc53dcdb24 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -718,7 +718,7 @@ {"name":"spring","version":"4.3.0","platform":"ruby","checksum":"0aaaf3bcce38e8528275854881d1922660d76cbd19a9a3af4a419d95b7fe7122"}, {"name":"spring-commands-rspec","version":"1.0.4","platform":"ruby","checksum":"6202e54fa4767452e3641461a83347645af478bf45dddcca9737b43af0dd1a2c"}, {"name":"sprite-factory","version":"1.7.1","platform":"ruby","checksum":"5586524a1aec003241f1abc6852b61433e988aba5ee2b55f906387bf49b01ba2"}, -{"name":"sprockets","version":"3.7.2","platform":"ruby","checksum":"5ea1d7facd09203c1aa196afd6178208cd25abdbcc2a9978810a2f0754e152a0"}, +{"name":"sprockets","version":"3.7.5","platform":"ruby","checksum":"72c20f256548f8a37fe7db41d96be86c3262fddaf4ebe9d69ec8317394fed383"}, {"name":"sprockets-rails","version":"3.5.2","platform":"ruby","checksum":"a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e"}, {"name":"ssh_data","version":"1.3.0","platform":"ruby","checksum":"ec7c1e95a3aebeee412147998f4c147b4b05da6ed0aafda6083f9449318eaac0"}, {"name":"ssrf_filter","version":"1.0.8","platform":"ruby","checksum":"03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index 3d1d266f5af..ca67c51fcbc 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -1843,7 +1843,8 @@ GEM spring-commands-rspec (1.0.4) spring (>= 0.9.1) sprite-factory (1.7.1) - sprockets (3.7.2) + sprockets (3.7.5) + base64 concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.5.2) diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index e5508fb3ce5..1e80e25d0b9 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -877,6 +877,7 @@ function handleContinueList(e, textArea) { function handleContinueIndentedText(e, textArea) { if (!gon.features?.continueIndentedText) return; + if (!gon.markdown_maintain_indentation) return; if (!shouldHandleIndentation(e, textArea)) { return; diff --git a/app/assets/javascripts/members/components/modals/leave_modal.vue b/app/assets/javascripts/members/components/modals/leave_modal.vue index 0f76cb6e9d8..40806337383 100644 --- a/app/assets/javascripts/members/components/modals/leave_modal.vue +++ b/app/assets/javascripts/members/components/modals/leave_modal.vue @@ -27,7 +27,7 @@ export default { 'Members|You cannot remove yourself from a personal project.', ), preventedBodyGroupMemberModelType: s__( - 'Members|A group must have at least one owner. To leave this group, assign a new owner.', + 'Members|Groups require a human Owner. Assign another human user as Owner to leave.', ), }, components: { GlModal, GlForm, GlSprintf, UserDeletionObstaclesList }, diff --git a/app/assets/javascripts/todos/components/todo_item.vue b/app/assets/javascripts/todos/components/todo_item.vue index 0b88220e458..95af2685c58 100644 --- a/app/assets/javascripts/todos/components/todo_item.vue +++ b/app/assets/javascripts/todos/components/todo_item.vue @@ -1,24 +1,19 @@ diff --git a/app/assets/javascripts/todos/components/todo_item_timestamp.vue b/app/assets/javascripts/todos/components/todo_item_timestamp.vue index 6a9df01d0bf..52dfa6890b9 100644 --- a/app/assets/javascripts/todos/components/todo_item_timestamp.vue +++ b/app/assets/javascripts/todos/components/todo_item_timestamp.vue @@ -52,7 +52,9 @@ export default { diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 7bcf7e17af6..648d690d4ad 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -37,8 +37,6 @@ module WikiActions push_frontend_feature_flag(:glql_work_items, container) push_force_frontend_feature_flag(:glql_integration, !!container&.glql_integration_feature_flag_enabled?) push_force_frontend_feature_flag(:glql_load_on_click, !!container&.glql_load_on_click_feature_flag_enabled?) - push_force_frontend_feature_flag(:continue_indented_text, - !!container&.continue_indented_text_feature_flag_enabled?) end before_action only: [:show, :edit, :update] do diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index 3ff428b6f19..72b2647c948 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -11,7 +11,6 @@ class Groups::BoardsController < Groups::ApplicationController push_force_frontend_feature_flag(:work_items_beta, !!group&.work_items_beta_feature_flag_enabled?) push_frontend_feature_flag(:notifications_todos_buttons, current_user) push_force_frontend_feature_flag(:glql_integration, !!group&.glql_integration_feature_flag_enabled?) - push_force_frontend_feature_flag(:continue_indented_text, !!group&.continue_indented_text_feature_flag_enabled?) push_frontend_feature_flag(:work_item_status_feature_flag, group&.root_ancestor) end diff --git a/app/controllers/groups/work_items_controller.rb b/app/controllers/groups/work_items_controller.rb index 476a1c40b6b..9d002e46a61 100644 --- a/app/controllers/groups/work_items_controller.rb +++ b/app/controllers/groups/work_items_controller.rb @@ -13,7 +13,6 @@ module Groups !!group&.create_group_level_work_items_feature_flag_enabled?) push_force_frontend_feature_flag(:glql_integration, !!group&.glql_integration_feature_flag_enabled?) push_force_frontend_feature_flag(:glql_load_on_click, !!group&.glql_load_on_click_feature_flag_enabled?) - push_force_frontend_feature_flag(:continue_indented_text, !!group&.continue_indented_text_feature_flag_enabled?) push_frontend_feature_flag(:issues_list_drawer, group) push_frontend_feature_flag(:work_item_status_feature_flag, group&.root_ancestor) push_frontend_feature_flag(:work_item_planning_view, group) diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 118605d9011..0c9b9fe5dc6 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -64,6 +64,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController :keyboard_shortcuts_enabled, :markdown_surround_selection, :markdown_automatic_lists, + :markdown_maintain_indentation, :use_new_navigation, :enabled_following, :use_work_items_view, diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 180397058e9..3e56acd18ce 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -11,7 +11,6 @@ class Projects::BoardsController < Projects::ApplicationController push_force_frontend_feature_flag(:work_items_beta, !!project&.work_items_beta_feature_flag_enabled?) push_frontend_feature_flag(:notifications_todos_buttons, current_user) push_force_frontend_feature_flag(:glql_integration, !!project&.glql_integration_feature_flag_enabled?) - push_force_frontend_feature_flag(:continue_indented_text, !!project&.continue_indented_text_feature_flag_enabled?) push_frontend_feature_flag(:work_item_status_feature_flag, project&.root_ancestor) end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 5d829a7f6f5..df7daf9e1ea 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -56,7 +56,6 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:work_item_planning_view, project&.group) push_force_frontend_feature_flag(:glql_integration, !!project&.glql_integration_feature_flag_enabled?) push_force_frontend_feature_flag(:glql_load_on_click, !!project&.glql_load_on_click_feature_flag_enabled?) - push_force_frontend_feature_flag(:continue_indented_text, !!project&.continue_indented_text_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_beta, !!project&.work_items_beta_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_alpha, !!project&.work_items_alpha_feature_flag_enabled?) push_frontend_feature_flag(:work_item_view_for_issues, project&.group) diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index d379dce5023..921a5f68bb7 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -10,7 +10,6 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont before_action do push_force_frontend_feature_flag(:glql_integration, !!project&.glql_integration_feature_flag_enabled?) push_force_frontend_feature_flag(:glql_load_on_click, !!project&.glql_load_on_click_feature_flag_enabled?) - push_force_frontend_feature_flag(:continue_indented_text, !!project&.continue_indented_text_feature_flag_enabled?) end private diff --git a/app/controllers/projects/work_items_controller.rb b/app/controllers/projects/work_items_controller.rb index 37f13ef0a5c..732d4384de1 100644 --- a/app/controllers/projects/work_items_controller.rb +++ b/app/controllers/projects/work_items_controller.rb @@ -14,7 +14,6 @@ class Projects::WorkItemsController < Projects::ApplicationController push_force_frontend_feature_flag(:work_items_alpha, !!project&.work_items_alpha_feature_flag_enabled?) push_force_frontend_feature_flag(:glql_integration, !!project&.glql_integration_feature_flag_enabled?) push_force_frontend_feature_flag(:glql_load_on_click, !!project&.glql_load_on_click_feature_flag_enabled?) - push_force_frontend_feature_flag(:continue_indented_text, !!project&.continue_indented_text_feature_flag_enabled?) push_frontend_feature_flag(:work_item_status_feature_flag, project&.root_ancestor) push_frontend_feature_flag(:work_item_planning_view, project&.group) push_force_frontend_feature_flag(:work_items_bulk_edit, project&.work_items_bulk_edit_feature_flag_enabled?) diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 37439a4eb43..56dd3848a2f 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -61,11 +61,11 @@ module Ci end event :start_cancel do - transition CANCELABLE_STATUSES.map(&:to_sym) + [:manual] => :canceling + transition CANCELABLE_STATUSES.map(&:to_sym) => :canceling end event :finish_cancel do - transition CANCELABLE_STATUSES.map(&:to_sym) + [:manual, :canceling] => :canceled + transition CANCELABLE_STATUSES.map(&:to_sym) + [:canceling] => :canceled end event :success do diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 1d306546692..58de029c172 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -35,7 +35,6 @@ module Ci DEFAULT_CONFIG_PATH = '.gitlab-ci.yml' - CANCELABLE_STATUSES = (Ci::HasStatus::CANCELABLE_STATUSES + ['manual']).freeze UNLOCKABLE_STATUSES = (Ci::Pipeline.completed_statuses + [:manual]).freeze # UI only shows 100+. TODO: pass constant to UI for SSoT COUNT_FAILED_JOBS_LIMIT = 101 diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 376b93f44c5..32efa5ac664 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -175,7 +175,7 @@ class CommitStatus < Ci::ApplicationRecord event :cancel do transition running: :canceling, if: :supports_canceling? - transition CANCELABLE_STATUSES.map(&:to_sym) + [:manual] => :canceled + transition CANCELABLE_STATUSES.map(&:to_sym) => :canceled end event :force_cancel do diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb index fac19802166..b9ab3507e11 100644 --- a/app/models/concerns/ci/has_status.rb +++ b/app/models/concerns/ci/has_status.rb @@ -45,7 +45,7 @@ module Ci IGNORED_STATUSES = %w[manual].to_set.freeze EXECUTING_STATUSES = %w[running canceling].freeze ALIVE_STATUSES = ORDERED_STATUSES - COMPLETED_STATUSES - BLOCKED_STATUS - CANCELABLE_STATUSES = (ALIVE_STATUSES + ['scheduled'] - ['canceling']).freeze + CANCELABLE_STATUSES = (ORDERED_STATUSES - COMPLETED_STATUSES - ['canceling']).freeze STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, failed: 4, canceled: 5, skipped: 6, manual: 7, scheduled: 8, preparing: 9, waiting_for_resource: 10, diff --git a/app/models/group.rb b/app/models/group.rb index 1f64be67632..b907c19ba92 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -715,12 +715,12 @@ class Group < Namespace def last_owner?(user) return false unless user - all_owners = member_owners_excluding_project_bots + all_owners = member_owners_excluding_project_bots_and_service_accounts last_owner_in_list?(user, all_owners) end # This is used in BillableMember Entity to - # avoid multiple "member_owners_excluding_project_bots" calls + # avoid multiple "member_owners_excluding_project_bots_and_service_accounts" calls # for each billable members def last_owner_in_list?(user, all_owners) return false unless user @@ -730,18 +730,18 @@ class Group < Namespace # Excludes non-direct owners for top-level group # Excludes project_bots - def member_owners_excluding_project_bots - members_from_hiearchy = if root? - members.non_minimal_access.without_invites_and_requests - else - members_with_parents(only_active_users: false) - end + # Excludes service accounts + def member_owners_excluding_project_bots_and_service_accounts + members_from_hierarchy = if root? + members.non_minimal_access.without_invites_and_requests + else + members_with_parents(only_active_users: false) + end owners = [] - - members_from_hiearchy.all_owners.non_invite.each_batch do |relation| + members_from_hierarchy.all_owners.non_invite.each_batch do |relation| owners += relation.preload(:user, :source).load.reject do |member| - member.user.nil? || member.user.project_bot? + member.user.nil? || member.user.project_bot? || member.user.service_account? end end @@ -1108,10 +1108,6 @@ class Group < Namespace licensed_feature_available?(:work_item_status) end - def continue_indented_text_feature_flag_enabled? - feature_flag_enabled_for_self_or_ancestor?(:continue_indented_text, type: :wip) - end - def glql_integration_feature_flag_enabled? feature_flag_enabled_for_self_or_ancestor?(:glql_integration) end diff --git a/app/models/issue.rb b/app/models/issue.rb index 7f248861aea..af402adc696 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -419,11 +419,7 @@ class Issue < ApplicationRecord end def self.alternative_reference_prefix_with_postfix - if Feature.enabled?(:extensible_reference_filters, Feature.current_request) - '[issue:' - else - '' - end + '[issue:' end def self.reference_postfix diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 0f59cbafdcf..4a56b51451d 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -62,7 +62,7 @@ class GroupMember < Member return false unless access_level == Gitlab::Access::OWNER return last_owner unless last_owner.nil? - owners = group.member_owners_excluding_project_bots + owners = group.member_owners_excluding_project_bots_and_service_accounts owners.reject! do |member| member.group == group && member.user_id == user_id diff --git a/app/models/members/last_group_owner_assigner.rb b/app/models/members/last_group_owner_assigner.rb index 707cd7bf31c..028ffaafe3d 100644 --- a/app/models/members/last_group_owner_assigner.rb +++ b/app/models/members/last_group_owner_assigner.rb @@ -30,6 +30,6 @@ class LastGroupOwnerAssigner end def owners - @owners ||= group.member_owners_excluding_project_bots + @owners ||= group.member_owners_excluding_project_bots_and_service_accounts end end diff --git a/app/models/project.rb b/app/models/project.rb index 218f8e926f8..8a9106ca9bd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3465,10 +3465,6 @@ class Project < ApplicationRecord group&.work_items_bulk_edit_feature_flag_enabled? || Feature.enabled?(:work_items_bulk_edit, self, type: :wip) end - def continue_indented_text_feature_flag_enabled? - group&.continue_indented_text_feature_flag_enabled? || Feature.enabled?(:continue_indented_text, self, type: :wip) - end - def enqueue_record_project_target_platforms return unless Gitlab.com? diff --git a/app/models/user.rb b/app/models/user.rb index 40947cce5b1..9572c32badc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -444,6 +444,7 @@ class User < ApplicationRecord :render_whitespace_in_code, :render_whitespace_in_code=, :markdown_surround_selection, :markdown_surround_selection=, :markdown_automatic_lists, :markdown_automatic_lists=, + :markdown_maintain_indentation, :markdown_maintain_indentation=, :diffs_deletion_color, :diffs_deletion_color=, :diffs_addition_color, :diffs_addition_color=, :use_new_navigation, :use_new_navigation=, diff --git a/app/models/work_item.rb b/app/models/work_item.rb index ce85b1482b4..54b02a2e6ab 100644 --- a/app/models/work_item.rb +++ b/app/models/work_item.rb @@ -82,11 +82,7 @@ class WorkItem < Issue end def alternative_reference_prefix_with_postfix - if Feature.enabled?(:extensible_reference_filters, Feature.current_request) - '[work_item:' - else - '' - end + '[work_item:' end def reference_pattern diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 08c9f3a0faa..9d150cfa87f 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -73,6 +73,7 @@ module Groups end transfer_labels + transfer_status_data(old_root_ancestor_id) remove_paid_features_for_projects(old_root_ancestor_id) post_update_hooks(@updated_project_ids, old_root_ancestor_id) propagate_integrations @@ -89,6 +90,9 @@ module Groups end end + # Overridden in EE + def transfer_status_data(old_root_ancestor_id); end + # Overridden in EE def post_update_hooks(updated_project_ids, old_root_ancestor_id) refresh_project_authorizations diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 1563a818166..caf63a659a8 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -144,7 +144,11 @@ = f.gitlab_ui_checkbox_component :markdown_automatic_lists, s_('Preferences|Automatically add new list items'), help_text: html_escape(s_('Preferences|When you type in a description or comment box, pressing %{kbdOpen}Enter%{kbdClose} in a list adds a new item below.')) % { kbdOpen: ''.html_safe, kbdClose: ''.html_safe } - + - if Feature.enabled?(:continue_indented_text, current_user) + .form-group + = f.gitlab_ui_checkbox_component :markdown_maintain_indentation, + s_('Preferences|Maintain indentation'), + help_text: safe_format(s_('Preferences|When you type in a description or comment box, pressing %{kbdOpen}Enter%{kbdClose} creates a new line indented the same as the previous line.'), tag_pair(tag.kbd, :kbdOpen, :kbdClose)) .form-group = f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold' = f.number_field :tab_width, diff --git a/config/events/api_request_from_runner.yml b/config/events/api_request_from_runner.yml index 1d16e000d62..069feda8e6f 100644 --- a/config/events/api_request_from_runner.yml +++ b/config/events/api_request_from_runner.yml @@ -15,6 +15,7 @@ additional_properties: product_group: authorization product_categories: - permissions +- runner milestone: '18.1' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189430 tiers: diff --git a/config/events/click_create_group_runner_button.yml b/config/events/click_create_group_runner_button.yml index e4338bdf776..74807ce66f1 100644 --- a/config/events/click_create_group_runner_button.yml +++ b/config/events/click_create_group_runner_button.yml @@ -6,6 +6,8 @@ identifiers: - namespace - user product_group: runner +product_categories: + - fleet_visibility milestone: '16.10' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146063 tiers: diff --git a/config/events/click_create_project_runner_button.yml b/config/events/click_create_project_runner_button.yml index e11f1891f84..4c4da2ef569 100644 --- a/config/events/click_create_project_runner_button.yml +++ b/config/events/click_create_project_runner_button.yml @@ -7,6 +7,8 @@ identifiers: - namespace - user product_group: runner +product_categories: + - fleet_visibility milestone: '16.10' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146063 tiers: diff --git a/config/events/click_view_all_link_in_pipeline_analytics.yml b/config/events/click_view_all_link_in_pipeline_analytics.yml index d6556a4a929..6fae3ea0354 100644 --- a/config/events/click_view_all_link_in_pipeline_analytics.yml +++ b/config/events/click_view_all_link_in_pipeline_analytics.yml @@ -7,6 +7,8 @@ identifiers: - namespace - user product_group: runner +product_categories: +- fleet_visibility milestone: '17.2' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/158957 tiers: diff --git a/config/events/click_view_runners_button_in_new_group_runner_form.yml b/config/events/click_view_runners_button_in_new_group_runner_form.yml index 11ab0a308f4..e37cda615e1 100644 --- a/config/events/click_view_runners_button_in_new_group_runner_form.yml +++ b/config/events/click_view_runners_button_in_new_group_runner_form.yml @@ -6,6 +6,8 @@ identifiers: - namespace - user product_group: runner +product_categories: + - fleet_visibility milestone: '16.10' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146063 tiers: diff --git a/config/events/click_view_runners_button_in_new_project_runner_form.yml b/config/events/click_view_runners_button_in_new_project_runner_form.yml index e43b6e7a7ef..200cc52ea0a 100644 --- a/config/events/click_view_runners_button_in_new_project_runner_form.yml +++ b/config/events/click_view_runners_button_in_new_project_runner_form.yml @@ -7,6 +7,8 @@ identifiers: - namespace - user product_group: runner +product_categories: + - fleet_visibility milestone: '16.10' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146063 tiers: diff --git a/config/events/set_runner_maintenance_note.yml b/config/events/set_runner_maintenance_note.yml index ee9b8863b7e..d4a2162f5ba 100644 --- a/config/events/set_runner_maintenance_note.yml +++ b/config/events/set_runner_maintenance_note.yml @@ -10,6 +10,8 @@ additional_properties: label: description: The runner type, which can be `project_type`, `group_type`, or `instance_type` product_group: runner +product_categories: +- fleet_visibility milestone: '17.6' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/170483 tiers: diff --git a/config/events/view_admin_runners_pageload.yml b/config/events/view_admin_runners_pageload.yml index 4e665a544fd..ec1dce0034d 100644 --- a/config/events/view_admin_runners_pageload.yml +++ b/config/events/view_admin_runners_pageload.yml @@ -5,6 +5,8 @@ action: view_admin_runners_pageload identifiers: - user product_group: personal_productivity +product_categories: +- fleet_visibility milestone: '17.2' introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156812 tiers: diff --git a/config/feature_flags/beta/directory_code_dropdown_updates.yml b/config/feature_flags/beta/directory_code_dropdown_updates.yml index a4e2e4e2eac..96f239c3df4 100644 --- a/config/feature_flags/beta/directory_code_dropdown_updates.yml +++ b/config/feature_flags/beta/directory_code_dropdown_updates.yml @@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/514750 milestone: '17.9' group: group::source code type: beta -default_enabled: true +default_enabled: false diff --git a/config/feature_flags/beta/stream_audit_events_remote_ip_proxy_protocol.yml b/config/feature_flags/beta/stream_audit_events_remote_ip_proxy_protocol.yml deleted file mode 100644 index e39908b5422..00000000000 --- a/config/feature_flags/beta/stream_audit_events_remote_ip_proxy_protocol.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: stream_audit_events_remote_ip_proxy_protocol -description: Fixes remote IP in stream audit events of Git over SSH -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/378590 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/191408 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/543033 -milestone: '18.1' -group: group::source code -type: beta -default_enabled: true diff --git a/config/feature_flags/gitlab_com_derisk/keyset_paginate_exported_merge_requests.yml b/config/feature_flags/gitlab_com_derisk/keyset_paginate_exported_merge_requests.yml deleted file mode 100644 index 5d6a4c00617..00000000000 --- a/config/feature_flags/gitlab_com_derisk/keyset_paginate_exported_merge_requests.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: keyset_paginate_exported_merge_requests -description: Use keyset pagination for serializing MRs during file-based export -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/553170 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/196530 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/553171 -milestone: '18.2' -group: group::import -type: gitlab_com_derisk -default_enabled: false diff --git a/config/feature_flags/wip/continue_indented_text.yml b/config/feature_flags/wip/continue_indented_text.yml index f9c932d92da..edfa9d31641 100644 --- a/config/feature_flags/wip/continue_indented_text.yml +++ b/config/feature_flags/wip/continue_indented_text.yml @@ -1,7 +1,7 @@ --- name: continue_indented_text feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/23978 -milestone: '17.9' +milestone: '18.2' group: group::knowledge type: wip default_enabled: false diff --git a/config/feature_flags/wip/extensible_reference_filters.yml b/config/feature_flags/wip/extensible_reference_filters.yml deleted file mode 100644 index b4d676ed2ea..00000000000 --- a/config/feature_flags/wip/extensible_reference_filters.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: extensible_reference_filters -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352861 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181859 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/535018 -milestone: '18.1' -group: group::knowledge -type: wip -default_enabled: false diff --git a/config/initializers/sprockets_patch.rb b/config/initializers/sprockets_patch.rb index b14b2edeef3..9c76e38ba42 100644 --- a/config/initializers/sprockets_patch.rb +++ b/config/initializers/sprockets_patch.rb @@ -8,7 +8,7 @@ require 'sprockets/utils' -unless Gem::Version.new(Sprockets::VERSION) == Gem::Version.new('3.7.2') +if Gem::Version.new(Sprockets::VERSION) >= Gem::Version.new('4.2.0') raise 'New version of Sprockets detected. This patch can likely be removed.' end diff --git a/config/metrics/counts_28d/count_distinct_user_id_from_click_create_group_runner_button_monthly.yml b/config/metrics/counts_28d/count_distinct_user_id_from_click_create_group_runner_button_monthly.yml index 995ad026390..6c95795a3b1 100644 --- a/config/metrics/counts_28d/count_distinct_user_id_from_click_create_group_runner_button_monthly.yml +++ b/config/metrics/counts_28d/count_distinct_user_id_from_click_create_group_runner_button_monthly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_create_group_runner_button_monthly description: Monthly count of unique users who clicked the 'Create runner' button in a group product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_28d/count_distinct_user_id_from_click_create_project_runner_button_monthly.yml b/config/metrics/counts_28d/count_distinct_user_id_from_click_create_project_runner_button_monthly.yml index 1c23c883396..80d7d18c8c7 100644 --- a/config/metrics/counts_28d/count_distinct_user_id_from_click_create_project_runner_button_monthly.yml +++ b/config/metrics/counts_28d/count_distinct_user_id_from_click_create_project_runner_button_monthly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_create_project_runner_button_monthly description: Monthly count of unique users who clicked the 'Create runner' button in a project product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_28d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_monthly.yml b/config/metrics/counts_28d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_monthly.yml index 789d4e7d1be..8db9fb3ce6f 100644 --- a/config/metrics/counts_28d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_monthly.yml +++ b/config/metrics/counts_28d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_monthly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_monthly description: Monthly count of unique users who clicked on the View All link on the Pipeline Analytics page product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_monthly.yml b/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_monthly.yml index cf0add35b14..feb3df1ad9d 100644 --- a/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_monthly.yml +++ b/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_monthly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_monthly description: Monthly count of unique users who clicked the 'View Runners' button after creating a group runner product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_monthly.yml b/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_monthly.yml index 7b8c318e631..b0836fc0052 100644 --- a/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_monthly.yml +++ b/config/metrics/counts_28d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_monthly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_monthly description: Monthly count of unique users who clicked the 'View Runners' button after creating a project runner product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_28d/count_distinct_user_id_from_view_admin_runners_pageload_monthly.yml b/config/metrics/counts_28d/count_distinct_user_id_from_view_admin_runners_pageload_monthly.yml index 4d81e37c735..412400bdcff 100644 --- a/config/metrics/counts_28d/count_distinct_user_id_from_view_admin_runners_pageload_monthly.yml +++ b/config/metrics/counts_28d/count_distinct_user_id_from_view_admin_runners_pageload_monthly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_runners_pageload_monthly description: Monthly count of unique users who visit the admin runners page product_group: personal_productivity +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_28d/count_total_runner_created_with_maintenance_note_monthly.yml b/config/metrics/counts_28d/count_total_runner_created_with_maintenance_note_monthly.yml index e689a13f501..928f7928912 100644 --- a/config/metrics/counts_28d/count_total_runner_created_with_maintenance_note_monthly.yml +++ b/config/metrics/counts_28d/count_total_runner_created_with_maintenance_note_monthly.yml @@ -2,6 +2,8 @@ key_path: counts.count_total_set_runner_maintenance_note_monthly description: Monthly count of assignments of maintenance notes to runners product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_28d/count_total_view_admin_runners_pageload_monthly.yml b/config/metrics/counts_28d/count_total_view_admin_runners_pageload_monthly.yml index 7602e52a2d7..a52cc05c7dd 100644 --- a/config/metrics/counts_28d/count_total_view_admin_runners_pageload_monthly.yml +++ b/config/metrics/counts_28d/count_total_view_admin_runners_pageload_monthly.yml @@ -2,6 +2,8 @@ key_path: counts.count_total_view_admin_runners_pageload_monthly description: Monthly count of total users who visit the admin runners page product_group: personal_productivity +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_distinct_user_id_from_click_create_group_runner_button_weekly.yml b/config/metrics/counts_7d/count_distinct_user_id_from_click_create_group_runner_button_weekly.yml index 8da501ebcef..d65467c23b9 100644 --- a/config/metrics/counts_7d/count_distinct_user_id_from_click_create_group_runner_button_weekly.yml +++ b/config/metrics/counts_7d/count_distinct_user_id_from_click_create_group_runner_button_weekly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_create_group_runner_button_weekly description: Weekly count of unique users who clicked the 'Create runner' button in a group product_group: runner +product_categories: + - fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_distinct_user_id_from_click_create_project_runner_button_weekly.yml b/config/metrics/counts_7d/count_distinct_user_id_from_click_create_project_runner_button_weekly.yml index 502641ac830..cbb6af61e78 100644 --- a/config/metrics/counts_7d/count_distinct_user_id_from_click_create_project_runner_button_weekly.yml +++ b/config/metrics/counts_7d/count_distinct_user_id_from_click_create_project_runner_button_weekly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_create_project_runner_button_weekly description: Weekly count of unique users who clicked the 'Create runner' button in a project product_group: runner +product_categories: + - fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_weekly.yml b/config/metrics/counts_7d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_weekly.yml index 3edca4f3aa0..9718b746da8 100644 --- a/config/metrics/counts_7d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_weekly.yml +++ b/config/metrics/counts_7d/count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_weekly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_view_all_link_in_pipeline_analytics_weekly description: Weekly count of unique users who clicked on the View All link on the Pipeline Analytics page product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_weekly.yml b/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_weekly.yml index 6835175cdef..e922e1b84fa 100644 --- a/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_weekly.yml +++ b/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_weekly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_view_runners_button_in_new_group_runner_form_weekly description: Weekly count of unique users who clicked the 'View Runners' button after creating a group runner product_group: runner +product_categories: + - fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_weekly.yml b/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_weekly.yml index 3c95414256c..887578cb458 100644 --- a/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_weekly.yml +++ b/config/metrics/counts_7d/count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_weekly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_click_view_runners_button_in_new_project_runner_form_weekly description: Weekly count of unique users who clicked the 'View Runners' button after creating a project runner product_group: runner +product_categories: + - fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_distinct_user_id_from_view_admin_runners_pageload_weekly.yml b/config/metrics/counts_7d/count_distinct_user_id_from_view_admin_runners_pageload_weekly.yml index 8d88347f2da..59f8d9d25a3 100644 --- a/config/metrics/counts_7d/count_distinct_user_id_from_view_admin_runners_pageload_weekly.yml +++ b/config/metrics/counts_7d/count_distinct_user_id_from_view_admin_runners_pageload_weekly.yml @@ -2,6 +2,8 @@ key_path: redis_hll_counters.count_distinct_user_id_from_view_admin_runners_pageload_weekly description: Weekly count of unique users who visit the admin runners page product_group: personal_productivity +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_total_runner_created_with_maintenance_note_weekly.yml b/config/metrics/counts_7d/count_total_runner_created_with_maintenance_note_weekly.yml index 8adc7cd9394..27600440ce3 100644 --- a/config/metrics/counts_7d/count_total_runner_created_with_maintenance_note_weekly.yml +++ b/config/metrics/counts_7d/count_total_runner_created_with_maintenance_note_weekly.yml @@ -2,6 +2,8 @@ key_path: counts.count_total_set_runner_maintenance_note_weekly description: Weekly count of assignments of maintenance notes to runners product_group: runner +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_7d/count_total_view_admin_runners_pageload_weekly.yml b/config/metrics/counts_7d/count_total_view_admin_runners_pageload_weekly.yml index 3553a0935e5..8c3dfd3f7ea 100644 --- a/config/metrics/counts_7d/count_total_view_admin_runners_pageload_weekly.yml +++ b/config/metrics/counts_7d/count_total_view_admin_runners_pageload_weekly.yml @@ -2,6 +2,8 @@ key_path: counts.count_total_view_admin_runners_pageload_weekly description: Weekly count of total users who visit the admin runners page product_group: personal_productivity +product_categories: +- fleet_visibility performance_indicator_type: [] value_type: number status: active diff --git a/config/metrics/counts_all/20210216175520_ci_runners.yml b/config/metrics/counts_all/20210216175520_ci_runners.yml index e567ab310bc..d4c3ac7dcd8 100644 --- a/config/metrics/counts_all/20210216175520_ci_runners.yml +++ b/config/metrics/counts_all/20210216175520_ci_runners.yml @@ -5,6 +5,7 @@ description: Total configured Runners of all types product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active time_frame: all diff --git a/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml b/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml index 9d2cb602c1a..eb1c63bfa80 100644 --- a/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml +++ b/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml @@ -5,6 +5,7 @@ description: Total active Shared (Instance) Runners product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active milestone: "13.12" diff --git a/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml b/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml index 7f28238279c..caaffbf2ae0 100644 --- a/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml +++ b/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml @@ -5,6 +5,7 @@ description: Total active Group Runners product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active milestone: "13.12" diff --git a/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml b/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml index a82be2a98d9..b49a14f3013 100644 --- a/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml +++ b/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml @@ -5,6 +5,7 @@ description: Total active Specific (Project) Runners product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active milestone: "13.12" diff --git a/config/metrics/counts_all/20210502050942_ci_runners_online.yml b/config/metrics/counts_all/20210502050942_ci_runners_online.yml index c7c1654fa24..b5ef3ef15a6 100644 --- a/config/metrics/counts_all/20210502050942_ci_runners_online.yml +++ b/config/metrics/counts_all/20210502050942_ci_runners_online.yml @@ -5,6 +5,7 @@ description: Total online Runners of all types product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active milestone: "13.12" diff --git a/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml b/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml index ca801095c02..70f04dacf69 100644 --- a/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml +++ b/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml @@ -5,6 +5,7 @@ description: Total active and online Shared (Instance) Runners product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active milestone: "13.12" diff --git a/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml b/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml index a3fcebb6e2d..8cfadec29e1 100644 --- a/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml +++ b/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml @@ -5,6 +5,7 @@ description: Total active and online Group Runners product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active milestone: "13.12" diff --git a/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml b/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml index 4a6d2cfd5c9..e2d5458d617 100644 --- a/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml +++ b/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml @@ -5,6 +5,7 @@ description: Total active and online Specific (Project) Runners product_group: runner product_categories: - fleet_visibility +- runner value_type: number status: active milestone: "13.12" diff --git a/config/metrics/settings/20240425162824_instance_runner_registration_token_allowed.yml b/config/metrics/settings/20240425162824_instance_runner_registration_token_allowed.yml index 54d1c32afbb..3518fd150e7 100644 --- a/config/metrics/settings/20240425162824_instance_runner_registration_token_allowed.yml +++ b/config/metrics/settings/20240425162824_instance_runner_registration_token_allowed.yml @@ -2,6 +2,8 @@ key_path: settings.allow_runner_registration_token description: Whether runner registration tokens are allowed on the instance product_group: runner +product_categories: + - runner value_type: boolean status: active milestone: "17.0" diff --git a/config/metrics/settings/20250321144300_instance_runner_token_expiration_enabled.yml b/config/metrics/settings/20250321144300_instance_runner_token_expiration_enabled.yml index e53534a5187..1583963dd14 100644 --- a/config/metrics/settings/20250321144300_instance_runner_token_expiration_enabled.yml +++ b/config/metrics/settings/20250321144300_instance_runner_token_expiration_enabled.yml @@ -3,6 +3,8 @@ key_path: settings.instance_runner_token_expiration_enabled description: > Tracks whether an expiration interval is defined for instance runner authentication tokens product_group: runner +product_categories: + - runner value_type: boolean status: active milestone: "17.11" diff --git a/config/metrics/settings/20250321144301_group_runner_token_expiration_enabled.yml b/config/metrics/settings/20250321144301_group_runner_token_expiration_enabled.yml index 29e47dc3386..0cea3eb9ce3 100644 --- a/config/metrics/settings/20250321144301_group_runner_token_expiration_enabled.yml +++ b/config/metrics/settings/20250321144301_group_runner_token_expiration_enabled.yml @@ -3,6 +3,8 @@ key_path: settings.group_runner_token_expiration_enabled description: > Tracks whether an expiration interval is defined for group runner authentication tokens product_group: runner +product_categories: + - runner value_type: boolean status: active milestone: "17.11" diff --git a/config/metrics/settings/20250321144302_project_runner_token_expiration_enabled.yml b/config/metrics/settings/20250321144302_project_runner_token_expiration_enabled.yml index 312fe99d2a1..9d1afa79150 100644 --- a/config/metrics/settings/20250321144302_project_runner_token_expiration_enabled.yml +++ b/config/metrics/settings/20250321144302_project_runner_token_expiration_enabled.yml @@ -3,6 +3,8 @@ key_path: settings.project_runner_token_expiration_enabled description: > Tracks whether an expiration interval is defined for project runner authentication tokens product_group: runner +product_categories: + - runner value_type: boolean status: active milestone: "17.11" diff --git a/db/docs/batched_background_migrations/backfill_issue_assignees_namespace_id.yml b/db/docs/batched_background_migrations/backfill_issue_assignees_namespace_id.yml index 37b3580dd20..32e7942ebca 100644 --- a/db/docs/batched_background_migrations/backfill_issue_assignees_namespace_id.yml +++ b/db/docs/batched_background_migrations/backfill_issue_assignees_namespace_id.yml @@ -5,4 +5,4 @@ feature_category: team_planning introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174106 milestone: '17.10' queued_migration_version: 20250220131539 -finalized_by: # version of the migration that finalized this BBM +finalized_by: '20250708204640' diff --git a/db/migrate/20250604220725_add_markdown_maintain_indentation.rb b/db/migrate/20250604220725_add_markdown_maintain_indentation.rb new file mode 100644 index 00000000000..17a9e45b20a --- /dev/null +++ b/db/migrate/20250604220725_add_markdown_maintain_indentation.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddMarkdownMaintainIndentation < Gitlab::Database::Migration[2.3] + milestone '18.2' + + def up + add_column :user_preferences, :markdown_maintain_indentation, :boolean, default: false, null: false + end + + def down + remove_column :user_preferences, :markdown_maintain_indentation, :boolean + end +end diff --git a/db/post_migrate/20250708204640_finalize_backfill_issue_assignees_namespace_id.rb b/db/post_migrate/20250708204640_finalize_backfill_issue_assignees_namespace_id.rb new file mode 100644 index 00000000000..276a3eb95d6 --- /dev/null +++ b/db/post_migrate/20250708204640_finalize_backfill_issue_assignees_namespace_id.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class FinalizeBackfillIssueAssigneesNamespaceId < Gitlab::Database::Migration[2.3] + milestone '18.2' + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main_cell + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'BackfillIssueAssigneesNamespaceId', + table_name: :issue_assignees, + column_name: :issue_id, + job_arguments: [:namespace_id, :issues, :namespace_id, :issue_id], + finalize: true + ) + end + + def down; end +end diff --git a/db/schema_migrations/20250604220725 b/db/schema_migrations/20250604220725 new file mode 100644 index 00000000000..69dca048561 --- /dev/null +++ b/db/schema_migrations/20250604220725 @@ -0,0 +1 @@ +7140654d9f62a72e40808df6c28ec1e4dcb50b1a33e924c2c70b7e36cda583a8 \ No newline at end of file diff --git a/db/schema_migrations/20250708204640 b/db/schema_migrations/20250708204640 new file mode 100644 index 00000000000..8c6c28ae9fc --- /dev/null +++ b/db/schema_migrations/20250708204640 @@ -0,0 +1 @@ +84d12bf9c49331d3d2d0143fec3662050bb0433369f9212cb93d9cad37fe6492 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index b01b796ae75..f6a62d02ebb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -25020,6 +25020,7 @@ CREATE TABLE user_preferences ( extensions_marketplace_opt_in_url text, dark_color_scheme_id smallint, work_items_display_settings jsonb DEFAULT '{}'::jsonb NOT NULL, + markdown_maintain_indentation boolean DEFAULT false NOT NULL, default_duo_add_on_assignment_id bigint, CONSTRAINT check_1d670edc68 CHECK ((time_display_relative IS NOT NULL)), CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)), diff --git a/doc/administration/settings/vscode_extension_marketplace.md b/doc/administration/settings/vscode_extension_marketplace.md index 1c75e310295..9fa2a24c73e 100644 --- a/doc/administration/settings/vscode_extension_marketplace.md +++ b/doc/administration/settings/vscode_extension_marketplace.md @@ -2,7 +2,7 @@ stage: Create group: Remote Development info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments -description: Configure VS Code Extension Marketplace for features on the GitLab self-managed instance. +description: Configure VS Code Extension Marketplace for features on the GitLab Self-Managed instance. title: Configure VS Code Extension Marketplace --- @@ -20,7 +20,7 @@ feature across your GitLab instance and configure which extension registry your {{< alert type="note" >}} -To access the VS Code Extension Marketplace, your web browser must be able to access the `.cdn.web-ide.gitlab-static.net` assets host. This security requirement ensures that third-party extensions run in isolation, and cannot access your account. +To access the VS Code Extension Marketplace, your web browser must have access to the `.cdn.web-ide.gitlab-static.net` assets host. This security requirement ensures that third-party extensions run in isolation, and cannot access your account. {{< /alert >}} diff --git a/doc/api/groups.md b/doc/api/groups.md index 197d61dad4d..c342cd95ab5 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1472,6 +1472,26 @@ curl --request POST --header "PRIVATE-TOKEN: " \ ### Archive a group +{{< details >}} + +- Status: Experiment + +{{< /details >}} + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/481969) in GitLab 18.0 [with a flag](../administration/feature_flags/_index.md) named `archive_group`. Disabled by default. This feature is an [experiment](../policy/development_stages_support.md). + +{{< /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 >}} + Archive a group. Prerequisites: @@ -1552,6 +1572,26 @@ Example response: ### Unarchive a group +{{< details >}} + +- Status: Experiment + +{{< /details >}} + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/481969) in GitLab 18.0 [with a flag](../administration/feature_flags/_index.md) named `archive_group`. Disabled by default. This feature is an [experiment](../policy/development_stages_support.md). + +{{< /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 >}} + Unarchive a group. Prerequisites: diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index b8e849c9286..49aafacb1bb 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -242,8 +242,7 @@ Example of response ## Get variables for a pipeline -Get the variables of a pipeline. Does not include variables that come from a pipeline schedule. -For more information, see [issue 250850](https://gitlab.com/gitlab-org/gitlab/-/issues/250850). +Get the [pipeline variables](../ci/variables/_index.md#use-pipeline-variables) of a pipeline. ```plaintext GET /projects/:id/pipelines/:pipeline_id/variables diff --git a/doc/ci/jobs/job_control.md b/doc/ci/jobs/job_control.md index 9115fe5adcf..1f7639d5dbf 100644 --- a/doc/ci/jobs/job_control.md +++ b/doc/ci/jobs/job_control.md @@ -15,6 +15,7 @@ title: Control how jobs run Before a new pipeline starts, GitLab checks the pipeline configuration to determine which jobs can run in that pipeline. You can configure jobs to run depending on conditions like the value of variables or the pipeline type with [`rules`](job_rules.md). +When using job rules, learn how to [avoid duplicate pipelines](job_rules.md#avoid-duplicate-pipelines). To control pipeline creation, use [workflow:rules](../yaml/workflow.md). ## Create a job that must be run manually diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index bf310b45a4f..77c91027dd8 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -266,8 +266,8 @@ Other examples of review apps: Route maps let you navigate directly from source files to their corresponding public pages in the review app environment. This feature makes it easier to preview specific changes in your merge requests. -When configured, route maps enhance the review app experience by adding links to review app versions -of the changed files in: +When configured, route maps add contextual links that let you view the review app version of files +that match your mapping patterns. These links appear in: - The merge request widget. - Commit and file views. @@ -290,8 +290,8 @@ Each mapping in the route map follows this format: You can use two types of mapping: -- **Exact match**: String literals enclosed in single quotes -- **Pattern match**: Regular expressions enclosed in forward slashes +- Exact match: String literals enclosed in single quotes +- Pattern match: Regular expressions enclosed in forward slashes For pattern matching with regular expressions: @@ -332,25 +332,41 @@ In this example: ### View mapped pages -After you configure route maps, you can find links to the mapped pages after the next -review app deployment. +Use route maps to navigate directly from source files to their corresponding pages in your review app. -To view mapped pages: +Prerequisites: -- In the merge request widget, select **View app** to go to the environment URL set in the - `.gitlab-ci.yml` file. The list shows up to 5 matched items from the route map (with - filtering if more are available). +- You must have configured route maps in `.gitlab/route-map.yml`. +- A review app must be deployed for your branch or merge request. - ![Merge request widget with route maps showing matched items and filter bar](img/mr_widget_route_maps_v17_11.png) +To view mapped pages from the merge request widget: -For files in your route map: +1. In the merge request widget, select **View app**. + The dropdown list shows up to 5 mapped pages (with filtering if more are available). -1. In the **Changes** tab of your merge request, select **View file @ [commit]** next to a file. -1. On the file's page, look for **View on [deployment-URL]** ({{< icon name="external-link" >}}) in the upper-right corner. +![Merge request widget with route maps showing matched items and filter bar.](img/mr_widget_route_maps_v17_11.png) -For merge requests using merged results pipelines: +To view a mapped page from a file: -1. Go to the **Pipelines** tab in your merge request. -1. Select the commit for the latest pipeline. -1. Select **View file @ [commit]** next to a file. -1. On the file's page, select **View on [deployment-URL]** ({{< icon name="external-link" >}}) in the upper-right corner. +1. Go to a file that matches your route map using one of these methods: + - From a merge request: In the **Changes** tab, select **View file @ [commit]**. + - From a commit page: Select the filename. + - From a comparison: When comparing revisions, select the filename. +1. On the file's page, select **View on [environment-name]** ({{< icon name="external-link" >}}) in the upper-right corner. + +To view mapped pages from a commit: + +1. Go to a commit that has a review app deployment: + - For branch pipelines: Select **Code > Commits** and select a commit with a pipeline badge. + - For merge request pipelines: In your merge request, select the **Commits** tab and select a commit. + - For merged results pipelines: In your merge request, select the **Pipelines** tab and select the pipeline commit. +1. Select the review app icon ({{< icon name="external-link" >}}) next to a filename that matches your route map. + The icon opens the corresponding page in your review app. + +{{< alert type="note" >}} + +Merged results pipelines create an internal commit that merges your branch with the target branch. +To access review app links for these pipelines, use the commit from the **Pipelines** tab, +not the **Commits** tab. + +{{< /alert >}} diff --git a/doc/development/testing_guide/end_to_end/best_practices/feature_flags.md b/doc/development/testing_guide/end_to_end/best_practices/feature_flags.md index f2038be67b7..5cb81111832 100644 --- a/doc/development/testing_guide/end_to_end/best_practices/feature_flags.md +++ b/doc/development/testing_guide/end_to_end/best_practices/feature_flags.md @@ -198,7 +198,7 @@ End-to-end tests should pass with a feature flag enabled before it is enabled on There are two ways to confirm that end-to-end tests pass: - If a merge request adds or edits a [feature flag definition file](../../../feature_flags/_index.md#feature-flag-definition-and-validation), - two `e2e:test-on-omnibus-ee` jobs (`ee:instance-parallel` and `ee:instance-parallel-ff-inverse`) are included automatically in the merge request pipeline. + two `e2e:test-on-gdk` jobs (`gdk-instance` and `gdk-instance-ff-inverse`) are included automatically in the merge request pipeline. One job runs the application with default feature flag state and another sets it to inverse value. The jobs execute the same suite of tests to confirm that they pass with the feature flag either enabled or disabled. - In some cases, if end-to-end test jobs didn't trigger automatically, or if it has run the tests with the default feature flag values (which might not be desired), you can create a Draft MR that enables the feature flag to ensure that all E2E tests pass with the feature flag enabled and disabled. diff --git a/doc/subscriptions/subscription-add-ons.md b/doc/subscriptions/subscription-add-ons.md index 36022a4c484..18129a7227e 100644 --- a/doc/subscriptions/subscription-add-ons.md +++ b/doc/subscriptions/subscription-add-ons.md @@ -37,7 +37,7 @@ GitLab Duo Core is included automatically if you have: If you are a new customer in GitLab 18.0 or later, IDE features are automatically turned on and no further action is needed. -If you are a pre-existing customer from GitLab 17.11 or earlier, you must [turn on IDE features](../user/gitlab_duo/turn_on_off.md#change-gitlab-duo-core-availability) to start using GitLab Duo in your IDEs. No further action is needed. +If you are a pre-existing customer from GitLab 17.11 or earlier, you must [turn on IDE features](../user/gitlab_duo/turn_on_off.md#turn-gitlab-duo-core-on-or-off) to start using GitLab Duo in your IDEs. No further action is needed. Users assigned the following roles have access to GitLab Duo Core: diff --git a/doc/tutorials/scrum_events/_index.md b/doc/tutorials/scrum_events/_index.md index 19c7e3fe25e..eed13e270be 100644 --- a/doc/tutorials/scrum_events/_index.md +++ b/doc/tutorials/scrum_events/_index.md @@ -433,7 +433,7 @@ Mark them for refinement: By this point in the tutorial, your **Backlog** board should look like this: -![Example issue board](img/issue_board_demo_v16_10.png) +![A backlog board with user stories distributed across iteration columns.](img/issue_board_demo_v16_10.png) In practice, you will use this board to sequence many stories into upcoming iterations. When your backlog grows, and you have dozens of stories spanning multiple features, it can be helpful diff --git a/doc/user/application_security/coverage_fuzzing/_index.md b/doc/user/application_security/coverage_fuzzing/_index.md index d217784f815..ab48f09cab1 100644 --- a/doc/user/application_security/coverage_fuzzing/_index.md +++ b/doc/user/application_security/coverage_fuzzing/_index.md @@ -13,6 +13,8 @@ description: Coverage-guided fuzzing, random inputs, and unexpected behavior. {{< /details >}} +## Getting started + Coverage-guided fuzz testing sends random inputs to an instrumented version of your application in an effort to cause unexpected behavior. Such behavior indicates a bug that you should address. GitLab allows you to add coverage-guided fuzz testing to your pipelines. This helps you discover @@ -25,43 +27,7 @@ you can run your coverage-guided fuzz testing as part your CI/CD workflow. For an overview, see [Coverage Fuzzing](https://www.youtube.com/watch?v=bbIenVVcjW0). -## Coverage-guided fuzz testing process - -The fuzz testing process: - -1. Compiles the target application. -1. Runs the instrumented application, using the `gitlab-cov-fuzz` tool. -1. Parses and analyzes the exception information output by the fuzzer. -1. Downloads the [corpus](../terminology/_index.md#corpus) from either: - - The previous pipelines. - - If `COVFUZZ_USE_REGISTRY` is set to `true`, the [corpus registry](#corpus-registry). -1. Downloads crash events from previous pipeline. -1. Outputs the parsed crash events and data to the `gl-coverage-fuzzing-report.json` file. -1. Updates the corpus, either: - - In the job's pipeline. - - If `COVFUZZ_USE_REGISTRY` is set to `true`, in the corpus registry. - -The results of the coverage-guided fuzz testing are available in the CI/CD pipeline. - -## Supported fuzzing engines and languages - -You can use the following fuzzing engines to test the specified languages. - -| Language | Fuzzing Engine | Example | -|---------------------------------------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| -| C/C++ | [libFuzzer](https://llvm.org/docs/LibFuzzer.html) | [c-cpp-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/c-cpp-fuzzing-example) | -| Go | [go-fuzz (libFuzzer support)](https://github.com/dvyukov/go-fuzz) | [go-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example) | -| Swift | [libFuzzer](https://github.com/apple/swift/blob/master/docs/libFuzzerIntegration.md) | [swift-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/swift-fuzzing-example) | -| Rust | [cargo-fuzz (libFuzzer support)](https://github.com/rust-fuzz/cargo-fuzz) | [rust-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example) | -| Java (Maven only)1 | [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) (recommended) | [javafuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/javafuzz-fuzzing-example) | -| Java | [JQF](https://github.com/rohanpadhye/JQF) (not preferred) | [jqf-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/java-fuzzing-example) | -| JavaScript | [`jsfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz) | [jsfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/jsfuzz-fuzzing-example) | -| Python | [`pythonfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/pythonfuzz) | [pythonfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/pythonfuzz-fuzzing-example) | -| AFL (any language that works on top of AFL) | [AFL](https://lcamtuf.coredump.cx/afl/) | [afl-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/afl-fuzzing-example) | - -1. Support for Gradle is planned in [issue 409764](https://gitlab.com/gitlab-org/gitlab/-/issues/409764). - -## Confirm status of coverage-guided fuzz testing +### Confirm status of coverage-guided fuzz testing To confirm the status of coverage-guided fuzz testing: @@ -72,7 +38,7 @@ To confirm the status of coverage-guided fuzz testing: - **Enabled** - A prompt to upgrade to GitLab Ultimate. -## Enable coverage-guided fuzz testing +### Enable coverage-guided fuzz testing To enable coverage-guided fuzz testing, edit `.gitlab-ci.yml`: @@ -121,34 +87,9 @@ job. If you include these keys in your own job, you must copy their original con - `artifacts` - `rules` -### Available CI/CD variables +## Understanding the results -Use the following variables to configure coverage-guided fuzz testing in your CI/CD pipeline. - -{{< alert type="warning" >}} - -All customization of GitLab security scanning tools should be tested in a merge request before -merging these changes to the default branch. Failure to do so can give unexpected results, including -a large number of false positives. - -{{< /alert >}} - -| CI/CD variable | Description | -|---------------------------|---------------------------------------------------------------------------------| -| `COVFUZZ_ADDITIONAL_ARGS` | Arguments passed to `gitlab-cov-fuzz`. Used to customize the behavior of the underlying fuzzing engine. Read the fuzzing engine's documentation for a complete list of arguments. | -| `COVFUZZ_BRANCH` | The branch on which long-running fuzzing jobs are to be run. On all other branches, only fuzzing regression tests are run. Default: Repository's default branch. | -| `COVFUZZ_SEED_CORPUS` | Path to a seed corpus directory. Default: empty. | -| `COVFUZZ_URL_PREFIX` | Path to the `gitlab-cov-fuzz` repository cloned for use with an offline environment. You should only change this value when using an offline environment. Default: `https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw`. | -| `COVFUZZ_USE_REGISTRY` | Set to `true` to have the corpus stored in the GitLab corpus registry. The variables `COVFUZZ_CORPUS_NAME` and `COVFUZZ_GITLAB_TOKEN` are required if this variable is set to `true`. Default: `false`. | -| `COVFUZZ_CORPUS_NAME` | Name of the corpus to be used in the job. | -| `COVFUZZ_GITLAB_TOKEN` | Environment variable configured with [personal access token](../../profile/personal_access_tokens.md#create-a-personal-access-token) or [project access token](../../project/settings/project_access_tokens.md#create-a-project-access-token) with API read/write access. | - -#### Seed corpus - -Files in the [seed corpus](../terminology/_index.md#seed-corpus) must be updated manually. They are -not updated or overwritten by the coverage-guide fuzz testing job. - -## Output +### Output Each fuzzing step outputs these artifacts: @@ -162,7 +103,7 @@ Each fuzzing step outputs these artifacts: You can download the JSON report file from the CI/CD pipelines page. For more information, see [Downloading artifacts](../../../ci/jobs/job_artifacts.md#download-job-artifacts). -## Corpus registry +### Corpus registry The corpus registry is a library of corpora. Corpora in a project's registry are available to all jobs in that project. A project-wide registry is a more efficient way to manage corpora than @@ -175,7 +116,7 @@ When you download a corpus, the file is named `artifacts.zip`, regardless of the the corpus was initially uploaded. This file contains only the corpus, which is different to the artifacts files you can download from the CI/CD pipeline. Also, a project member with a Reporter or above privilege can download the corpus using the direct download link. -### View details of the corpus registry +#### View details of the corpus registry To view details of the corpus registry: @@ -183,14 +124,14 @@ To view details of the corpus registry: 1. Select **Secure > Security configuration**. 1. In the **Coverage Fuzzing** section, select **Manage corpus**. -### Create a corpus in the corpus registry +#### Create a corpus in the corpus registry To create a corpus in the corpus registry, either: - Create a corpus in a pipeline - Upload an existing corpus file -#### Create a corpus in a pipeline +##### Create a corpus in a pipeline To create a corpus in a pipeline: @@ -203,7 +144,7 @@ To create a corpus in a pipeline: After the `my_fuzz_target` job runs, the corpus is stored in the corpus registry, with the name provided by the `COVFUZZ_CORPUS_NAME` variable. The corpus is updated on every pipeline run. -#### Upload a corpus file +##### Upload a corpus file To upload an existing corpus file: @@ -232,7 +173,7 @@ Prerequisites: - Set `COVFUZZ_CORPUS_NAME` to the name of the corpus. - Set `COVFUZZ_GITLAB_TOKEN` to the value of the personal access token. -## Coverage-guided fuzz testing report +### Coverage-guided fuzz testing report For detailed information about the `gl-coverage-fuzzing-report.json` file's format, read the [schema](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/coverage-fuzzing-report-format.json). @@ -266,7 +207,106 @@ Example coverage-guided fuzzing report: } ``` -## Duration of coverage-guided fuzz testing +### Interacting with the vulnerabilities + +After a vulnerability is found, you can [address it](../vulnerabilities/_index.md). +The merge request widget lists the vulnerability and contains a button for downloading the fuzzing +artifacts. By selecting one of the detected vulnerabilities, you can see its details. + +![Coverage Fuzzing Security Report](img/coverage_fuzzing_report_v13_6.png) + +You can also view the vulnerability from the [Security Dashboard](../security_dashboard/_index.md), +which shows an overview of all the security vulnerabilities in your groups, projects, and pipelines. + +Selecting the vulnerability opens a modal that provides additional information about the +vulnerability: + +- Status: The vulnerability's status. As with any type of vulnerability, a coverage fuzzing + vulnerability can be Detected, Confirmed, Dismissed, or Resolved. +- Project: The project in which the vulnerability exists. +- Crash type: The type of crash or weakness in the code. This typically maps to a [CWE](https://cwe.mitre.org/). +- Crash state: A normalized version of the stack trace, containing the last three functions of the + crash (without random addresses). +- Stack trace snippet: The last few lines of the stack trace, which shows details about the crash. +- Identifier: The vulnerability's identifier. This maps to either a [CVE](https://cve.mitre.org/) + or [CWE](https://cwe.mitre.org/). +- Severity: The vulnerability's severity. This can be Critical, High, Medium, Low, Info, or Unknown. +- Scanner: The scanner that detected the vulnerability (for example, Coverage Fuzzing). +- Scanner Provider: The engine that did the scan. For Coverage Fuzzing, this can be any of the + engines listed in [Supported fuzzing engines and languages](#supported-fuzzing-engines-and-languages). + +## Optimization + +Use the following customization options to optimize coverage-guided fuzz testing to your project. + +### Available CI/CD variables + +Use the following variables to configure coverage-guided fuzz testing in your CI/CD pipeline. + +{{< alert type="warning" >}} + +All customization of GitLab security scanning tools should be tested in a merge request before +merging these changes to the default branch. Failure to do so can give unexpected results, including +a large number of false positives. + +{{< /alert >}} + +| CI/CD variable | Description | +|---------------------------|---------------------------------------------------------------------------------| +| `COVFUZZ_ADDITIONAL_ARGS` | Arguments passed to `gitlab-cov-fuzz`. Used to customize the behavior of the underlying fuzzing engine. Read the fuzzing engine's documentation for a complete list of arguments. | +| `COVFUZZ_BRANCH` | The branch on which long-running fuzzing jobs are to be run. On all other branches, only fuzzing regression tests are run. Default: Repository's default branch. | +| `COVFUZZ_SEED_CORPUS` | Path to a seed corpus directory. Default: empty. | +| `COVFUZZ_URL_PREFIX` | Path to the `gitlab-cov-fuzz` repository cloned for use with an offline environment. You should only change this value when using an offline environment. Default: `https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw`. | +| `COVFUZZ_USE_REGISTRY` | Set to `true` to have the corpus stored in the GitLab corpus registry. The variables `COVFUZZ_CORPUS_NAME` and `COVFUZZ_GITLAB_TOKEN` are required if this variable is set to `true`. Default: `false`. | +| `COVFUZZ_CORPUS_NAME` | Name of the corpus to be used in the job. | +| `COVFUZZ_GITLAB_TOKEN` | Environment variable configured with [personal access token](../../profile/personal_access_tokens.md#create-a-personal-access-token) or [project access token](../../project/settings/project_access_tokens.md#create-a-project-access-token) with API read/write access. | + +#### Seed corpus + +Files in the [seed corpus](../terminology/_index.md#seed-corpus) must be updated manually. They are +not updated or overwritten by the coverage-guide fuzz testing job. + +### Coverage-guided fuzz testing process + +The fuzz testing process: + +1. Compiles the target application. +1. Runs the instrumented application, using the `gitlab-cov-fuzz` tool. +1. Parses and analyzes the exception information output by the fuzzer. +1. Downloads the [corpus](../terminology/_index.md#corpus) from either: + - The previous pipelines. + - If `COVFUZZ_USE_REGISTRY` is set to `true`, the [corpus registry](#corpus-registry). +1. Downloads crash events from previous pipeline. +1. Outputs the parsed crash events and data to the `gl-coverage-fuzzing-report.json` file. +1. Updates the corpus, either: + - In the job's pipeline. + - If `COVFUZZ_USE_REGISTRY` is set to `true`, in the corpus registry. + +The results of the coverage-guided fuzz testing are available in the CI/CD pipeline. + +## Roll out + +After you're comfortable using coverage-guided fuzz testing in a single project, you can take advantage of the following advanced features, including enabling testing in offline environments. + +### Supported fuzzing engines and languages + +You can use the following fuzzing engines to test the specified languages. + +| Language | Fuzzing Engine | Example | +|---------------------------------------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| C/C++ | [libFuzzer](https://llvm.org/docs/LibFuzzer.html) | [c-cpp-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/c-cpp-fuzzing-example) | +| Go | [go-fuzz (libFuzzer support)](https://github.com/dvyukov/go-fuzz) | [go-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example) | +| Swift | [libFuzzer](https://github.com/apple/swift/blob/master/docs/libFuzzerIntegration.md) | [swift-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/swift-fuzzing-example) | +| Rust | [cargo-fuzz (libFuzzer support)](https://github.com/rust-fuzz/cargo-fuzz) | [rust-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example) | +| Java (Maven only)1 | [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) (recommended) | [javafuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/javafuzz-fuzzing-example) | +| Java | [JQF](https://github.com/rohanpadhye/JQF) (not preferred) | [jqf-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/java-fuzzing-example) | +| JavaScript | [`jsfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz) | [jsfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/jsfuzz-fuzzing-example) | +| Python | [`pythonfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/pythonfuzz) | [pythonfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/pythonfuzz-fuzzing-example) | +| AFL (any language that works on top of AFL) | [AFL](https://lcamtuf.coredump.cx/afl/) | [afl-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/afl-fuzzing-example) | + +1. Support for Gradle is planned in [issue 409764](https://gitlab.com/gitlab-org/gitlab/-/issues/409764). + +### Duration of coverage-guided fuzz testing The available durations for coverage-guided fuzz testing are: @@ -276,7 +316,7 @@ The available durations for coverage-guided fuzz testing are: For a complete example, read the [Go coverage-guided fuzzing example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/-/blob/master/.gitlab-ci.yml). -### Continuous coverage-guided fuzz testing +#### Continuous coverage-guided fuzz testing It's also possible to run the coverage-guided fuzzing jobs longer and without blocking your main pipeline. This configuration uses the GitLab @@ -321,11 +361,11 @@ This creates two jobs: The `covfuzz-ci.yml` is the same as that in the [original synchronous example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example#running-go-fuzz-from-ci). -## FIPS-enabled binary +### FIPS-enabled binary [Starting in GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/352549) the coverage fuzzing binary is compiled with `golang-fips` on Linux x86 and uses OpenSSL as the cryptographic backend. For more details, see FIPS compliance at GitLab with Go. -## Offline environment +### Offline environment To use coverage fuzzing in an offline environment: @@ -336,34 +376,6 @@ To use coverage fuzzing in an offline environment: `NEW_URL_GITLAB_COV_FUZ` is the URL of the private `gitlab-cov-fuzz` clone that you set up in the first step. -## Interacting with the vulnerabilities - -After a vulnerability is found, you can [address it](../vulnerabilities/_index.md). -The merge request widget lists the vulnerability and contains a button for downloading the fuzzing -artifacts. By selecting one of the detected vulnerabilities, you can see its details. - -![Coverage Fuzzing Security Report](img/coverage_fuzzing_report_v13_6.png) - -You can also view the vulnerability from the [Security Dashboard](../security_dashboard/_index.md), -which shows an overview of all the security vulnerabilities in your groups, projects, and pipelines. - -Selecting the vulnerability opens a modal that provides additional information about the -vulnerability: - -- Status: The vulnerability's status. As with any type of vulnerability, a coverage fuzzing - vulnerability can be Detected, Confirmed, Dismissed, or Resolved. -- Project: The project in which the vulnerability exists. -- Crash type: The type of crash or weakness in the code. This typically maps to a [CWE](https://cwe.mitre.org/). -- Crash state: A normalized version of the stack trace, containing the last three functions of the - crash (without random addresses). -- Stack trace snippet: The last few lines of the stack trace, which shows details about the crash. -- Identifier: The vulnerability's identifier. This maps to either a [CVE](https://cve.mitre.org/) - or [CWE](https://cwe.mitre.org/). -- Severity: The vulnerability's severity. This can be Critical, High, Medium, Low, Info, or Unknown. -- Scanner: The scanner that detected the vulnerability (for example, Coverage Fuzzing). -- Scanner Provider: The engine that did the scan. For Coverage Fuzzing, this can be any of the - engines listed in [Supported fuzzing engines and languages](#supported-fuzzing-engines-and-languages). - ## Troubleshooting ### Error `Unable to extract corpus folder from artifacts zip file` diff --git a/doc/user/gitlab_duo/troubleshooting.md b/doc/user/gitlab_duo/troubleshooting.md index e6bab9e4e4f..f46725c3ac8 100644 --- a/doc/user/gitlab_duo/troubleshooting.md +++ b/doc/user/gitlab_duo/troubleshooting.md @@ -102,7 +102,7 @@ you can also do the following: - If you have GitLab Duo Core, verify that you have: - A Premium or Ultimate subscription. - - [Turned on IDE features](turn_on_off.md#change-gitlab-duo-core-availability). + - [Turned on IDE features](turn_on_off.md#turn-gitlab-duo-core-on-or-off). - If you have GitLab Duo Pro or Enterprise: - Verify that [a subscription add-on has been purchased](../../subscriptions/subscription-add-ons.md#purchase-gitlab-duo). - Ensure that [seats are assigned to users](../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats). diff --git a/doc/user/gitlab_duo/turn_on_off.md b/doc/user/gitlab_duo/turn_on_off.md index 7631f402d73..170af40b37e 100644 --- a/doc/user/gitlab_duo/turn_on_off.md +++ b/doc/user/gitlab_duo/turn_on_off.md @@ -5,68 +5,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w title: Control GitLab Duo availability --- -GitLab Duo availability depends on your subscription add-on: +{{< details >}} -- [GitLab Duo Core](../../subscriptions/subscription-add-ons.md#gitlab-duo-core), or -- [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md#gitlab-duo-pro-and-enterprise). +- Tier: Premium, Ultimate +- Add-on: GitLab Duo Core, Pro, or Enterprise +- Offering: GitLab.com, GitLab Self-Managed -Depending on your add-on, you can turn GitLab Duo on and off for a group, project, or instance. - -## Change GitLab Duo Core availability - -{{< history >}} - -- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/538857) in GitLab 18.0. - -{{< /history >}} - -If you have the GitLab Duo Core add-on, which is included with Premium and Ultimate subscriptions, -GitLab Duo Chat and Code Suggestions are available in your IDEs, and are turned on by default. - -If you were an existing user with a Premium or Ultimate subscription before May 15, 2025, -Chat and Code Suggestions in your IDEs are turned off by default. To turn on these -features: - -1. Upgrade to GitLab 18.0 or later. -1. Turn on the IDE features for your group or instance. - -It may take up to 10 minutes for the change to take effect. - -### For a group - -On GitLab.com, you can turn GitLab Duo Core on or off for a top-level group, but not for a subgroup or project. - -Prerequisites: - -- You must have the Owner role for the top-level group. - -To turn GitLab Duo Core on or off for a top-level group on GitLab.com: - -1. On the left sidebar, select **Search or go to** and find your top-level group. -1. Select **Settings > GitLab Duo**. -1. Select **Change configuration**. -1. Below **GitLab Duo Core**, select or clear the **Turn on IDE features** checkbox. -1. Select **Save changes**. - -It may take up to 10 minutes for the change to take effect. - -### For an instance - -On GitLab Self-Managed, you can turn GitLab Duo Core on or off for an instance. - -Prerequisites: - -- You must be an administrator. - -To turn GitLab Duo Core on or off for an instance: - -1. On the left sidebar, at the bottom, select **Admin area**. -1. Select **GitLab Duo**. -1. Select **Change configuration**. -1. Below **GitLab Duo Core**, select or clear the **Turn on IDE features** checkbox. -1. Select **Save changes**. - -## Change GitLab Duo Pro and Enterprise availability +{{< /details >}} {{< history >}} @@ -75,133 +20,166 @@ To turn GitLab Duo Core on or off for an instance: {{< /history >}} -For GitLab Duo Pro or Enterprise, GitLab Duo is turned on by default. -For some generally available features, like Code Suggestions, -[you must also assign seats](../../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats) -to the users you want to have access. +GitLab Duo is on by default when you [have a subscription](../../subscriptions/subscription-add-ons.md). -You can turn GitLab Duo on or off for a group, project, or instance. +You can turn GitLab Duo on or off: -When GitLab Duo is turned off for a group, project, or instance: +- On GitLab.com: For top-level groups, other groups or subgroups, and projects. +- On GitLab Self-Managed: For instances, groups or subgroups, and projects. -- GitLab Duo features that access resources, like code, issues, and vulnerabilities, are not available. -- Code Suggestions is not available. -- GitLab Duo Chat is not available. +You can also turn GitLab Duo Core (a subset of GitLab Duo features) on or off. -### For a group or subgroup +## Turn GitLab Duo Core on or off -{{< tabs >}} +{{< history >}} -{{< tab title="In 17.8 and later" >}} +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/538857) in GitLab 18.0. +- GitLab availability settings, and group, subgroup, and project controls [added](https://gitlab.com/gitlab-org/gitlab/-/issues/551895) in GitLab 18.2. -In GitLab 17.8 and later, follow these instructions to turn GitLab Duo on or off -for a group, including its subgroups and projects. +{{< /history >}} + +If you have [GitLab Duo Core](../../subscriptions/subscription-add-ons.md#gitlab-duo-core), +which is included with Premium and Ultimate subscriptions, GitLab Duo Chat and +Code Suggestions are available in your IDEs, and are turned on by default. + +If you were an existing user with a Premium or Ultimate subscription before May 15, 2025, +when you upgrade to GitLab 18.0 or later, you have access to Chat and Code Suggestions in your IDEs, +but are turned off by default. However, you can turn them on. + +### On GitLab.com + +On GitLab.com, you can change availability for GitLab Duo Core for your top-level group (namespace). + +Prerequisites: + +- You must have the Owner role for the top-level group. + +To change GitLab Duo Core availability: + +1. On the left sidebar, select **Search or go to** and find your top-level group. +1. Select **Settings > GitLab Duo**. +1. Select **Change configuration**. +1. Under **GitLab Duo availability in this namespace**, select an option. +1. Under **GitLab Duo Core**, select or clear the **Turn on IDE features** checkbox. + If you selected **Always off** for GitLab Duo availability, you cannot access + this setting. +1. Select **Save changes**. + +It might take up to 10 minutes for the change to take effect. + +### On GitLab Self-Managed + +On GitLab Self-Managed, you can change availability for GitLab Duo Core for your instance. + +Prerequisites: + +- You must be an administrator. + +To change GitLab Duo Core availability: + +1. On the left sidebar, at the bottom, select **Admin**. +1. Select **GitLab Duo**. +1. Select **Change configuration**. +1. Under **GitLab Duo availability in this instance**, select an option. +1. Under **GitLab Duo Core**, select or clear the **Turn on IDE features** checkbox. + If you selected **Always off** for GitLab Duo availability, you cannot access + this setting. +1. Select **Save changes**. + +## Turn GitLab Duo on or off + +GitLab Duo is on by default when you [have a subscription](../../subscriptions/subscription-add-ons.md). +You can choose to change its availability for different groups and projects. + +### On GitLab.com + +On GitLab.com, you can control GitLab Duo availability for the top-level group, +other groups, subgroups, and projects. + +#### For a top-level group Prerequisites: - You must have the Owner role for the group. -To turn GitLab Duo on or off for a group or subgroup: +To change GitLab Duo Core availability for the top-level group: + +1. On the left sidebar, select **Search or go to** and find your top-level group. +1. Select **Settings > GitLab Duo**. +1. Select **Change configuration**. +1. Under **GitLab Duo availability in this namespace**, select an option. +1. Select **Save changes**. + +GitLab Duo availability changes for all subgroups and projects. + +#### For a group or subgroup + +Prerequisites: + +- You must have the Owner role for the group. + +To change GitLab Duo Core availability for a group or subgroup: 1. On the left sidebar, select **Search or go to** and find your group or subgroup. -1. Go to the settings, based on your deployment type and group level: - - For GitLab.com top-level groups: Select **Settings > GitLab Duo** and select **Change configuration**. - - For GitLab.com subgroups: Select **Settings > General** and expand **GitLab Duo features**. - - For GitLab Self-Managed (all groups and subgroups): Select **Settings > General** and expand **GitLab Duo features**. -1. Choose an option. +1. Select **Settings > General**. +1. Expand **GitLab Duo features**. +1. Under **GitLab Duo availability in this group**, select an option. 1. Select **Save changes**. -{{< /tab >}} +GitLab Duo availability changes for all subgroups and projects. -{{< tab title="In 17.7" >}} - -In GitLab 17.7, follow these instructions to turn GitLab Duo on or off -for a group, including its subgroups and projects. - -{{< alert type="note" >}} - -In GitLab 17.7: - -- For GitLab.com, the GitLab Duo settings page is only available for top-level groups, not for subgroups. - -- For GitLab Self-Managed, the GitLab Duo settings page is not available for groups or subgroups. - -{{< /alert >}} +#### For a project Prerequisites: -- You must have the Owner role for the group. +- You must have the Owner role for the project. -To turn GitLab Duo on or off for a top-level group: +To change GitLab Duo Core availability for a project: -1. On the left sidebar, select **Search or go to** and find your top-level group. -1. Select **Settings > GitLab Duo**. +1. On the left sidebar, select **Search or go to** and find your project. +1. Select **Settings > General**. +1. Expand **GitLab Duo**. +1. Turn the **Use AI-native features in this project** toggle on or off. +1. Select **Save changes**. + +### On GitLab Self-Managed + +On GitLab Self-Managed, you can control GitLab Duo availability for the instance, +groups, subgroups, or projects. + +#### For an instance + +Prerequisites: + +- You must be an administrator. + +To change GitLab Duo Core availability for the instance: + +1. On the left sidebar, at the bottom, select **Admin**. +1. Select **GitLab Duo**. 1. Select **Change configuration**. -1. Choose an option. +1. Under **GitLab Duo availability in this instance**, select an option. 1. Select **Save changes**. -{{< /tab >}} +GitLab Duo availability changes for the entire instance. -{{< tab title="In 17.4 to 17.6" >}} - -In GitLab 17.4 to 17.6, follow these instructions to turn GitLab Duo on or off -for a group and its subgroups and projects. - -{{< alert type="note" >}} - -In GitLab 17.4 to 17.6: - -- For GitLab.com, the GitLab Duo settings page is only available for top-level groups, not for subgroups. - -- For GitLab Self-Managed, the GitLab Duo settings page is not available for groups or subgroups. - -{{< /alert >}} +#### For a group or subgroup Prerequisites: -- You must have the Owner role for the group. - -To turn GitLab Duo on or off for a top-level group: - -1. On the left sidebar, select **Search or go to** and find your top-level group. -1. Select **Settings > GitLab Duo**. -1. Select **Change configuration**. -1. Choose an option. -1. Select **Save changes**. - -{{< /tab >}} - -{{< tab title="In 17.3 and earlier" >}} - -In GitLab 17.3 and earlier, follow these instructions to turn GitLab Duo on or off for a group -and its subgroups and projects. - -Prerequisites: - -- You must have the Owner role for the group. +- You must have the Owner role for the group or subgroup. To turn GitLab Duo on or off for a group or subgroup: 1. On the left sidebar, select **Search or go to** and find your group or subgroup. 1. Select **Settings > General**. -1. Expand **Permissions and group features**. -1. Select or clear the **Use GitLab Duo features** checkbox. -1. Optional. Select the **Enforce for all subgroups** checkbox to cascade the setting to - all subgroups. +1. Expand **GitLab Duo features**. +1. Under **GitLab Duo availability in this group**, select an option. +1. Select **Save changes**. - ![Cascading setting](img/disable_duo_features_v17_1.png) +GitLab Duo availability changes for all subgroups and projects. -{{< /tab >}} - -{{< /tabs >}} - -### For a project - -{{< tabs >}} - -{{< tab title="In 17.4 and later" >}} - -In GitLab 17.4 and later, follow these instructions to turn GitLab Duo on or off for a project. +#### For a project Prerequisites: @@ -211,93 +189,16 @@ To turn GitLab Duo on or off for a project: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Settings > General**. -1. Expand **Visibility, project features, permissions**. -1. Under **GitLab Duo**, turn the toggle on or off. +1. Expand **GitLab Duo**. +1. Turn the **Use AI-native features in this project** toggle on or off. 1. Select **Save changes**. -{{< /tab >}} +GitLab Duo availability changes for the project. -{{< tab title="In 17.3 and earlier" >}} +### For earlier GitLab versions -In GitLab 17.3 and earlier, follow these instructions to turn GitLab Duo on or off for a project. - -1. Use the GitLab GraphQL API - [`projectSettingsUpdate`](../../api/graphql/reference/_index.md#mutationprojectsettingsupdate) - mutation. -1. Set the - [`duo_features_enabled`](../../api/graphql/getting_started.md#update-project-settings) - setting to `true` or `false`. - -{{< /tab >}} - -{{< /tabs >}} - -### For an instance - -{{< details >}} - -- Offering: GitLab Self-Managed - -{{< /details >}} - -{{< tabs >}} - -{{< tab title="In 17.7 and later" >}} - -In GitLab 17.7 and later, follow these instructions to turn GitLab Duo on or off for an instance. - -Prerequisites: - -- You must be an administrator. - -To turn GitLab Duo on or off for an instance: - -1. On the left sidebar, at the bottom, select **Admin area**. -1. Select **GitLab Duo**. -1. Select **Change configuration**. -1. Choose an option. -1. Select **Save changes**. - -{{< /tab >}} - -{{< tab title="In 17.4 to 17.6" >}} - -In GitLab 17.4 to 17.6, follow these instructions to turn GitLab Duo on or off for the instance. - -Prerequisites: - -- You must be an administrator. - -To turn GitLab Duo on or off for an instance: - -1. On the left sidebar, at the bottom, select **Admin area**. -1. Select **Settings > General**. -1. Expand **GitLab Duo features**. -1. Choose an option. -1. Select **Save changes**. - -{{< /tab >}} - -{{< tab title="In 17.3 and earlier" >}} - -In GitLab 17.3 and earlier, follow these instructions to turn GitLab Duo on or off for an instance. - -Prerequisites: - -- You must be an administrator. - -To turn GitLab Duo on or off for an instance: - -1. On the left sidebar, at the bottom, select **Admin**. -1. Select **Settings > General**. -1. Expand **AI-powered features**. -1. Select or clear the **Use Duo features** checkbox. -1. Optional. Select the **Enforce for all subgroups** checkbox to cascade - the setting to all groups in the instance. - -{{< /tab >}} - -{{< /tabs >}} +For information on how to turn GitLab Duo on of off in earlier GitLab versions, +see [Control GitLab Duo availability for earlier GitLab versions](turn_on_off_earlier.md). ## Turn on beta and experimental features diff --git a/doc/user/gitlab_duo/turn_on_off_earlier.md b/doc/user/gitlab_duo/turn_on_off_earlier.md new file mode 100644 index 00000000000..cb839692f4c --- /dev/null +++ b/doc/user/gitlab_duo/turn_on_off_earlier.md @@ -0,0 +1,240 @@ +--- +stage: AI-powered +group: AI Framework +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +title: GitLab Duo availability - earlier versions +--- + +{{< details >}} + +- Tier: Premium, Ultimate +- Add-on: GitLab Duo Pro or Enterprise +- Offering: GitLab.com, GitLab Self-Managed + +{{< /details >}} + +{{< history >}} + +- [Settings to turn AI features on and off introduced](https://gitlab.com/groups/gitlab-org/-/epics/12404) in GitLab 16.10. +- [Settings to turn AI features on and off added to the UI](https://gitlab.com/gitlab-org/gitlab/-/issues/441489) in GitLab 16.11. + +{{< /history >}} + +For GitLab Duo Pro or Enterprise, you can turn GitLab Duo on or off for a group, project, or instance. + +When GitLab Duo is turned off for a group, project, or instance: + +- GitLab Duo features that access resources, like code, issues, and vulnerabilities, are not available. +- Code Suggestions is not available. +- GitLab Duo Chat is not available. + +## For a group or subgroup + +{{< tabs >}} + +{{< tab title="In 17.8 to 18.1" >}} + +In GitLab 17.8 and later, follow these instructions to turn GitLab Duo on or off +for a group, including its subgroups and projects. + +Prerequisites: + +- You must have the Owner role for the group. + +To turn GitLab Duo on or off for a group or subgroup: + +1. On the left sidebar, select **Search or go to** and find your group or subgroup. +1. Go to the settings, based on your deployment type and group level: + - For GitLab.com top-level groups: Select **Settings > GitLab Duo** and select **Change configuration**. + - For GitLab.com subgroups: Select **Settings > General** and expand **GitLab Duo features**. + - For GitLab Self-Managed (all groups and subgroups): Select **Settings > General** and expand **GitLab Duo features**. +1. Choose an option. +1. Select **Save changes**. + +{{< /tab >}} + +{{< tab title="In 17.7" >}} + +In GitLab 17.7, follow these instructions to turn GitLab Duo on or off +for a group, including its subgroups and projects. + +{{< alert type="note" >}} + +In GitLab 17.7: + +- For GitLab.com, the GitLab Duo settings page is only available for top-level groups, not for subgroups. + +- For GitLab Self-Managed, the GitLab Duo settings page is not available for groups or subgroups. + +{{< /alert >}} + +Prerequisites: + +- You must have the Owner role for the group. + +To turn GitLab Duo on or off for a top-level group: + +1. On the left sidebar, select **Search or go to** and find your top-level group. +1. Select **Settings > GitLab Duo**. +1. Select **Change configuration**. +1. Choose an option. +1. Select **Save changes**. + +{{< /tab >}} + +{{< tab title="In 17.4 to 17.6" >}} + +In GitLab 17.4 to 17.6, follow these instructions to turn GitLab Duo on or off +for a group and its subgroups and projects. + +{{< alert type="note" >}} + +In GitLab 17.4 to 17.6: + +- For GitLab.com, the GitLab Duo settings page is only available for top-level groups, not for subgroups. + +- For GitLab Self-Managed, the GitLab Duo settings page is not available for groups or subgroups. + +{{< /alert >}} + +Prerequisites: + +- You must have the Owner role for the group. + +To turn GitLab Duo on or off for a top-level group: + +1. On the left sidebar, select **Search or go to** and find your top-level group. +1. Select **Settings > GitLab Duo**. +1. Select **Change configuration**. +1. Choose an option. +1. Select **Save changes**. + +{{< /tab >}} + +{{< tab title="In 17.3 and earlier" >}} + +In GitLab 17.3 and earlier, follow these instructions to turn GitLab Duo on or off for a group +and its subgroups and projects. + +Prerequisites: + +- You must have the Owner role for the group. + +To turn GitLab Duo on or off for a group or subgroup: + +1. On the left sidebar, select **Search or go to** and find your group or subgroup. +1. Select **Settings > General**. +1. Expand **Permissions and group features**. +1. Select or clear the **Use GitLab Duo features** checkbox. +1. Optional. Select the **Enforce for all subgroups** checkbox to cascade the setting to + all subgroups. + + ![Cascading setting](img/disable_duo_features_v17_1.png) + +{{< /tab >}} + +{{< /tabs >}} + +## For a project + +{{< tabs >}} + +{{< tab title="In 17.4 to 18.1" >}} + +In GitLab 17.4 and later, follow these instructions to turn GitLab Duo on or off for a project. + +Prerequisites: + +- You must have the Owner role for the project. + +To turn GitLab Duo on or off for a project: + +1. On the left sidebar, select **Search or go to** and find your project. +1. Select **Settings > General**. +1. Expand **Visibility, project features, permissions**. +1. Under **GitLab Duo**, turn the toggle on or off. +1. Select **Save changes**. + +{{< /tab >}} + +{{< tab title="In 17.3 and earlier" >}} + +In GitLab 17.3 and earlier, follow these instructions to turn GitLab Duo on or off for a project. + +1. Use the GitLab GraphQL API + [`projectSettingsUpdate`](../../api/graphql/reference/_index.md#mutationprojectsettingsupdate) + mutation. +1. Set the + [`duo_features_enabled`](../../api/graphql/getting_started.md#update-project-settings) + setting to `true` or `false`. + +{{< /tab >}} + +{{< /tabs >}} + +## For an instance + +{{< details >}} + +- Offering: GitLab Self-Managed + +{{< /details >}} + +{{< tabs >}} + +{{< tab title="In 17.7 to 18.1" >}} + +In GitLab 17.7 and later, follow these instructions to turn GitLab Duo on or off for an instance. + +Prerequisites: + +- You must be an administrator. + +To turn GitLab Duo on or off for an instance: + +1. On the left sidebar, at the bottom, select **Admin area**. +1. Select **GitLab Duo**. +1. Select **Change configuration**. +1. Choose an option. +1. Select **Save changes**. + +{{< /tab >}} + +{{< tab title="In 17.4 to 17.6" >}} + +In GitLab 17.4 to 17.6, follow these instructions to turn GitLab Duo on or off for the instance. + +Prerequisites: + +- You must be an administrator. + +To turn GitLab Duo on or off for an instance: + +1. On the left sidebar, at the bottom, select **Admin area**. +1. Select **Settings > General**. +1. Expand **GitLab Duo features**. +1. Choose an option. +1. Select **Save changes**. + +{{< /tab >}} + +{{< tab title="In 17.3 and earlier" >}} + +In GitLab 17.3 and earlier, follow these instructions to turn GitLab Duo on or off for an instance. + +Prerequisites: + +- You must be an administrator. + +To turn GitLab Duo on or off for an instance: + +1. On the left sidebar, at the bottom, select **Admin**. +1. Select **Settings > General**. +1. Expand **AI-powered features**. +1. Select or clear the **Use Duo features** checkbox. +1. Optional. Select the **Enforce for all subgroups** checkbox to cascade + the setting to all groups in the instance. + +{{< /tab >}} + +{{< /tabs >}} diff --git a/doc/user/group/_index.md b/doc/user/group/_index.md index 40b3f07a148..7f9111ca413 100644 --- a/doc/user/group/_index.md +++ b/doc/user/group/_index.md @@ -238,7 +238,7 @@ When you leave a group: To leave a group: 1. On the left sidebar, select **Search or go to** and find your group. -1. On the group overview page, in the upper-right corner, select **Actions** (**{ellipsis_v})**. +1. On the group overview page, in the upper-right corner, select **Actions** ({{< icon name="ellipsis_v" >}}). 1. Select **Leave group**, then **Leave group** again. ## Delete a group diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md index a41e5cee453..5ed07e8a962 100644 --- a/doc/user/profile/preferences.md +++ b/doc/user/profile/preferences.md @@ -284,6 +284,18 @@ To add a new list item when you press the Enter key: 1. Select the **Automatically add new list items** checkbox. 1. Select **Save changes**. +### Maintain cursor indentation + +Maintain the indentation when you press Enter. The cursor on the new line is automatically indented the same as the previous line. This setting works only in description and comment boxes. + +To add a new list item when you press the Enter key: + +1. On the left sidebar, select your avatar. +1. Select **Preferences**. +1. Scroll to the **Behavior** section. +1. Select the **Maintain cursor indentation** checkbox. +1. Select **Save changes**. + ### Change the tab width Change the default size of tabs in diffs, blobs, and snippets. The Web IDE, file editor, and Markdown editor do not support this feature. diff --git a/doc/user/project/repository/code_suggestions/_index.md b/doc/user/project/repository/code_suggestions/_index.md index 20e32d36f8d..bec179759b2 100644 --- a/doc/user/project/repository/code_suggestions/_index.md +++ b/doc/user/project/repository/code_suggestions/_index.md @@ -11,7 +11,7 @@ title: Code Suggestions - Tier: Premium, Ultimate - Add-on: GitLab Duo Core, Pro, or Enterprise, GitLab Duo with Amazon Q - Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated -- LLMs: For code completion, Fireworks AI-hosted [`Codestral`](https://mistral.ai/news/codestral-2501) (default) and Vertex AI-hosted [`Codestral`](https://console.cloud.google.com/vertex-ai/publishers/mistralai/model-garden/codestral-2501). For code generation, Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet). +- LLMs: For code completion, Fireworks AI-hosted [`Codestral`](https://mistral.ai/news/codestral-2501) (default) and Vertex AI-hosted [`Codestral`](https://console.cloud.google.com/vertex-ai/publishers/mistralai/model-garden/codestral-2501). For code generation, Anthropic [Claude Sonnet 4](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-sonnet-4). - To opt out of Fireworks for a group, the feature flag `code_completion_opt_out_fireworks` is available. - LLM for Amazon Q: Amazon Q Developer @@ -30,6 +30,7 @@ title: Code Suggestions - Enabled Fireworks hosted `Codestral` by default via the feature flag `use_fireworks_codestral_code_completion` in GitLab 17.11. - Changed to include GitLab Duo Core in GitLab 18.0. - Enabled Fireworks hosted `Codestral` as the default model in GitLab 18.1. +- [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/545489) the default model for Code Generation to Claude Sonnet 4 in GitLab 18.2. {{< /history >}} @@ -47,7 +48,7 @@ To use Code Suggestions, you need: - A GitLab Duo Core, Pro, or Enterprise add-on. - A Premium or Ultimate subscription. - If you have GitLab Duo Pro or Enterprise, an assigned seat. -- If you have GitLab Duo Core, [IDE features turned on](../../../gitlab_duo/turn_on_off.md#change-gitlab-duo-core-availability). +- If you have GitLab Duo Core, [IDE features turned on](../../../gitlab_duo/turn_on_off.md#turn-gitlab-duo-core-on-or-off). {{< alert type="note" >}} diff --git a/doc/user/project/repository/code_suggestions/set_up.md b/doc/user/project/repository/code_suggestions/set_up.md index c624802a9e4..40f0c846335 100644 --- a/doc/user/project/repository/code_suggestions/set_up.md +++ b/doc/user/project/repository/code_suggestions/set_up.md @@ -22,7 +22,7 @@ To use Code Suggestions, you need: - A GitLab Duo Core, Pro, or Enterprise add-on. - A Premium or Ultimate subscription. - If you have GitLab Duo Pro or Enterprise, an assigned seat. -- If you have GitLab Duo Core, [IDE features turned on](../../../gitlab_duo/turn_on_off.md#change-gitlab-duo-core-availability). +- If you have GitLab Duo Core, [IDE features turned on](../../../gitlab_duo/turn_on_off.md#turn-gitlab-duo-core-on-or-off). - To confirm that Code Suggestions [supports your preferred language](supported_extensions.md#supported-languages-by-ide). Different IDEs support different languages. diff --git a/doc/user/project/repository/files/_index.md b/doc/user/project/repository/files/_index.md index ca125e934b5..e26f3466d62 100644 --- a/doc/user/project/repository/files/_index.md +++ b/doc/user/project/repository/files/_index.md @@ -127,7 +127,8 @@ The availability of this feature is controlled by a feature flag. For more infor {{< /alert >}} -When viewing a file in your repository, GitLab shows a badge with the number of open merge requests that target the current branch and modify the file. This helps you identify files that have pending changes. +When viewing a repository file, GitLab shows a badge with the number of open merge requests that target +the current branch and modify the file. This helps you identify files that have pending changes. To view the open merge requests for a file: diff --git a/doc/user/project/web_ide/_index.md b/doc/user/project/web_ide/_index.md index d1657f69aed..e43f305a49e 100644 --- a/doc/user/project/web_ide/_index.md +++ b/doc/user/project/web_ide/_index.md @@ -268,7 +268,7 @@ functionality of the Web IDE. By default, the GitLab Web IDE instance is configu {{< alert type="note" >}} -To access the VS Code Extension Marketplace, your web browser must be able to access the `.cdn.web-ide.gitlab-static.net` assets host. This security requirement ensures that third-party extensions run in isolation, and cannot access your account. +To access the VS Code Extension Marketplace, your web browser must have access to the `.cdn.web-ide.gitlab-static.net` assets host. This security requirement ensures that third-party extensions run in isolation, and cannot access your account. {{< /alert >}} @@ -313,9 +313,9 @@ With the Extensions Marketplace, you can add Vim keybindings to the Web IDE. To enable Vim keybindings, install the [Vim](https://open-vsx.org/extension/vscodevim/vim) extension. For more information, see [install an extension](#install-an-extension). -#### ASCIIDoc Support +#### AsciiDoc Support -The [ASCIIDoc](https://open-vsx.org/extension/asciidoctor/asciidoctor-vscode) extension provides live preview, syntax highlighting, and snippets for ASCIIDoc files in the Web IDE. To use ASCIIDoc markup preview in the Web IDE, you must install the ASCIIDoc extension. For more information, see [install an extension](#install-an-extension). +The [AsciiDoc](https://open-vsx.org/extension/asciidoctor/asciidoctor-vscode) extension provides live preview, syntax highlighting, and snippets for AsciiDoc files in the Web IDE. To use AsciiDoc markup preview in the Web IDE, you must install the AsciiDoc extension. For more information, see [install an extension](#install-an-extension). ## Related topics diff --git a/doc/user/workspace/_index.md b/doc/user/workspace/_index.md index 5275246564f..751293b34fb 100644 --- a/doc/user/workspace/_index.md +++ b/doc/user/workspace/_index.md @@ -360,8 +360,7 @@ Terminating the workspace revokes the token. Use the `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_n`, and `GIT_CONFIG_VALUE_n` [environment variables](https://git-scm.com/docs/git-config/#Documentation/git-config.txt-GITCONFIGCOUNT) -for Git authentication in the workspace. Git added support for these variables in Git 2.31, so the Git version -you use in the workspace container must be 2.31 or later. +for Git authentication in the workspace. These variables require Git 2.31 or later in the workspace container. ## Pod interaction in a cluster diff --git a/doc/user/workspace/create_image.md b/doc/user/workspace/create_image.md index e7acaef7d63..010388c8668 100644 --- a/doc/user/workspace/create_image.md +++ b/doc/user/workspace/create_image.md @@ -6,6 +6,8 @@ description: Create a custom workspace image to support any workspace you create title: 'Tutorial: Create a custom workspace image that supports arbitrary user IDs' --- + + This tutorial guides you through creating a custom workspace image that supports arbitrary user IDs. Once complete, you can use this custom image with any [workspace](_index.md) you create in GitLab. diff --git a/doc/user/workspace/gitlab_agent_configuration.md b/doc/user/workspace/gitlab_agent_configuration.md index 6db8c3035d9..d2524c3f3d5 100644 --- a/doc/user/workspace/gitlab_agent_configuration.md +++ b/doc/user/workspace/gitlab_agent_configuration.md @@ -47,7 +47,8 @@ Prerequisites: {{< /history >}} -With the new authorization strategy that replaces the [legacy authorization strategy](#legacy-agent-authorization-strategy), group owners and administrators can control which cluster agents can be used for hosting workspaces in a group. +The new authorization strategy replaces the [legacy authorization strategy](#legacy-agent-authorization-strategy). +Group owners and administrators can control which cluster agents host workspaces in their group. For example, if the path to your workspace project is `top-level-group/subgroup-1/subgroup-2/workspace-project`, you can use any configured agent for either `top-level-group`, `subgroup-1` or `subgroup-2` group. diff --git a/doc/user/workspace/set_up_gitlab_agent_and_proxies.md b/doc/user/workspace/set_up_gitlab_agent_and_proxies.md index a56758a0e47..7b464a2f50f 100644 --- a/doc/user/workspace/set_up_gitlab_agent_and_proxies.md +++ b/doc/user/workspace/set_up_gitlab_agent_and_proxies.md @@ -6,6 +6,8 @@ description: Create a GitLab workspaces proxy to authenticate and authorize work title: 'Tutorial: Set up the GitLab agent for Kubernetes' --- + + This tutorial shows you how to: - Set up the [GitLab agent for Kubernetes](../clusters/agent/_index.md) diff --git a/doc/user/workspace/settings.md b/doc/user/workspace/settings.md index 4ae74cda103..e28717b183a 100644 --- a/doc/user/workspace/settings.md +++ b/doc/user/workspace/settings.md @@ -536,9 +536,8 @@ For more information about `labels`, see {{< /history >}} -Use this setting to automatically stop the agent's workspaces after the specified number of hours -have passed, because the workspace last transitioned to an active state. -An "active state" is defined as any non-stopped or non-terminated state. +This setting automatically stops the agent's workspaces after they have been active for the specified +number of hours. An active state is any non-stopped or non-terminated state. The timer for this setting starts when you create the workspace, and is reset every time you restart the workspace. diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index bbe3a0365b8..14e01f329f8 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -141,9 +141,7 @@ module API end def include_ip_address_in_audit_event?(ip_address) - params[:protocol] == 'ssh' && ip_address && Feature.enabled?( - :stream_audit_events_remote_ip_proxy_protocol, project - ) + params[:protocol] == 'ssh' && ip_address end private diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index d04fe59b48e..c26ae704652 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -17,6 +17,7 @@ module Gitlab gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.markdown_surround_selection = current_user&.markdown_surround_selection gon.markdown_automatic_lists = current_user&.markdown_automatic_lists + gon.markdown_maintain_indentation = current_user&.markdown_maintain_indentation gon.math_rendering_limits_enabled = Gitlab::CurrentSettings.math_rendering_limits_enabled add_browsersdk_tracking @@ -60,23 +61,33 @@ module Gitlab gon.diagramsnet_url = Gitlab::CurrentSettings.diagramsnet_url if Gitlab::CurrentSettings.diagramsnet_enabled - if current_user - gon.version = Gitlab::VERSION # publish version only for logged in users - gon.current_user_id = current_user.id - gon.current_username = current_user.username - gon.current_user_fullname = current_user.name - gon.current_user_avatar_url = current_user.avatar_url - gon.time_display_relative = current_user.time_display_relative - gon.time_display_format = current_user.time_display_format - gon.text_editor = current_user.user_preference&.text_editor if current_user.user_preference - end - if current_organization && Feature.enabled?(:ui_for_organizations, current_user) gon.current_organization = current_organization.slice(:id, :name, :web_url, :avatar_url) end - # Initialize gon.features with any flags that should be - # made globally available to the frontend + add_gon_user_specific + add_gon_feature_flags + end + + def add_gon_user_specific + return unless current_user + + gon.version = Gitlab::VERSION # publish version only for logged in users + gon.current_user_id = current_user.id + gon.current_username = current_user.username + gon.current_user_fullname = current_user.name + gon.current_user_avatar_url = current_user.avatar_url + gon.time_display_relative = current_user.time_display_relative + gon.time_display_format = current_user.time_display_format + + return unless current_user.user_preference + + gon.text_editor = current_user.user_preference.text_editor + end + + # Initialize gon.features with any flags that should be + # made globally available to the frontend + def add_gon_feature_flags push_frontend_feature_flag(:ui_for_organizations, current_user) push_frontend_feature_flag(:organization_switching, current_user) push_frontend_feature_flag(:find_and_replace, current_user) @@ -87,6 +98,7 @@ module Gitlab push_frontend_feature_flag(:new_project_creation_form, current_user, type: :wip) push_frontend_feature_flag(:work_items_client_side_boards, current_user) push_frontend_feature_flag(:glql_work_items, current_user, type: :wip) + push_frontend_feature_flag(:continue_indented_text, current_user) end # Exposes the state of a feature flag to the frontend code. diff --git a/lib/gitlab/import_export/json/streaming_serializer.rb b/lib/gitlab/import_export/json/streaming_serializer.rb index 68de57dc854..fa59918490f 100644 --- a/lib/gitlab/import_export/json/streaming_serializer.rb +++ b/lib/gitlab/import_export/json/streaming_serializer.rb @@ -230,10 +230,7 @@ module Gitlab # @param record_ids An optional array of record IDs that may be provided during # direct transfer batch export. def batch_ordering(records, key, record_ids) - if key == :merge_requests && - (record_ids || Feature.disabled?(:keyset_paginate_exported_merge_requests, exportable)) - return - end + return if key == :merge_requests && record_ids export_reorder = relations_schema[:export_reorder]&.dig(key) return unless export_reorder diff --git a/lib/gitlab/topology_service_client/health_service.rb b/lib/gitlab/topology_service_client/health_service.rb new file mode 100644 index 00000000000..07b2af1e36f --- /dev/null +++ b/lib/gitlab/topology_service_client/health_service.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Gitlab + module TopologyServiceClient + # Uses GRPC Health Checking Protocol. See https://github.com/grpc/grpc/blob/master/doc/health-checking.md + class HealthService < BaseService + # From https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + # UNKNOWN = 0 + # SERVING = 1 + # NOT_SERVING = 2 + SERVING_STATUS = :SERVING + + def service_healthy? + response = service_class.check(health_check_request) + + Gitlab::AppLogger.info(message: "Topology Service status code is: #{response.status}") + + response.status == SERVING_STATUS + rescue GRPC::Unavailable => e + Gitlab::AppLogger.error(message: "Topology Service is UNAVAILABLE: #{e.message}") + + false + end + + private + + def service_class + @service_class ||= Grpc::Health::V1::Health::Stub.new(topology_service_address, service_credentials) + end + + def health_check_request + @health_check_request ||= Grpc::Health::V1::HealthCheckRequest.new + end + end + end +end diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake index ab52d6494cd..7ea1dbdcb33 100644 --- a/lib/tasks/gitlab/db.rake +++ b/lib/tasks/gitlab/db.rake @@ -145,6 +145,8 @@ namespace :gitlab do end def configure_pg_databases + check_topology_service_health! + databases_with_tasks = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env) databases_loaded = [] @@ -192,6 +194,20 @@ namespace :gitlab do load_database end + def check_topology_service_health! + return unless Gitlab.config.cell.enabled + + if Gitlab.config.cell.database.skip_sequence_alteration + return puts 'Skipping Topology Service health check due to cell sequences alteration being disabled' + end + + unless Gitlab::TopologyServiceClient::HealthService.new.service_healthy? + raise 'Error: Topology Service is `UNAVAILABLE`. Exiting DB configuration.' + end + + puts 'Topology Service is HEALTHY.' + end + def alter_cell_sequences_range return unless Gitlab.config.cell.enabled diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 26814ffcb15..5875fd2e417 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6099,6 +6099,9 @@ msgstr "" msgid "AiPowered|Local AI gateway URL" msgstr "" +msgid "AiPowered|Lock tooltip icon" +msgstr "" + msgid "AiPowered|Might result in requests having higher latency." msgstr "" @@ -6150,6 +6153,9 @@ msgstr "" msgid "AiPowered|This setting applies to the whole top-level group. By turning this on, you accept the %{termsLinkStart}GitLab AI Functionality Terms%{termsLinkEnd} unless your organization has a separate agreement with GitLab governing AI feature usage. Check the %{requirementsLinkStart}eligibility requirements%{requirementsLinkEnd}." msgstr "" +msgid "AiPowered|This setting requires GitLab Duo availability to be on or off by default." +msgstr "" + msgid "AiPowered|Turn off GitLab Duo Workflow" msgstr "" @@ -15608,42 +15614,6 @@ msgstr "" msgid "Compliance frameworks" msgstr "" -msgid "Compliance report|%{count} projects are not covered by %{framework}" -msgstr "" - -msgid "Compliance report|%{count} projects have at least one framework" -msgstr "" - -msgid "Compliance report|%{count} projects have framework: %{framework}" -msgstr "" - -msgid "Compliance report|%{count} projects have no framework at all" -msgstr "" - -msgid "Compliance report|%{percent}%% coverage" -msgstr "" - -msgid "Compliance report|%{percent}%% coverage for %{framework}" -msgstr "" - -msgid "Compliance report|All frameworks" -msgstr "" - -msgid "Compliance report|Click to check all projects" -msgstr "" - -msgid "Compliance report|Projects covered by frameworks" -msgstr "" - -msgid "Compliance report|Projects not covered by frameworks" -msgstr "" - -msgid "Compliance report|Start by adding a compliance framework to your group." -msgstr "" - -msgid "Compliance report|There are no compliance frameworks." -msgstr "" - msgid "Compliance requirement control successfully deleted" msgstr "" @@ -16097,9 +16067,30 @@ msgstr "" msgid "ComplianceManagement|Requirement" msgstr "" +msgid "ComplianceReport|%{count} projects are not covered by %{framework}" +msgstr "" + +msgid "ComplianceReport|%{count} projects have at least one framework" +msgstr "" + +msgid "ComplianceReport|%{count} projects have framework: %{framework}" +msgstr "" + +msgid "ComplianceReport|%{count} projects have no framework at all" +msgstr "" + +msgid "ComplianceReport|%{percent}%% coverage" +msgstr "" + +msgid "ComplianceReport|%{percent}%% coverage for %{framework}" +msgstr "" + msgid "ComplianceReport|Action" msgstr "" +msgid "ComplianceReport|All frameworks" +msgstr "" + msgid "ComplianceReport|Apply frameworks to selected projects" msgstr "" @@ -16124,6 +16115,9 @@ msgstr "" msgid "ComplianceReport|Choose one bulk action" msgstr "" +msgid "ComplianceReport|Click to check all projects" +msgstr "" + msgid "ComplianceReport|Compliance framework" msgstr "" @@ -16133,6 +16127,9 @@ msgstr "" msgid "ComplianceReport|Compliance pipelines are deprecated" msgstr "" +msgid "ComplianceReport|Controls" +msgstr "" + msgid "ComplianceReport|Create a new framework" msgstr "" @@ -16154,10 +16151,7 @@ msgstr "" msgid "ComplianceReport|Edit the framework" msgstr "" -msgid "ComplianceReport|Failed controls" -msgstr "" - -msgid "ComplianceReport|Failed requirements" +msgid "ComplianceReport|Failed" msgstr "" msgid "ComplianceReport|Failed to update violation status. Please try again." @@ -16217,12 +16211,27 @@ msgstr "" msgid "ComplianceReport|Only group owners and maintainers can view the framework details" msgstr "" +msgid "ComplianceReport|Passed" +msgstr "" + +msgid "ComplianceReport|Pending" +msgstr "" + msgid "ComplianceReport|Please remove the compliance pipeline configuration from the compliance framework so that the new pipeline execution policy can take precedence." msgstr "" +msgid "ComplianceReport|Projects covered by frameworks" +msgstr "" + +msgid "ComplianceReport|Projects not covered by frameworks" +msgstr "" + msgid "ComplianceReport|Remove frameworks from selected projects" msgstr "" +msgid "ComplianceReport|Requirements" +msgstr "" + msgid "ComplianceReport|Search target branch" msgstr "" @@ -16235,6 +16244,18 @@ msgstr "" msgid "ComplianceReport|Show old report" msgstr "" +msgid "ComplianceReport|Start by adding a compliance framework to your group." +msgstr "" + +msgid "ComplianceReport|There are no compliance frameworks." +msgstr "" + +msgid "ComplianceReport|There are no controls." +msgstr "" + +msgid "ComplianceReport|There are no requirements." +msgstr "" + msgid "ComplianceReport|There was a problem fetching compliance frameworks." msgstr "" @@ -16271,6 +16292,12 @@ msgstr "" msgid "ComplianceReport|You are viewing the compliance centre for %{project}. To see information for all projects, go to %{linkStart}group%{linkEnd}." msgstr "" +msgid "ComplianceReport|You can add controls for requirements inside the compliance framework." +msgstr "" + +msgid "ComplianceReport|You can add requirements inside the compliance framework." +msgstr "" + msgid "ComplianceReport|and %{count} more" msgstr "" @@ -38620,9 +38647,6 @@ msgstr "" msgid "Members|2FA" msgstr "" -msgid "Members|A group must have at least one owner. To leave this group, assign a new owner." -msgstr "" - msgid "Members|A group must have at least one owner. To remove the member, assign a new owner." msgstr "" @@ -38710,6 +38734,9 @@ msgstr "" msgid "Members|Filter members" msgstr "" +msgid "Members|Groups require a human Owner. Assign another human user as Owner to leave." +msgstr "" + msgid "Members|Indirect" msgstr "" @@ -47170,6 +47197,9 @@ msgstr "" msgid "Preferences|Light color scheme" msgstr "" +msgid "Preferences|Maintain indentation" +msgstr "" + msgid "Preferences|Mode" msgstr "" @@ -47239,6 +47269,9 @@ msgstr "" msgid "Preferences|Web IDE and Workspaces" msgstr "" +msgid "Preferences|When you type in a description or comment box, pressing %{kbdOpen}Enter%{kbdClose} creates a new line indented the same as the previous line." +msgstr "" + msgid "Preferences|When you type in a description or comment box, pressing %{kbdOpen}Enter%{kbdClose} in a list adds a new item below." msgstr "" diff --git a/rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations.rb b/rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations.rb deleted file mode 100644 index b1d0b0a3e0e..00000000000 --- a/rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../migration_helpers' - -module RuboCop - module Cop - module Migration - # Prevents usage of `enable_lock_retries!` for transactional migrations. - # - # @example - # - # # bad - # class MyMigration < Gitlab::Database::Migration[2.2] - # milestone '18.0' - # enable_lock_retries! - # - # def change - # add_column :users, :column_id, :smallint - # end - # end - # - # # good - # class MyMigration < Gitlab::Database::Migration[2.2] - # milestone '18.0' - # - # def change - # add_column :users, :column_id, :smallint - # end - # end - class PreventEnablingLockRetriesForTransactionalMigrations < RuboCop::Cop::Base - include MigrationHelpers - - URL = 'https://docs.gitlab.com/development/migration_style_guide/#transactional-migrations' - MSG = 'Avoid using `enable_lock_retries! for transactional migrations`. The lock-retry mechanism ' \ - "is enforced by default. Check #{URL} for more details.".freeze - - RESTRICT_ON_SEND = %i[enable_lock_retries!].freeze - - # @!method enable_lock_retries?(node) - def_node_matcher :enable_lock_retries?, <<~PATTERN - `$(send nil? :enable_lock_retries!) - PATTERN - - def on_send(node) - add_offense(node) - end - alias_method :on_csend, :on_send - end - end - end -end diff --git a/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb b/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb deleted file mode 100644 index 9556a16dc01..00000000000 --- a/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../migration_helpers' - -module RuboCop - module Cop - module Migration - # Cop that prevents usage of `enable_lock_retries!` within the `disable_ddl_transaction!` method. - class PreventGlobalEnableLockRetriesWithDisableDdlTransaction < RuboCop::Cop::Base - include MigrationHelpers - - MSG = '`enable_lock_retries!` cannot be used with `disable_ddl_transaction!`. Use the `with_lock_retries` helper method to define retriable code blocks.' - - def_node_matcher :enable_lock_retries?, <<~PATTERN - (send _ :enable_lock_retries! ...) - PATTERN - - def_node_matcher :disable_ddl_transaction?, <<~PATTERN - (send _ :disable_ddl_transaction! ...) - PATTERN - - def on_begin(node) - return unless in_migration?(node) - - has_enable_lock_retries = false - has_disable_ddl_transaction = false - - node.each_descendant(:send) do |send_node| - has_enable_lock_retries = true if enable_lock_retries?(send_node) - has_disable_ddl_transaction = true if disable_ddl_transaction?(send_node) - - if has_enable_lock_retries && has_disable_ddl_transaction - add_offense(send_node, message: MSG) - break - end - end - end - end - end - end -end diff --git a/scripts/frontend/find_jest_predictive_tests.js b/scripts/frontend/find_jest_predictive_tests.js new file mode 100755 index 00000000000..06654ef9100 --- /dev/null +++ b/scripts/frontend/find_jest_predictive_tests.js @@ -0,0 +1,175 @@ +#!/usr/bin/env node + +/** + * Unlike rspec tests, we don't have a crystalball mapping file to calculate predicted tests, instead we need to find them using the dependency graph generated at runtime. + * + * So, we use helper functions in this module to list the frontend predictive tests (Jest) that are ran in tier-1, based on these criteria + * 1. Changed files (changed_files.txt) + * 2. Vue version predictive tests -- this doesn't affect test discovery, only test execution, so, we'lll get the list using Vue2 + * 2b. Vue 2 (includes all tests) + * 2a. Vue 3 (some vue3 tests are quarantined) + * 3. Backend changes (`js_matching_files.txt`) + * 3a. Fixtures + * 3b. Views + * 4. Jest integration tests (uses --config `jest.config.integration.js`) + * + * CI rule match these file patterns + * .frontend-predictive-patterns: + * '{,ee/,jh/}{app/assets/javascripts,spec/frontend}/**' + * + * Command usage: + * Run + * ```sh + * # set env variables and then run the following + * ./scripts/frontend/find_jest_predictive_tests.js + * ``` + */ +const { spawnSync } = require('node:child_process'); +const { readFileSync, writeFileSync, mkdirSync } = require('node:fs'); +const { relative, dirname } = require('node:path'); + +const requiredEnv = [ + 'JEST_MATCHING_TEST_FILES_PATH', + 'RSPEC_MATCHING_JS_FILES_PATH', + 'RSPEC_CHANGED_FILES_PATH', +]; + +function hasRequiredEnvironmentVariables() { + const missing = requiredEnv.filter((envVar) => !process.env[envVar]); + + if (missing.length > 0) { + console.warn(`Warning: Missing required environment variables: ${missing.join(', ')}`); + console.warn('Some functionality may not work as expected.'); + process.exitCode = 1; + } +} + +function getChangedFiles() { + const files = []; + const { RSPEC_MATCHING_JS_FILES_PATH, RSPEC_CHANGED_FILES_PATH } = process.env; + + for (const [name, filePath] of Object.entries({ + RSPEC_CHANGED_FILES_PATH, + RSPEC_MATCHING_JS_FILES_PATH, + })) { + try { + const contents = readFileSync(filePath, { encoding: 'UTF-8' }); + files.push(...contents.split(/\s+/).filter(Boolean)); + } catch (error) { + console.warn( + `Failed to read from path ${filePath} given by environment variable ${name}`, + error, + ); + } + } + + return Array.from(new Set(files)); +} + +function findJestTests(config, changedFiles) { + const args = ['--ci', '--config', config, '--listTests']; + + if (changedFiles) { + if (!changedFiles.length) return []; + + args.push('--findRelatedTests'); + args.push(...changedFiles); + } + try { + const childProcess = spawnSync('node_modules/.bin/jest', args, { + encoding: 'utf8', + stdio: 'pipe', + env: process.env, + }); + + if (childProcess.error) { + throw new Error(`Failed to spawn Jest: ${childProcess.error.message}`); + } + + if (childProcess.status !== 0) { + const errorOutput = childProcess.stderr || 'No error output captured'; + throw new Error(`Jest exited with code ${childProcess.status}: ${errorOutput}`); + } + + if (!childProcess.stdout) { + console.warn('No output from Jest.'); + return []; + } + return childProcess.stdout + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.endsWith('.js')) + .map((test) => relative(process.cwd(), test)); + } catch (error) { + throw new Error(`Failed to run Jest with config ${config}: ${error.message}`); + } +} + +function collectTests(changedFiles) { + if (changedFiles && !changedFiles.length) { + console.log('No changed files found - no tests to run'); + return []; + } + console.log(`Analyzing ${changedFiles.length} changed files...`); + + const configs = [ + { name: 'unit', configPath: 'jest.config.js' }, + { name: 'integration', configPath: 'jest.config.integration.js' }, + ]; + + const allTests = []; + + for (const { name, configPath } of configs) { + try { + console.log(`Checking ${name} tests...`); + const tests = findJestTests(configPath, changedFiles); + allTests.push(...tests); + } catch (error) { + console.error(`Error with ${name} tests config:\n`, error); + } + } + + return Array.from(new Set(allTests)).sort(); +} + +function saveMatchingTestFiles(testFiles) { + const { JEST_MATCHING_TEST_FILES_PATH } = process.env; + if (JEST_MATCHING_TEST_FILES_PATH) { + mkdirSync(dirname(JEST_MATCHING_TEST_FILES_PATH), { recursive: true }); + writeFileSync(JEST_MATCHING_TEST_FILES_PATH, testFiles.join(' ')); + console.log(`Saved to: ${JEST_MATCHING_TEST_FILES_PATH}`); + } +} + +function logAndSaveMatchingTestFiles(changedFiles) { + const matchingTestFiles = collectTests(changedFiles); + console.log(`\nFound ${matchingTestFiles.length} predictive test files`); + + console.log('\n=== TEST FILES ==='); + console.log(matchingTestFiles.join('\n')); + + saveMatchingTestFiles(matchingTestFiles); +} + +function listPredictiveTests() { + try { + hasRequiredEnvironmentVariables(); + + logAndSaveMatchingTestFiles(getChangedFiles()); + } catch (error) { + console.error('Error finding predictive jest tests:', error); + process.exitCode = 1; + } +} + +if (require.main === module) { + listPredictiveTests(); +} else { + module.exports = { + hasRequiredEnvironmentVariables, + getChangedFiles, + findJestTests, + collectTests, + logAndSaveMatchingTestFiles, + }; +} diff --git a/scripts/frontend/jest_ci.js b/scripts/frontend/jest_ci.js index 441a1a4ec37..a21826b42aa 100755 --- a/scripts/frontend/jest_ci.js +++ b/scripts/frontend/jest_ci.js @@ -1,9 +1,9 @@ #!/usr/bin/env node const { spawnSync } = require('node:child_process'); -const { readFileSync } = require('node:fs'); const defaultChalk = require('chalk'); const { program } = require('commander'); +const { getChangedFiles } = require('./find_jest_predictive_tests'); const IS_CI = Boolean(process.env.CI); @@ -71,26 +71,11 @@ function parseArgumentsAndEnvironment() { process.exit(1); } - const changedFiles = []; + let changedFiles = []; if (options.predictive) { - const { RSPEC_MATCHING_JS_FILES_PATH, RSPEC_CHANGED_FILES_PATH } = process.env; + changedFiles = getChangedFiles(); - for (const [name, path] of Object.entries({ - RSPEC_CHANGED_FILES_PATH, - RSPEC_MATCHING_JS_FILES_PATH, - })) { - try { - const contents = readFileSync(path, { encoding: 'UTF-8' }); - changedFiles.push(...contents.split(/\s+/).filter(Boolean)); - } catch (error) { - console.warn( - `Failed to read from path ${path} given by environment variable ${name}`, - error, - ); - } - } - - if (!changedFiles) { + if (!changedFiles || !changedFiles.length) { console.warn('No changed files detected; will not run Jest.'); process.exit(0); } diff --git a/scripts/frontend/jest_vue3_quarantine_utils.js b/scripts/frontend/jest_vue3_quarantine_utils.js index a1f51b5cd00..94fabf4a8da 100644 --- a/scripts/frontend/jest_vue3_quarantine_utils.js +++ b/scripts/frontend/jest_vue3_quarantine_utils.js @@ -1,5 +1,7 @@ const { readFile } = require('node:fs/promises'); const { join } = require('node:path'); +const { setTimeout: setTimeoutPromise } = require('node:timers/promises'); +const axios = require('axios'); function parse(quarantineFileContent) { return quarantineFileContent @@ -16,7 +18,31 @@ async function getLocalQuarantinedFiles() { return parse(content); } +// See https://gitlab.com/gitlab-org/frontend/playground/fast-jest-vue-3-quarantine for details +// about how to fast quarantine files. +async function getFastQuarantinedFiles(n = 0, maxRetries = 3) { + const url = + 'https://gitlab-org.gitlab.io/frontend/playground/fast-jest-vue-3-quarantine/gitlab.txt'; + + try { + const { data } = await axios.get(url, { timeout: 10_000 }); + return parse(data); + } catch (error) { + console.error('\nFailed to fetch list of specs failing with @vue/compat: %s', error.message); + + if (n < maxRetries) { + const waitMs = 5_000 * 2 ** n; + console.error(`Waiting ${waitMs}ms to retry (${maxRetries - n} remaining)`); + await setTimeoutPromise(waitMs); + return getFastQuarantinedFiles(n + 1); + } + + throw error; + } +} + Object.assign(module.exports, { parse, getLocalQuarantinedFiles, + getFastQuarantinedFiles, }); diff --git a/scripts/frontend/skip_specs_broken_in_vue_compat_fixture_ci_sequencer.js b/scripts/frontend/skip_specs_broken_in_vue_compat_fixture_ci_sequencer.js index 2c18d7615a9..e3ca5f1e6cc 100644 --- a/scripts/frontend/skip_specs_broken_in_vue_compat_fixture_ci_sequencer.js +++ b/scripts/frontend/skip_specs_broken_in_vue_compat_fixture_ci_sequencer.js @@ -1,32 +1,10 @@ const { relative } = require('node:path'); -const { setTimeout: setTimeoutPromise } = require('node:timers/promises'); -const axios = require('axios'); -const { parse, getLocalQuarantinedFiles } = require('./jest_vue3_quarantine_utils'); +const { + getFastQuarantinedFiles, + getLocalQuarantinedFiles, +} = require('./jest_vue3_quarantine_utils'); const FixtureCISequencer = require('./fixture_ci_sequencer'); -const url = - 'https://gitlab-org.gitlab.io/frontend/playground/fast-jest-vue-3-quarantine/gitlab.txt'; - -// See https://gitlab.com/gitlab-org/frontend/playground/fast-jest-vue-3-quarantine for details -// about how to fast quarantine files. -async function getFastQuarantinedFiles(n = 0, maxRetries = 3) { - try { - const { data } = await axios.get(url, { timeout: 10_000 }); - return parse(data); - } catch (error) { - console.error('\nFailed to fetch list of specs failing with @vue/compat: %s', error.message); - - if (n < maxRetries) { - const waitMs = 5_000 * 2 ** n; - console.error(`Waiting ${waitMs}ms to retry (${maxRetries - n} remaining)`); - await setTimeoutPromise(waitMs); - return getFastQuarantinedFiles(n + 1); - } - - throw error; - } -} - async function getQuarantinedFiles() { const results = await Promise.all([getFastQuarantinedFiles(), getLocalQuarantinedFiles()]); return new Set(results.flat()); diff --git a/scripts/internal_events/cli/flows/metric_definer.rb b/scripts/internal_events/cli/flows/metric_definer.rb index 1878ba91185..3f2854219f6 100755 --- a/scripts/internal_events/cli/flows/metric_definer.rb +++ b/scripts/internal_events/cli/flows/metric_definer.rb @@ -16,7 +16,7 @@ module InternalEventsCli 'Type', 'Events', 'Scope', - 'Descriptions', + 'Description', 'Copy event', 'Group', 'Categories', @@ -30,7 +30,7 @@ module InternalEventsCli def initialize(cli, starting_event = nil) @cli = cli @selected_event_paths = Array(starting_event) - @metrics = [] + @metric = nil @selected_filters = {} end @@ -41,22 +41,25 @@ module InternalEventsCli return unless @selected_event_paths.any? prompt_for_metrics + + return unless metric + prompt_for_event_filters - - return unless @metrics.any? - - prompt_for_descriptions + prompt_for_description + prompt_for_metric_name defaults = prompt_for_copying_event_properties prompt_for_product_group(defaults) prompt_for_product_categories(defaults) prompt_for_url(defaults) prompt_for_tier(defaults) - outcomes = create_metric_files - prompt_for_next_steps(outcomes) + outcome = create_metric_file + prompt_for_next_steps(outcome) end private + attr_reader :metric + # ----- Memoization Helpers ----------------- def events @@ -131,7 +134,7 @@ module InternalEventsCli cli.say selected_events_filter_options.join cli.say "\n" - @metrics = cli.select( + @metric = cli.select( 'Which metrics do you want to add?', eligible_metrics, **select_opts, @@ -139,7 +142,6 @@ module InternalEventsCli per_page: 20, &disabled_format_callback ) - @metrics = reduce_metrics_by_time_frame(@metrics) assign_shared_attrs(:actions, :milestone) do { @@ -150,9 +152,9 @@ module InternalEventsCli end def prompt_for_event_filters - return if @metrics.none?(&:filters_expected?) + return unless metric.filters_expected? - selected_unique_identifier = @metrics.first.identifier.value + selected_unique_identifier = metric.identifier.value event_count = selected_events.length previous_inputs = { 'label' => nil, @@ -183,42 +185,6 @@ module InternalEventsCli bulk_assign(filters: event_filters) end - def prompt_for_descriptions - default_description = nil - default_key = nil - - separate_page_per_metric = @metrics.any? do |metric| - name_requirement_reason(metric) - end - - @metrics.each_with_index do |metric, idx| - if idx == 0 || separate_page_per_metric - new_page!(on_step: 'Descriptions', steps: STEPS) - - cli.say DESCRIPTION_INTRO - cli.say selected_event_descriptions.join - end - - cli.say "\n" - cli.say format_prompt(format_subheader( - 'DESCRIBING METRIC', - metric.technical_description, - idx, - @metrics.length - )) - - prompt_for_description(metric, default_description).tap do |description| - default_description = description - metric.description = "#{metric.description_prefix} #{description}" - end - - prompt_for_metric_name(metric, default_key)&.tap do |key| - default_key = key - metric.key = key - end - end - end - def file_saved_context_message(attributes) format_prefix " ", <<~TEXT.chomp - Visit #{format_info('https://metrics.gitlab.com')} to find dashboard links for this metric @@ -228,6 +194,7 @@ module InternalEventsCli def metric_dashboard_links(attributes) time_frames = attributes['time_frame'] + unless time_frames.is_a?(Array) return "- Metric trend dashboard: #{format_info(metric_trend_path(attributes['key_path']))}" end @@ -285,7 +252,7 @@ module InternalEventsCli TEXT potential_groups = [ - *@metrics.map(&:product_group), + metric.product_group, *selected_events.map(&:product_group), defaults[:product_group] ] @@ -320,18 +287,16 @@ module InternalEventsCli end end - def create_metric_files - @metrics.map.with_index do |metric, idx| - new_page!(on_step: 'Save files', steps: STEPS) # Repeat the same step but increment metric counter + def create_metric_file + new_page!(on_step: 'Save files', steps: STEPS) # Repeat the same step but increment metric counter - cli.say show_all_metric_paths(metric) - cli.say "\n" + cli.say show_all_metric_paths(metric) + cli.say "\n" - cli.say format_prompt(format_subheader('SAVING FILE', metric.description, idx, @metrics.length)) - cli.say "\n" + cli.say format_prompt(format_subheader('SAVING FILE', metric.description)) + cli.say "\n" - prompt_to_save_file(metric.file_path, metric.formatted_output) - end + prompt_to_save_file(metric.file_path, metric.formatted_output) end def show_all_metric_paths(metric) @@ -348,11 +313,10 @@ module InternalEventsCli TEXT end - def prompt_for_next_steps(outcomes = []) + def prompt_for_next_steps(outcome = nil) new_page! - outcome = outcomes.any? ? outcomes.compact.join("\n") : ' No files saved.' - metric = @metrics.first + outcome ||= ' No files saved.' cli.say <<~TEXT #{divider} @@ -416,19 +380,6 @@ module InternalEventsCli end end - def reduce_metrics_by_time_frame(metrics) - # MetricOptions class returns one metric per time_frame value, - # here we merge them into a singular metric including all the time_frame values - return metrics unless metrics.length > 1 - - time_frames = metrics.map do |metric| - metric.time_frame.value - end - - attributes = metrics.first.to_h.merge(time_frame: time_frames) - [Metric.new(**attributes)] - end - # Helper for #prompt_for_event_filters def print_event_filter_header(event, idx, total) cli.say "\n" @@ -508,7 +459,7 @@ module InternalEventsCli end end - # Helper for #prompt_for_descriptions + # Helper for #prompt_for_description def selected_event_descriptions selected_events.map do |event| filters = @selected_filters[event.action] @@ -522,31 +473,41 @@ module InternalEventsCli end end - # Helper for #prompt_for_descriptions - def prompt_for_description(metric, default) + def prompt_for_description + new_page!(on_step: 'Description', steps: STEPS) + + cli.say DESCRIPTION_INTRO + cli.say selected_event_descriptions.join + description_start = format_info("#{metric.description_prefix}...") cli.say <<~TEXT #{input_opts[:prefix]} How would you describe this metric to a non-technical person? #{input_required_text} + #{format_info('Technical description:')} #{metric.technical_description} + TEXT - prompt_for_text(" Finish the description: #{description_start}", default, multiline: true) do |q| + description = prompt_for_text(" Finish the description: #{description_start}", multiline: true) do |q| q.required true q.modify :trim q.messages[:required?] = DESCRIPTION_HELP end + + metric.description = "#{metric.description_prefix} #{description}" end - # Helper for #prompt_for_descriptions - def prompt_for_metric_name(metric, default) - name_reason = name_requirement_reason(metric) + def prompt_for_metric_name + name_reason = name_requirement_reason + + return unless name_reason + default_name = metric.key.value display_name = metric.key.value("\e[0m[REPLACE ME]\e[36m") empty_name = metric.key.value('') - - return unless name_reason + max_length = MAX_FILENAME_LENGTH - "#{empty_name}.yml".length + help_tokens = { name: default_name, count: max_length } cli.say <<~TEXT @@ -557,10 +518,7 @@ module InternalEventsCli TEXT - max_length = MAX_FILENAME_LENGTH - "#{empty_name}.yml".length - help_tokens = { name: default_name, count: max_length } - - prompt_for_text(' Replace with: ', default, multiline: true) do |q| + metric.key = prompt_for_text(' Replace with: ', multiline: true) do |q| q.required true q.messages[:required?] = name_reason[:help] % help_tokens q.messages[:valid?] = NAME_ERROR % help_tokens @@ -572,8 +530,8 @@ module InternalEventsCli end end - # Helper for #prompt_for_descriptions - def name_requirement_reason(metric) + # Helper for #prompt_for_description + def name_requirement_reason if metric.filters.assigned? NAME_REQUIREMENT_REASONS[:filters] elsif metric.file_name.length > MAX_FILENAME_LENGTH @@ -583,7 +541,7 @@ module InternalEventsCli end end - # Helper for #prompt_for_descriptions + # Helper for #prompt_for_description def conflicting_key_path?(key_path) cli.global.metrics.any? do |existing_metric| existing_metric.key_path == key_path @@ -624,7 +582,6 @@ module InternalEventsCli # ----- Shared Helpers ---------------------- def assign_shared_attrs(...) - metric = @metrics.first attrs = metric.to_h.slice(...) attrs = yield(metric) unless attrs.values.all? @@ -638,7 +595,7 @@ module InternalEventsCli end def bulk_assign(attrs) - @metrics.each { |metric| metric.bulk_assign(attrs) } + metric.bulk_assign(attrs) end end end diff --git a/scripts/internal_events/cli/global_state.rb b/scripts/internal_events/cli/global_state.rb index f781ecd5781..14f71d1e1ad 100644 --- a/scripts/internal_events/cli/global_state.rb +++ b/scripts/internal_events/cli/global_state.rb @@ -18,6 +18,7 @@ module InternalEventsCli InternalEventsCli::NEW_METRIC_FIELDS, all_metric_paths ) + loaded_files.flat_map do |metric| # copy logic of Gitlab::Usage::MetricDefinition next metric unless metric.time_frame.is_a?(Array) diff --git a/scripts/internal_events/cli/helpers/formatting.rb b/scripts/internal_events/cli/helpers/formatting.rb index 92e0d4847d8..da5b392d07c 100755 --- a/scripts/internal_events/cli/helpers/formatting.rb +++ b/scripts/internal_events/cli/helpers/formatting.rb @@ -87,7 +87,7 @@ module InternalEventsCli # @param item [String] describes specific context ex) Chocolate Chip # @param count [Integer] ex) 2 # @param total [Integer] ex) 3 - def format_subheader(subject, item, count, total) + def format_subheader(subject, item, count = 1, total = 1) formatting_end = "\e[0m" suffix = formatting_end if subject[-formatting_end.length..] == formatting_end diff --git a/scripts/internal_events/cli/helpers/metric_options.rb b/scripts/internal_events/cli/helpers/metric_options.rb index 2da70bcd368..65ed973ccc2 100755 --- a/scripts/internal_events/cli/helpers/metric_options.rb +++ b/scripts/internal_events/cli/helpers/metric_options.rb @@ -15,37 +15,58 @@ module InternalEventsCli # disabled: [String] reason metrics are disabled def get_metric_options(events) selection = EventSelection.new(events) + available_options = [] + disabled_options = [] options = get_all_metric_options(selection.actions) - options = options.group_by do |metric| - [ - metric.identifier.value, - conflicting_metric_exists?(metric), - metric.filters_expected? - ] - end - options = options.filter_map do |(identifier, defined, filtered), metrics| + options.each do |metric| + identifier = metric.identifier.value + # Hide the filtered version of an option if unsupported; it just adds noise without value. Still, # showing unsupported options is valuable, because it advertises possibilities and explains why # those options aren't available. - next if filtered && !selection.can_be_unique?(identifier) - next if filtered && !selection.can_filter_when_unique?(identifier) + next if metric.filtered? && !selection.can_be_unique?(identifier) + next if metric.filtered? && !selection.can_filter_when_unique?(identifier) next if selection.exclude_filter_identifier?(identifier) - Option.new( - identifier: identifier, + option = Option.new( + metric: metric, events_name: selection.events_name, - filter_name: (selection.filter_name(identifier) if filtered), - metrics: metrics, - defined: defined, - supported: selection.can_be_unique?(identifier) - ).formatted + filter_name: (selection.filter_name(identifier) if metric.filtered?), + defined: false, + supported: true + ) + + conflicting_timeframes = get_conflicting_timeframes(metric) + available_timeframes = metric.time_frame.value - conflicting_timeframes + + if conflicting_timeframes.any? + disabled_metric = metric.dup + disabled_metric[:time_frame] = conflicting_timeframes + + disabled_option = option.dup + disabled_option.defined = true + disabled_option.metric = disabled_metric + + disabled_options << disabled_option.formatted + end + + next if available_timeframes.none? + + metric[:time_frame] = available_timeframes.sort + + if selection.can_be_unique?(identifier) + available_options << option.formatted + else + option.supported = false + disabled_options << option.formatted + end end # Push disabled options to the end for better skimability; # retain relative order for continuity - options.partition { |opt| !opt[:disabled] }.flatten + available_options + disabled_options end private @@ -57,54 +78,32 @@ module InternalEventsCli # @return [Array] def get_all_metric_options(actions) [ - Metric.new(actions: actions, time_frame: '28d', identifier: 'user'), - Metric.new(actions: actions, time_frame: '7d', identifier: 'user'), - Metric.new(actions: actions, time_frame: '28d', identifier: 'project'), - Metric.new(actions: actions, time_frame: '7d', identifier: 'project'), - Metric.new(actions: actions, time_frame: '28d', identifier: 'namespace'), - Metric.new(actions: actions, time_frame: '7d', identifier: 'namespace'), - Metric.new(actions: actions, time_frame: '28d', identifier: 'user', filters: []), - Metric.new(actions: actions, time_frame: '7d', identifier: 'user', filters: []), - Metric.new(actions: actions, time_frame: '28d', identifier: 'project', filters: []), - Metric.new(actions: actions, time_frame: '7d', identifier: 'project', filters: []), - Metric.new(actions: actions, time_frame: '28d', identifier: 'namespace', filters: []), - Metric.new(actions: actions, time_frame: '7d', identifier: 'namespace', filters: []), - Metric.new(actions: actions, time_frame: '28d'), - Metric.new(actions: actions, time_frame: '7d'), - Metric.new(actions: actions, time_frame: '28d', filters: []), - Metric.new(actions: actions, time_frame: '7d', filters: []), - Metric.new(actions: actions, time_frame: 'all'), - Metric.new(actions: actions, time_frame: 'all', filters: []), - Metric.new(actions: actions, time_frame: '28d', identifier: 'label'), - Metric.new(actions: actions, time_frame: '7d', identifier: 'label'), - Metric.new(actions: actions, time_frame: '28d', identifier: 'property'), - Metric.new(actions: actions, time_frame: '7d', identifier: 'property'), - Metric.new(actions: actions, time_frame: '28d', identifier: 'value'), - Metric.new(actions: actions, time_frame: '7d', identifier: 'value'), - Metric.new(actions: actions, time_frame: '28d', identifier: 'label', filters: []), - Metric.new(actions: actions, time_frame: '7d', identifier: 'label', filters: []), - Metric.new(actions: actions, time_frame: '28d', identifier: 'property', filters: []), - Metric.new(actions: actions, time_frame: '7d', identifier: 'property', filters: []), - Metric.new(actions: actions, time_frame: '28d', identifier: 'value', filters: []), - Metric.new(actions: actions, time_frame: '7d', identifier: 'value', filters: []) + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'user'), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'project'), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'namespace'), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'user', filters: []), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'project', filters: []), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'namespace', filters: []), + Metric.new(actions: actions, time_frame: %w[7d 28d all]), + Metric.new(actions: actions, time_frame: %w[7d 28d all], filters: []), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'label'), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'property'), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'value'), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'label', filters: []), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'property', filters: []), + Metric.new(actions: actions, time_frame: %w[7d 28d], identifier: 'value', filters: []) ] end - # Checks if there's an existing metric which has the same - # properties as the new one - # - # @param new_metric [NewMetric] - # @return [Boolean] - def conflicting_metric_exists?(new_metric) - # metrics with filters are conflict-free until new filters are defined - return false if new_metric.filters_expected? + def get_conflicting_timeframes(metric) + return [] if metric.filters_expected? - cli.global.metrics.any? do |existing_metric| - existing_metric.actions == new_metric.actions && - existing_metric.time_frame == new_metric.time_frame.value && - existing_metric.identifier == new_metric.identifier.value && - !existing_metric.filtered? - end + cli.global.metrics.flat_map do |existing_metric| + next if existing_metric.filtered? + next unless (existing_metric.unique_ids & metric.unique_ids).any? + + existing_metric.time_frame + end.compact.uniq end # Represents the attributes of set of events that depend on @@ -182,7 +181,7 @@ module InternalEventsCli # @param metrics [Array] # @option defined [Boolean] whether this metric already exists # @option supported [Boolean] whether unique metrics are supported for this identifier - Option = Struct.new(:identifier, :events_name, :filter_name, :metrics, :defined, :supported, + Option = Struct.new(:events_name, :filter_name, :metric, :defined, :supported, keyword_init: true) do include InternalEventsCli::Helpers::Formatting @@ -193,16 +192,16 @@ module InternalEventsCli name = [time_frame_phrase, identifier_phrase, filter_phrase].compact.join(' ') name = format_help(name) if disabled - { name: name, disabled: disabled, value: metrics }.compact + { name: name, disabled: disabled, value: metric }.compact end def identifier - Metric::Identifier.new(self[:identifier]) + metric.identifier end # ex) "Monthly/Weekly" def time_frame_phrase - phrase = metrics.map { |metric| metric.time_frame.description.capitalize }.join('/') + phrase = metric.time_frame.description disabled ? phrase : format_info(phrase) end diff --git a/scripts/internal_events/cli/metric.rb b/scripts/internal_events/cli/metric.rb index c156f888977..8c7c79042b0 100755 --- a/scripts/internal_events/cli/metric.rb +++ b/scripts/internal_events/cli/metric.rb @@ -60,12 +60,25 @@ module InternalEventsCli def time_frame self[:time_frame] || 'all' end + + # Enables comparison with new metrics + def unique_ids + prefix = [ + (actions || []).sort.join('+'), + identifier, + 'filter-', + filtered? + ].join('_') + + Array(time_frame).map { |t| prefix + t } + end end NewMetric = Struct.new(*NEW_METRIC_FIELDS, :identifier, :actions, :key, :filters, keyword_init: true) do def formatted_output METRIC_DEFAULTS .merge(to_h.compact) + .merge(time_frame: assign_time_frame) .merge( key_path: key_path, events: events) @@ -99,7 +112,7 @@ module InternalEventsCli end def time_frame - Metric::TimeFrame.new(self[:time_frame]) + Metric::TimeFrames.new(self[:time_frame]) end def identifier @@ -114,6 +127,18 @@ module InternalEventsCli Metric::Filters.new(self[:filters]) end + # Enables comparison with existing metrics + def unique_ids + prefix = [ + actions.sort.join('+'), + identifier.value, + 'filter-', + filtered? + ].join('_') + + time_frame.value.map { |t| prefix + t } + end + # Returns value for the `events` key in the metric definition. # Requires #actions or #filters to be set by the caller first. # @@ -138,7 +163,7 @@ module InternalEventsCli self[:actions] || [] end - # How to interpretting different values for filters: + # How to interpret different values for filters: # nil --> not expected, assigned or filtered # (metric not initialized with filters) # [] --> both expected and filtered @@ -160,13 +185,11 @@ module InternalEventsCli # ex) Weekly/Monthly count of unique # ex) Count of def description_prefix - description_components = [ - time_frame.description, + [ + (time_frame.description if time_frame.single?), identifier.prefix, *(identifier.plural if identifier.default?) - ].compact - - description_components.join(' ').capitalize + ].compact.join(' ').capitalize end # Provides simplified but technically accurate description @@ -175,7 +198,7 @@ module InternalEventsCli event_name = actions.first if events.length == 1 && !filtered? event_name ||= 'the selected events' [ - time_frame.description, + (time_frame.description if time_frame.single?), (identifier.description % event_name).to_s ].compact.join(' ').capitalize end @@ -183,24 +206,35 @@ module InternalEventsCli def bulk_assign(key_value_pairs) key_value_pairs.each { |key, value| self[key] = value } end + + # Maintain current functionality of string time_frame for backward compatibility + # TODO: Remove once we can deduplicate and merge metric files + def assign_time_frame + time_frame.single? ? time_frame.value.first : time_frame.value + end end class Metric - TimeFrame = Struct.new(:value) do + TimeFrames = Struct.new(:value) do def description - return if value.is_a? Array # array time_frame metrics have no description prefix - - TimeFramedKeyPath::METRIC_TIME_FRAME_DESC[value] + (%w[all 28d 7d] & value).map do |time_trame| + TimeFramedKeyPath::METRIC_TIME_FRAME_DESC[time_trame].capitalize + end.join('/') end def directory_name - return "counts_all" if value.is_a? Array + return "counts_all" unless single? - "counts_#{value}" + "counts_#{value.first}" end def key_path - description&.downcase if %w[7d 28d].include?(value) + description&.downcase if single? && %w[7d 28d].include?(value.first) + end + + # TODO: Delete once we are able to deduplicate and merge metric files + def single? + !value.is_a?(Array) || value.length == 1 end end diff --git a/spec/factories/work_items.rb b/spec/factories/work_items.rb index 9641b909114..6c1db5bd73b 100644 --- a/spec/factories/work_items.rb +++ b/spec/factories/work_items.rb @@ -26,6 +26,11 @@ FactoryBot.define do closed_at { Time.now } end + trait :closed_as_duplicate do + closed + association :duplicated_to, factory: :work_item + end + trait :group_level do project { nil } association :namespace, factory: :group diff --git a/spec/features/projects/active_tabs_spec.rb b/spec/features/projects/active_tabs_spec.rb index a80a89bdc9c..86f5ff8c11c 100644 --- a/spec/features/projects/active_tabs_spec.rb +++ b/spec/features/projects/active_tabs_spec.rb @@ -4,7 +4,9 @@ require 'spec_helper' RSpec.describe 'Project active tab', :js, feature_category: :groups_and_projects do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :repository, :with_namespace_settings, namespace: user.namespace) } + let_it_be(:project) do + create(:project, :custom_repo, :with_namespace_settings, namespace: user.namespace, files: { 'README.txt' => '' }) + end before do sign_in(user) @@ -48,10 +50,6 @@ RSpec.describe 'Project active tab', :js, feature_category: :groups_and_projects before do root_ref = project.repository.root_ref visit project_tree_path(project, root_ref) - - # Enabling Js in here causes more SQL queries to be caught by the query limiter. - # We are increasing the limit here so that the tests pass. - allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(110) end it_behaves_like 'page has active tab', 'Code' diff --git a/spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event_array_metric.yml b/spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event_array_metric.yml new file mode 100644 index 00000000000..982870f8d05 --- /dev/null +++ b/spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event_array_metric.yml @@ -0,0 +1,21 @@ +--- +key_path: redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used +description: Weekly count of unique users who defined an event using the CLI +product_group: analytics_instrumentation +product_categories: +- service_ping +performance_indicator_type: [] +value_type: number +status: active +milestone: '16.6' +introduced_by_url: TODO +time_frame: +- 7d +data_source: internal_events +data_category: optional +tiers: +- premium +- ultimate +events: +- name: internal_events_cli_used + unique: user.id diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index 63f4b365bc5..b81f500ea19 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -431,7 +431,7 @@ describe('init markdown', () => { }); }); - describe('Continuing indented text', () => { + describe('Maintain indented text', () => { let enterEvent; beforeAll(() => { @@ -454,15 +454,21 @@ describe('init markdown', () => { }; }); + // Note that the ` a` tests use an invisible newline followed by a space, `0A20` it.each` - text | markdownAutomaticLists | expected - ${' nice'} | ${true} | ${' nice\n '} - ${'  a'} | ${true} | ${'  a\n  '} - ${' - item'} | ${true} | ${' - item\n - '} - ${' - item'} | ${false} | ${' - item\n '} + text | markdownMaintainIndentation | markdownAutomaticLists | expected + ${' nice'} | ${true} | ${true} | ${' nice\n '} + ${'  a'} | ${true} | ${true} | ${'  a\n  '} + ${' - item'} | ${true} | ${true} | ${' - item\n - '} + ${' - item'} | ${true} | ${false} | ${' - item\n '} + ${' nice'} | ${false} | ${true} | ${' nice'} + ${'  a'} | ${false} | ${true} | ${'  a'} + ${' - item'} | ${false} | ${true} | ${' - item\n - '} + ${' - item'} | ${false} | ${false} | ${' - item'} `( - 'adds correct indentation characters with markdown_automatic_lists preference: $markdownAutomaticLists', - ({ text, markdownAutomaticLists, expected }) => { + 'adds correct indentation characters based on user preference markdownMaintainIndentation', + ({ text, markdownMaintainIndentation, markdownAutomaticLists, expected }) => { + gon.markdown_maintain_indentation = markdownMaintainIndentation; gon.markdown_automatic_lists = markdownAutomaticLists; textArea.value = text; textArea.setSelectionRange(text.length, text.length); @@ -478,6 +484,8 @@ describe('init markdown', () => { const text = ' 日本語'; const expected = ' 日本語\n '; + gon.markdown_maintain_indentation = true; + textArea.dispatchEvent(new CompositionEvent('compositionstart')); textArea.value = text; diff --git a/spec/frontend/scripts/frontend/find_jest_predictive_tests.spec.js b/spec/frontend/scripts/frontend/find_jest_predictive_tests.spec.js new file mode 100644 index 00000000000..9d4f9a5a9f3 --- /dev/null +++ b/spec/frontend/scripts/frontend/find_jest_predictive_tests.spec.js @@ -0,0 +1,381 @@ +import { spawnSync } from 'node:child_process'; +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; + +import { + hasRequiredEnvironmentVariables, + getChangedFiles, + findJestTests, + collectTests, + logAndSaveMatchingTestFiles, +} from '../../../../scripts/frontend/find_jest_predictive_tests'; + +jest.mock('node:child_process'); +jest.mock('node:fs'); +jest.mock('node:path', () => ({ + ...jest.requireActual('node:path'), + dirname: jest.fn(), +})); + +describe('find_jest_predictive_tests', () => { + let originalEnv; + let consoleWarnSpy; + let consoleErrorSpy; + let processSpy; + const mockChangedFiles = [ + 'foo/bar.js', + 'spec/frontend/foo_spec.js', + 'app/assets/javascripts/foo.js', + ]; + + beforeEach(() => { + originalEnv = { ...process.env }; + + jest.clearAllMocks(); + + consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + processSpy = jest.spyOn(process, 'exitCode', 'set').mockImplementation(); + + // Setup default environment variables + process.env.JEST_MATCHING_TEST_FILES_PATH = '/tmp/jest_matching_tests.txt'; + process.env.RSPEC_MATCHING_JS_FILES_PATH = '/tmp/js_files.txt'; + process.env.RSPEC_CHANGED_FILES_PATH = '/tmp/changed_files.txt'; + }); + + afterEach(() => { + process.env = originalEnv; + jest.restoreAllMocks(); + }); + + describe('environment variable checks', () => { + it('does not throw warning when all required env variables are set', () => { + hasRequiredEnvironmentVariables(); + expect(consoleWarnSpy).not.toHaveBeenCalled(); + }); + + it('warns when environment variables are missing', () => { + delete process.env.JEST_MATCHING_TEST_FILES_PATH; + delete process.env.RSPEC_MATCHING_JS_FILES_PATH; + + hasRequiredEnvironmentVariables(); + expect(processSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy).toHaveBeenCalledWith( + 'Warning: Missing required environment variables: JEST_MATCHING_TEST_FILES_PATH, RSPEC_MATCHING_JS_FILES_PATH', + ); + expect(consoleWarnSpy).toHaveBeenCalledWith('Some functionality may not work as expected.'); + }); + }); + + describe('find changed files', () => { + it('reads and combines files from both sources', () => { + readFileSync.mockImplementation((path) => { + if (path === '/tmp/js_files.txt') { + return 'app/assets/javascripts/foo.js\napp/assets/javascripts/bar.js'; + } + if (path === '/tmp/changed_files.txt') { + return 'spec/frontend/foo_spec.js\napp/assets/javascripts/bar.js'; + } + return ''; + }); + + const files = getChangedFiles(); + + expect(files).toEqual( + expect.arrayContaining([ + 'app/assets/javascripts/foo.js', + 'app/assets/javascripts/bar.js', + 'spec/frontend/foo_spec.js', + ]), + ); + expect(readFileSync).toHaveBeenCalledTimes(2); + }); + + it('handles empty files gracefully', () => { + readFileSync.mockReturnValue(''); + + const files = getChangedFiles(); + + expect(files).toEqual([]); + }); + + it('handles files with extra whitespace', () => { + readFileSync.mockReturnValue(' file1.js \n\n file2.js \n '); + + const files = getChangedFiles(); + + expect(files).toEqual(['file1.js', 'file2.js']); + }); + + it('warns when files cannot be read', () => { + readFileSync.mockImplementation(() => { + throw new Error('File not found'); + }); + + const files = getChangedFiles(); + + expect(files).toEqual([]); + expect(consoleWarnSpy).toHaveBeenCalledTimes(2); + }); + + it('deduplicates files across sources', () => { + readFileSync.mockReturnValue('duplicate.js\nduplicate.js\nunique.js'); + + const files = getChangedFiles(); + + expect(files).toEqual(['duplicate.js', 'unique.js']); + }); + }); + + describe('Find and list jest tests', () => { + it('returns full test list', () => { + spawnSync.mockReturnValue({ + status: 0, + stdout: Array(100) + .fill(0) + .map((_, index) => `spec/frontend/${index}_spec.js`) + .join('\n'), + stderr: '', + }); + + const result = findJestTests('jest.config.js'); + + expect(result).toHaveLength(100); + }); + + it('returns empty array for empty changed files', () => { + const result = findJestTests('jest.config.js', []); + + expect(result).toEqual([]); + expect(spawnSync).not.toHaveBeenCalled(); + }); + + it('spawns jest with correct arguments', () => { + spawnSync.mockReturnValue({ + status: 0, + stdout: 'spec/frontend/foo_spec.js\nspec/frontend/bar_spec.js', + stderr: '', + }); + + const changedFiles = ['app/assets/javascripts/foo.js']; + const config = 'jest.config.js'; + + findJestTests(config, changedFiles); + + expect(spawnSync).toHaveBeenCalledWith( + expect.stringContaining('node_modules/.bin/jest'), + [ + '--ci', + '--config', + 'jest.config.js', + '--listTests', + '--findRelatedTests', + 'app/assets/javascripts/foo.js', + ], + { + encoding: 'utf8', + stdio: 'pipe', + env: process.env, + }, + ); + }); + + it('filters out non-test files from output', () => { + spawnSync.mockReturnValue({ + status: 0, + stdout: ` + Determining test suites to run... + spec/frontend/components/alert_spec.js + spec/frontend/components/button_spec.js + ee/spec/frontend/components/chart_spec.js + Test Suites: 3 tests total + `, + stderr: '', + }); + + const changedFiles = ['components/alert.js']; + const result = findJestTests('jest.config.js', changedFiles); + + expect(result).toEqual([ + 'spec/frontend/components/alert_spec.js', + 'spec/frontend/components/button_spec.js', + 'ee/spec/frontend/components/chart_spec.js', + ]); + }); + + it('throws error when jest fails', () => { + const status = 11; + const stderr = 'Jest configuration error'; + + spawnSync.mockReturnValue({ + status, + stdout: '', + stderr, + }); + + const changedFiles = ['foo.js']; + + expect(() => { + findJestTests('jest.config.js', changedFiles); + }).toThrow( + `Failed to run Jest with config jest.config.js: Jest exited with code ${status}: ${stderr}`, + ); + }); + + it('handles absolute paths correctly', () => { + const cwd = process.cwd(); + spawnSync.mockReturnValue({ + status: 0, + stdout: `${cwd}/spec/frontend/foo_spec.js\n${cwd}/ee/spec/frontend/bar_spec.js`, + stderr: '', + }); + + const changedFiles = ['foo.js']; + const result = findJestTests('jest.config.js', changedFiles); + + expect(result).toEqual(['spec/frontend/foo_spec.js', 'ee/spec/frontend/bar_spec.js']); + }); + }); + + describe('collectTests', () => { + it('returns empty array when no changed files', () => { + const result = collectTests([]); + + expect(spawnSync).not.toHaveBeenCalled(); + expect(result).toEqual([]); + }); + + it('collects tests from both unit and integration configs', () => { + spawnSync.mockImplementation((_, args) => { + const config = args[2]; // --config value + if (config === 'jest.config.js') { + return { + status: 0, + stdout: 'spec/frontend/foo_spec.js\nspec/frontend/bar_spec.js', + }; + } + if (config === 'jest.config.integration.js') { + return { + status: 0, + stdout: 'spec/frontend_integration/baz_spec.js', + }; + } + return { status: 1, stderr: 'Unknown config' }; + }); + + const result = collectTests(mockChangedFiles); + + expect(result).toEqual([ + 'spec/frontend/bar_spec.js', + 'spec/frontend/foo_spec.js', + 'spec/frontend_integration/baz_spec.js', + ]); + }); + + it('deduplicates tests across configs', () => { + spawnSync.mockReturnValue({ + status: 0, + stdout: 'spec/frontend/foo_spec.js\nspec/frontend/foo_spec.js', + }); + + const result = collectTests(mockChangedFiles); + + expect(result).toEqual(['spec/frontend/foo_spec.js']); + }); + + it('continues when one config fails', () => { + spawnSync.mockImplementation((_, args) => { + const config = args[2]; + if (config === 'jest.config.js') { + return { status: 1, stderr: 'Config error' }; + } + return { + status: 0, + stdout: 'spec/frontend_integration/test_spec.js', + }; + }); + + const result = collectTests(mockChangedFiles); + + expect(result).toEqual(['spec/frontend_integration/test_spec.js']); + expect(consoleErrorSpy).toHaveBeenCalledTimes(1); + }); + + it('sorts tests alphabetically', () => { + spawnSync.mockReturnValue({ + status: 0, + stdout: 'spec/frontend/z_spec.js\nspec/frontend/a_spec.js\nspec/frontend/m_spec.js', + }); + + const result = collectTests(mockChangedFiles); + + expect(result).toEqual([ + 'spec/frontend/a_spec.js', + 'spec/frontend/m_spec.js', + 'spec/frontend/z_spec.js', + ]); + }); + }); + + describe('logAndSaveMatchingTestFiles', () => { + beforeEach(() => { + spawnSync.mockReturnValue({ + status: 0, + stdout: 'spec/frontend/foo_spec.js\nspec/frontend/bar_spec.js', + }); + dirname.mockReturnValue('/tmp'); + }); + + it('saves test files output', () => { + logAndSaveMatchingTestFiles(mockChangedFiles); + + expect(mkdirSync).toHaveBeenCalledWith('/tmp', { recursive: true }); + expect(writeFileSync).toHaveBeenCalledWith( + '/tmp/jest_matching_tests.txt', + 'spec/frontend/bar_spec.js spec/frontend/foo_spec.js', + ); + }); + + it('formats output as space separated values for CI consumption', () => { + logAndSaveMatchingTestFiles(mockChangedFiles); + + expect(writeFileSync).toHaveBeenCalledWith( + expect.any(String), + expect.stringMatching(/^[^\n]+( [^\n]+)*$/), + ); + }); + + it('does not save when JEST_MATCHING_TEST_FILES_PATH is not set', () => { + delete process.env.JEST_MATCHING_TEST_FILES_PATH; + + logAndSaveMatchingTestFiles(mockChangedFiles); + + expect(mkdirSync).not.toHaveBeenCalled(); + expect(writeFileSync).not.toHaveBeenCalled(); + }); + + it('handles empty test results', () => { + spawnSync.mockReturnValue({ + status: 0, + stdout: '', + }); + + logAndSaveMatchingTestFiles(mockChangedFiles); + + expect(writeFileSync).toHaveBeenCalledWith('/tmp/jest_matching_tests.txt', ''); + }); + + it('creates nested directories if needed', () => { + process.env.JEST_MATCHING_TEST_FILES_PATH = '/deeply/nested/path/tests.txt'; + dirname.mockReturnValue('/deeply/nested/path'); + + logAndSaveMatchingTestFiles(mockChangedFiles); + + expect(mkdirSync).toHaveBeenCalledWith('/deeply/nested/path', { recursive: true }); + expect(writeFileSync).toHaveBeenCalledWith( + '/deeply/nested/path/tests.txt', + 'spec/frontend/bar_spec.js spec/frontend/foo_spec.js', + ); + }); + }); +}); diff --git a/spec/frontend/todos/components/todo_item_body_spec.js b/spec/frontend/todos/components/todo_item_body_spec.js index 8facfc73961..ba386f0d1cf 100644 --- a/spec/frontend/todos/components/todo_item_body_spec.js +++ b/spec/frontend/todos/components/todo_item_body_spec.js @@ -1,5 +1,8 @@ import { shallowMount } from '@vue/test-utils'; import { GlLink, GlAvatar, GlAvatarLink } from '@gitlab/ui'; +import TodoItemTitle from '~/todos/components/todo_item_title.vue'; +import TodoItemTitleHiddenBySaml from '~/todos/components/todo_item_title_hidden_by_saml.vue'; + import TodoItemBody from '~/todos/components/todo_item_body.vue'; import { TODO_ACTION_TYPE_ADDED_APPROVER, @@ -51,6 +54,16 @@ describe('TodoItemBody', () => { }); }; + it('renders TodoItemTitle component for normal todos', () => { + createComponent(); + expect(wrapper.findComponent(TodoItemTitle).exists()).toBe(true); + }); + + it('renders TodoItemTitleHiddenBySaml component for hidden todos', () => { + createComponent({}, { isHiddenBySaml: true }); + expect(wrapper.findComponent(TodoItemTitleHiddenBySaml).exists()).toBe(true); + }); + it('renders author avatar', () => { createComponent(); expect(wrapper.findComponent(GlAvatarLink).exists()).toBe(true); diff --git a/spec/frontend/todos/components/todo_item_spec.js b/spec/frontend/todos/components/todo_item_spec.js index 8aad2d8ec28..5c110c15b14 100644 --- a/spec/frontend/todos/components/todo_item_spec.js +++ b/spec/frontend/todos/components/todo_item_spec.js @@ -1,15 +1,13 @@ import { shallowMount } from '@vue/test-utils'; import { GlFormCheckbox, GlLink } from '@gitlab/ui'; import TodoItem from '~/todos/components/todo_item.vue'; -import TodoItemTitle from '~/todos/components/todo_item_title.vue'; -import TodoItemTitleHiddenBySaml from '~/todos/components/todo_item_title_hidden_by_saml.vue'; import TodoItemBody from '~/todos/components/todo_item_body.vue'; import TodoItemTimestamp from '~/todos/components/todo_item_timestamp.vue'; import TodoItemActions from '~/todos/components/todo_item_actions.vue'; import { TODO_STATE_DONE, TODO_STATE_PENDING } from '~/todos/constants'; import { useFakeDate } from 'helpers/fake_date'; import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper'; -import { SAML_HIDDEN_TODO, MR_REVIEW_REQUEST_TODO } from '../mock_data'; +import { MR_REVIEW_REQUEST_TODO } from '../mock_data'; describe('TodoItem', () => { let wrapper; @@ -42,16 +40,6 @@ describe('TodoItem', () => { expect(wrapper.exists()).toBe(true); }); - it('renders TodoItemTitle component for normal todos', () => { - createComponent(); - expect(wrapper.findComponent(TodoItemTitle).exists()).toBe(true); - }); - - it('renders TodoItemTitleHiddenBySaml component for hidden todos', () => { - createComponent({ todo: SAML_HIDDEN_TODO }); - expect(wrapper.findComponent(TodoItemTitleHiddenBySaml).exists()).toBe(true); - }); - it('renders TodoItemBody component', () => { createComponent(); expect(wrapper.findComponent(TodoItemBody).exists()).toBe(true); diff --git a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb index a8df45bdb92..2b197780d1c 100644 --- a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb @@ -198,43 +198,19 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor it_behaves_like 'an internal reference' end - context 'when feature flag extensible_reference_filters is enabled' do - before do - stub_feature_flags(extensible_reference_filters: true) - end + context 'alternative [issue:XXX] internal reference' do + let(:written_reference) { "[issue:#{issue.iid}]" } + let(:reference) { "##{issue.iid}" } - context 'alternative [issue:XXX] internal reference' do - let(:written_reference) { "[issue:#{issue.iid}]" } - let(:reference) { "##{issue.iid}" } - - it_behaves_like 'an internal reference' - end - - context 'project [issue:project/path/XXX] reference' do - let(:reference) { "[issue:#{project.full_path}/#{issue.iid}]" } - - it_behaves_like 'a reference containing an element node' - - it_behaves_like 'a reference with issue type information' - end + it_behaves_like 'an internal reference' end - context 'when feature flag extensible_reference_filters is disabled' do - before do - stub_feature_flags(extensible_reference_filters: false) - stub_commonmark_sourcepos_disabled - end + context 'project [issue:project/path/XXX] reference' do + let(:reference) { "[issue:#{project.full_path}/#{issue.iid}]" } - it 'alternative [issue:XXX] reference does not work' do - doc = reference_filter("[issue:#{issue.iid}]") - expect(doc.to_html).to eq("

[issue:#{issue.iid}]

") - end + it_behaves_like 'a reference containing an element node' - it 'project [issue:project/path/XXX] reference does not work' do - reference = "[issue:#{project.full_path}/#{issue.iid}]" - doc = reference_filter(reference) - expect(doc.to_html).to eq("

#{reference}

") - end + it_behaves_like 'a reference with issue type information' end context 'cross-project / cross-namespace complete reference' do diff --git a/spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb b/spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb index aec2f4fe01d..ef87e785601 100644 --- a/spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb @@ -161,50 +161,25 @@ RSpec.describe Banzai::Filter::References::WorkItemReferenceFilter, feature_cate it_behaves_like 'a work item reference' end - context 'when feature flag extensible_reference_filters is enabled' do - before do - stub_feature_flags(extensible_reference_filters: true) - end + context 'on [work_item:XXX] reference' do + let_it_be(:written_reference) { "[work_item:#{work_item.iid}]" } + let_it_be(:reference) { written_reference } + let_it_be(:inner_text) { written_reference } + let_it_be(:work_item_link_reference) { item_url(work_item) } + let_it_be(:work_item_url) { work_item_link_reference.gsub('work_items', 'issues') } - context 'on [work_item:XXX] reference' do - let_it_be(:written_reference) { "[work_item:#{work_item.iid}]" } - let_it_be(:reference) { written_reference } - let_it_be(:inner_text) { written_reference } - let_it_be(:work_item_link_reference) { item_url(work_item) } - let_it_be(:work_item_url) { work_item_link_reference.gsub('work_items', 'issues') } - - it_behaves_like 'a work item reference' - end - - context 'on cross project [work_item:project/path/XXX] reference' do - let_it_be(:work_item, reload: true) { create(:work_item, project: cross_project) } - let_it_be(:work_item_link_reference) { item_url(work_item) } - let_it_be(:work_item_url) { work_item_link_reference.gsub('work_items', 'issues') } - let_it_be(:written_reference) { "[work_item:#{cross_project.full_path}/#{work_item.iid}]" } - let_it_be(:reference) { written_reference } - let_it_be(:inner_text) { written_reference } - - it_behaves_like 'a work item reference' - end + it_behaves_like 'a work item reference' end - context 'when feature flag extensible_reference_filters is disabled' do - before do - stub_feature_flags(extensible_reference_filters: false) - stub_commonmark_sourcepos_disabled - end + context 'on cross project [work_item:project/path/XXX] reference' do + let_it_be(:work_item, reload: true) { create(:work_item, project: cross_project) } + let_it_be(:work_item_link_reference) { item_url(work_item) } + let_it_be(:work_item_url) { work_item_link_reference.gsub('work_items', 'issues') } + let_it_be(:written_reference) { "[work_item:#{cross_project.full_path}/#{work_item.iid}]" } + let_it_be(:reference) { written_reference } + let_it_be(:inner_text) { written_reference } - it 'alternative [work_item:XXX] reference does not work' do - doc = reference_filter("[work_item:#{work_item.iid}]") - expect(doc.to_html).to eq("

[work_item:#{work_item.iid}]

") - end - - it 'cross project [work_item:project/path/XXX] reference does not work' do - work_item = create(:issue, project: cross_project) - reference = "[work_item:#{cross_project.full_path}/#{work_item.iid}]" - doc = reference_filter(reference) - expect(doc.to_html).to eq("

#{reference}

") - end + it_behaves_like 'a work item reference' end # Example: diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb index c8a445d445c..3b10286add9 100644 --- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb +++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb @@ -564,24 +564,6 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer, :clean_gitlab_re serializer.serialize_relation({ merge_requests: { include: [] } }, batch_ids: exportable.merge_requests.pluck(:id)) end end - - context 'when :keyset_paginate_exported_merge_requests is disabled' do - before do - stub_feature_flags(keyset_paginate_exported_merge_requests: false) - end - - it 'calls json_writer.write_relation_array without using keyset pagination' do - expect(json_writer).to receive(:write_relation_array).with( - exportable_path, - :merge_requests, - exportable.merge_requests.map(&:to_json) - ) - - expect(Gitlab::Pagination::Keyset::Iterator).not_to receive(:new) - - serializer.serialize_relation({ merge_requests: { include: [] } }) - end - end end context 'when the record is a user' do diff --git a/spec/lib/gitlab/topology_service_client/health_service_spec.rb b/spec/lib/gitlab/topology_service_client/health_service_spec.rb new file mode 100644 index 00000000000..8b69c5ef38b --- /dev/null +++ b/spec/lib/gitlab/topology_service_client/health_service_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::TopologyServiceClient::HealthService, feature_category: :cell do + subject(:cell_service) { described_class.new } + + let(:service_class) { Grpc::Health::V1::Health::Stub } + let(:grpc_health_check_request_class) { Grpc::Health::V1::HealthCheckRequest } + let(:grpc_health_check_response_class) { Grpc::Health::V1::HealthCheckResponse } + + describe '#service_healthy?' do + before do + allow(Gitlab.config.cell).to receive(:enabled).and_return(configured_cell) + end + + context 'when topology service is disabled' do + let(:configured_cell) { false } + + it 'raises an error when topology service is not enabled' do + expect(Gitlab.config.cell).to receive(:enabled).and_return(false) + + expect { cell_service }.to raise_error(NotImplementedError) + end + end + + context 'when topology service is enabled' do + let(:configured_cell) { true } + + before do + allow_next_instance_of(service_class) do |instance| + allow(instance).to receive(:check).with(instance_of(grpc_health_check_request_class)).and_return( + grpc_health_check_response_class.new(status: grpc_status) + ) + end + end + + context 'when gRPC status is SERVING' do + let(:grpc_status) { :SERVING } + + it { expect(cell_service.service_healthy?).to be(true) } + end + + context 'when gRPC status is NOT_SERVING' do + let(:grpc_status) { :NOT_SERVING } + + it { expect(cell_service.service_healthy?).to be(false) } + end + + context 'when gRPC status is UNKNOWN' do + let(:grpc_status) { :UNKNOWN } + + it { expect(cell_service.service_healthy?).to be(false) } + end + end + + context 'when topology service is unavailable' do + let(:configured_cell) { true } + + before do + allow_next_instance_of(service_class) do |instance| + allow(instance).to receive(:check).with(instance_of(grpc_health_check_request_class)).and_raise( + GRPC::Unavailable + ) + end + end + + it { expect(cell_service.service_healthy?).to be(false) } + end + end +end diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 29d2b796f71..95da1ad7a0b 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -217,7 +217,7 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do describe 'state machine events' do describe 'start_cancel!' do - valid_statuses = Ci::HasStatus::CANCELABLE_STATUSES.map(&:to_sym) + [:manual] + valid_statuses = Ci::HasStatus::CANCELABLE_STATUSES.map(&:to_sym) # Invalid statuses are statuses that are COMPLETED_STATUSES or already canceling invalid_statuses = Ci::HasStatus::AVAILABLE_STATUSES.map(&:to_sym) - valid_statuses @@ -242,7 +242,7 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do end describe 'finish_cancel!' do - valid_statuses = Ci::HasStatus::CANCELABLE_STATUSES.map(&:to_sym) + [:manual, :canceling] + valid_statuses = Ci::HasStatus::CANCELABLE_STATUSES.map(&:to_sym) + [:canceling] invalid_statuses = Ci::HasStatus::AVAILABLE_STATUSES.map(&:to_sym) - valid_statuses valid_statuses.each do |status| it "transitions from #{status} to canceling" do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 87cb1c15b18..f8e6f2ad8d2 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -3601,6 +3601,27 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end + context 'when there is a manual action present in the pipeline' do + before do + create(:ci_build, :manual, pipeline: pipeline) + create(:ci_build, :running, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline).to be_cancelable + end + end + + context 'when there is only a manual action present in the pipeline' do + before do + create(:ci_build, :manual, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline).to be_cancelable + end + end + %i[success failed canceled].each do |status| context "when there is a build #{status}" do before do @@ -3622,16 +3643,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end end - - context 'when there is a manual action present in the pipeline' do - before do - create(:ci_build, :manual, pipeline: pipeline) - end - - it 'is not cancelable' do - expect(pipeline).not_to be_cancelable - end - end end describe '.cancelable' do diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 111a12c949b..7f964a4288d 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -187,7 +187,7 @@ RSpec.describe CommitStatus, feature_category: :continuous_integration do describe '.cancelable' do subject { described_class.cancelable } - %i[running pending waiting_for_resource waiting_for_callback preparing created scheduled].each do |status| + %i[running pending waiting_for_resource waiting_for_callback preparing created scheduled manual].each do |status| context "when #{status} commit status" do let!(:commit_status) { create(:commit_status, status, pipeline: pipeline) } @@ -195,7 +195,7 @@ RSpec.describe CommitStatus, feature_category: :continuous_integration do end end - %i[failed success skipped canceled manual].each do |status| + %i[failed success skipped canceled].each do |status| context "when #{status} commit status" do let!(:commit_status) { create(:commit_status, status, pipeline: pipeline) } diff --git a/spec/models/concerns/ci/has_status_spec.rb b/spec/models/concerns/ci/has_status_spec.rb index 9d629f9e6c2..fee9ff23800 100644 --- a/spec/models/concerns/ci/has_status_spec.rb +++ b/spec/models/concerns/ci/has_status_spec.rb @@ -335,11 +335,11 @@ RSpec.describe Ci::HasStatus, feature_category: :continuous_integration do describe '.cancelable' do subject { CommitStatus.cancelable } - %i[running pending waiting_for_callback waiting_for_resource preparing created scheduled].each do |status| + %i[running pending waiting_for_callback waiting_for_resource preparing created scheduled manual].each do |status| it_behaves_like 'containing the job', status end - %i[failed success skipped canceled manual].each do |status| + %i[failed success skipped canceled].each do |status| it_behaves_like 'not containing the job', status end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 08e1958afda..92d9559863a 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -2108,7 +2108,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end end - describe '#member_owners_excluding_project_bots' do + describe '#member_owners_excluding_project_bots_and_service_accounts' do let_it_be(:user) { create(:user) } let!(:member_owner) do @@ -2121,11 +2121,11 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns the member-owners' do - expect(group.member_owners_excluding_project_bots).to contain_exactly(member_owner) + expect(group.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner) end it 'preloads user and source' do - owner = group.member_owners_excluding_project_bots.first + owner = group.member_owners_excluding_project_bots_and_service_accounts.first expect(owner.association(:user).loaded?).to be_truthy expect(owner.association(:source).loaded?).to be_truthy @@ -2137,7 +2137,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns only the human member-owners' do - expect(group.member_owners_excluding_project_bots).to contain_exactly(member_owner) + expect(group.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner) end end @@ -2152,7 +2152,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns the member-owners' do - expect(group.member_owners_excluding_project_bots).to contain_exactly(member_owner) + expect(group.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner) end end @@ -2167,7 +2167,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns only direct member-owners' do - expect(group.member_owners_excluding_project_bots).to contain_exactly(member_owner) + expect(group.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner) end context 'when there is an invite in the linked group' do @@ -2176,7 +2176,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns only direct member-owners' do - expect(group.member_owners_excluding_project_bots).to contain_exactly(member_owner) + expect(group.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner) end end end @@ -2192,7 +2192,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns member-owners including parents' do - expect(subgroup.member_owners_excluding_project_bots).to contain_exactly(member_owner, member_owner_2) + expect(subgroup.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner, member_owner_2) end context 'with group sharing' do @@ -2205,7 +2205,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns member-owners including parents, and member-owners of the invited group' do - expect(subgroup.member_owners_excluding_project_bots).to contain_exactly(member_owner, member_owner_2, invited_group_owner) + expect(subgroup.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner, member_owner_2, invited_group_owner) end context 'when there is an invite in the linked group' do @@ -2215,7 +2215,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do end it 'returns member-owners including parents, and member-owners of the invited group' do - expect(subgroup.member_owners_excluding_project_bots).to contain_exactly(member_owner, member_owner_2, invited_group_owner) + expect(subgroup.member_owners_excluding_project_bots_and_service_accounts).to contain_exactly(member_owner, member_owner_2, invited_group_owner) end end end @@ -2226,7 +2226,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do let_it_be(:empty_group) { create(:group) } it 'returns an empty result' do - expect(empty_group.member_owners_excluding_project_bots).to be_empty + expect(empty_group.member_owners_excluding_project_bots_and_service_accounts).to be_empty end end @@ -2238,7 +2238,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do context 'and it is a direct member' do it 'does include blocked user' do - expect(group.member_owners_excluding_project_bots).to include(blocked_member) + expect(group.member_owners_excluding_project_bots_and_service_accounts).to include(blocked_member) end end @@ -2246,7 +2246,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do let!(:subgroup) { create(:group, parent: group) } it 'does include blocked user' do - expect(subgroup.member_owners_excluding_project_bots).to include(blocked_member) + expect(subgroup.member_owners_excluding_project_bots_and_service_accounts).to include(blocked_member) end end end @@ -4355,13 +4355,6 @@ RSpec.describe Group, feature_category: :groups_and_projects do it { is_expected.to be false } end - describe '#continue_indented_text_feature_flag_enabled?' do - it_behaves_like 'checks self and root ancestor feature flag' do - let(:feature_flag) { :continue_indented_text } - let(:feature_flag_method) { :continue_indented_text_feature_flag_enabled? } - end - end - describe '#glql_integration_feature_flag_enabled?' do it_behaves_like 'checks self and root ancestor feature flag' do let(:feature_flag) { :glql_integration } diff --git a/spec/models/members/last_group_owner_assigner_spec.rb b/spec/models/members/last_group_owner_assigner_spec.rb index 5e135665585..7ea43536b2f 100644 --- a/spec/models/members/last_group_owner_assigner_spec.rb +++ b/spec/models/members/last_group_owner_assigner_spec.rb @@ -151,11 +151,26 @@ RSpec.describe LastGroupOwnerAssigner, feature_category: :groups_and_projects do context 'when there are bot members' do context 'with a bot owner' do - specify do + before do create(:group_member, :owner, source: group, user: create(:user, :project_bot)) + end + it 'marks the human member as the last owner' do expect { assigner.execute }.to change(group_member, :last_owner) - .from(nil).to(true) + .from(nil).to(true) + end + end + end + + context 'when there are service account members' do + context 'with a service account owner' do + before do + create(:group_member, :owner, source: group, user: create(:user, :service_account)) + end + + it 'marks the human member as the last owner' do + expect { assigner.execute }.to change(group_member, :last_owner) + .from(nil).to(true) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 827597b528a..037deb45028 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -9478,16 +9478,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr end end - describe '#continue_indented_text_feature_flag_enabled?' do - let_it_be(:group_project) { create(:project, :in_subgroup) } - - it_behaves_like 'checks parent group and self feature flag' do - let(:feature_flag_method) { :continue_indented_text_feature_flag_enabled? } - let(:feature_flag) { :continue_indented_text } - let(:subject_project) { group_project } - end - end - describe '#work_items_beta_feature_flag_enabled?' do let_it_be(:group_project) { create(:project, :in_subgroup) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 67eaa5cf7b2..08fc03e6031 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -117,6 +117,9 @@ RSpec.describe User, feature_category: :user_profile do it { is_expected.to delegate_method(:markdown_automatic_lists).to(:user_preference) } it { is_expected.to delegate_method(:markdown_automatic_lists=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:markdown_maintain_indentation).to(:user_preference) } + it { is_expected.to delegate_method(:markdown_maintain_indentation=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:diffs_deletion_color).to(:user_preference) } it { is_expected.to delegate_method(:diffs_deletion_color=).to(:user_preference).with_arguments(:args) } diff --git a/spec/rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations_spec.rb b/spec/rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations_spec.rb deleted file mode 100644 index d72671401fe..00000000000 --- a/spec/rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'rubocop_spec_helper' -require_relative '../../../../rubocop/cop/migration/prevent_enabling_lock_retries_for_transactional_migrations' - -RSpec.describe RuboCop::Cop::Migration::PreventEnablingLockRetriesForTransactionalMigrations, feature_category: :database do - it 'adds an offense when the migration explicit calls for enable_lock_retries!' do - expect_offense(<<~RUBY) - class MyMigration < Gitlab::Database::Migration[2.2] - milestone '18.0' - - enable_lock_retries! - ^^^^^^^^^^^^^^^^^^^^ Avoid using `enable_lock_retries! for transactional migrations`. The lock-retry [...] - - def change - add_column :users, :column_id, :smallint - end - end - RUBY - end - - it "adds no offense if the migration doesn't calls enable_lock_retries!" do - expect_no_offenses(<<~RUBY) - class MyMigration < Gitlab::Database::Migration[2.2] - milestone '18.0' - - def change - add_column :users, :column_id, :smallint - end - end - RUBY - end -end diff --git a/spec/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction_spec.rb b/spec/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction_spec.rb deleted file mode 100644 index 1035ed2fb4a..00000000000 --- a/spec/rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require 'rubocop_spec_helper' -require_relative '../../../../rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction' - -RSpec.describe RuboCop::Cop::Migration::PreventGlobalEnableLockRetriesWithDisableDdlTransaction do - context 'when in migration' do - before do - allow(cop).to receive(:in_migration?).and_return(true) - end - - it 'registers an offense when `enable_lock_retries` and `disable_ddl_transaction` is used together' do - code = <<~RUBY - class SomeMigration < ActiveRecord::Migration[6.0] - enable_lock_retries! - disable_ddl_transaction! - end - RUBY - - expect_offense(<<~RUBY, node: code, msg: described_class::MSG) - class SomeMigration < ActiveRecord::Migration[6.0] - enable_lock_retries! - disable_ddl_transaction! - ^^^^^^^^^^^^^^^^^^^^^^^^ %{msg} - end - RUBY - end - - it 'registers no offense when `enable_lock_retries!` is used' do - expect_no_offenses(<<~RUBY) - class SomeMigration < ActiveRecord::Migration[6.0] - enable_lock_retries! - end - RUBY - end - - it 'registers no offense when `disable_ddl_transaction!` is used' do - expect_no_offenses(<<~RUBY) - class SomeMigration < ActiveRecord::Migration[6.0] - disable_ddl_transaction! - end - RUBY - end - end - - context 'when outside of migration' do - it 'registers no offense' do - expect_no_offenses(<<~RUBY) - class SomeMigration - enable_lock_retries! - disable_ddl_transaction! - end - RUBY - end - end -end diff --git a/spec/scripts/internal_events/cli/flows/metric_definer_spec.rb b/spec/scripts/internal_events/cli/flows/metric_definer_spec.rb index ca5046cbc9c..b48ea18fb28 100644 --- a/spec/scripts/internal_events/cli/flows/metric_definer_spec.rb +++ b/spec/scripts/internal_events/cli/flows/metric_definer_spec.rb @@ -138,7 +138,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f ‣ Monthly/Weekly count of unique users who triggered internal_events_cli_used Monthly/Weekly count of unique projects where internal_events_cli_used occurred Monthly/Weekly count of unique namespaces where internal_events_cli_used occurred - Monthly/Weekly/Total count of internal_events_cli_used occurrences + Total/Monthly/Weekly count of internal_events_cli_used occurrences TEXT with_cli_thread do @@ -161,7 +161,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f ‣ Monthly count of unique users who triggered internal_events_cli_used Monthly/Weekly count of unique projects where internal_events_cli_used occurred Monthly/Weekly count of unique namespaces where internal_events_cli_used occurred - Monthly/Weekly/Total count of internal_events_cli_used occurrences + Total/Monthly/Weekly count of internal_events_cli_used occurrences ✘ Weekly count of unique users who triggered internal_events_cli_used (already defined) TEXT @@ -186,7 +186,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f ‣ Monthly/Weekly count of unique users who triggered internal_events_cli_used Monthly/Weekly count of unique projects where internal_events_cli_used occurred Monthly/Weekly count of unique namespaces where internal_events_cli_used occurred - ✘ Monthly/Weekly/Total count of internal_events_cli_used occurrences (already defined) + ✘ Total/Monthly/Weekly count of internal_events_cli_used occurrences (already defined) TEXT with_cli_thread do @@ -240,7 +240,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f expected_output = <<~TEXT.chomp ‣ Monthly/Weekly count of unique users who triggered any of 2 events Monthly/Weekly count of unique namespaces where any of 2 events occurred - Monthly/Weekly/Total count of any of 2 events occurrences + Total/Monthly/Weekly count of any of 2 events occurrences ✘ Monthly/Weekly count of unique projects where any of 2 events occurred (already defined) TEXT @@ -264,7 +264,7 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f ]) expected_output = <<~TEXT.chomp - ‣ Monthly/Weekly/Total count of internal_events_cli_opened occurrences + ‣ Total/Monthly/Weekly count of internal_events_cli_opened occurrences ✘ Monthly/Weekly count of unique users who triggered internal_events_cli_opened (user unavailable) ✘ Monthly/Weekly count of unique projects where internal_events_cli_opened occurred (project unavailable) ✘ Monthly/Weekly count of unique namespaces where internal_events_cli_opened occurred (namespace unavailable) @@ -334,8 +334,8 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f Monthly/Weekly count of unique users who triggered internal_events_cli_used where label/property/value is... Monthly/Weekly count of unique projects where internal_events_cli_used occurred where label/property/value is... Monthly/Weekly count of unique namespaces where internal_events_cli_used occurred where label/property/value is... - Monthly/Weekly/Total count of internal_events_cli_used occurrences - Monthly/Weekly/Total count of internal_events_cli_used occurrences where label/property/value is... + Total/Monthly/Weekly count of internal_events_cli_used occurrences + Total/Monthly/Weekly count of internal_events_cli_used occurrences where label/property/value is... Monthly/Weekly count of unique values for 'label' from internal_events_cli_used occurrences Monthly/Weekly count of unique values for 'property' from internal_events_cli_used occurrences Monthly/Weekly count of unique values for 'value' from internal_events_cli_used occurrences @@ -376,8 +376,8 @@ RSpec.describe 'InternalEventsCli::Flows::MetricDefiner', :aggregate_failures, f Monthly/Weekly count of unique users who triggered any of 2 events where label/value/property is... Monthly/Weekly count of unique projects where any of 2 events occurred where label/value/property is... Monthly/Weekly count of unique namespaces where any of 2 events occurred where label/value/property is... - Monthly/Weekly/Total count of any of 2 events occurrences - Monthly/Weekly/Total count of any of 2 events occurrences where label/value/property is... + Total/Monthly/Weekly count of any of 2 events occurrences + Total/Monthly/Weekly count of any of 2 events occurrences where label/value/property is... Monthly/Weekly count of unique values for 'label' from any of 2 events occurrences Monthly/Weekly count of unique values for 'value' from any of 2 events occurrences Monthly/Weekly count of unique values for 'label' from any of 2 events occurrences where value/property is... diff --git a/spec/scripts/internal_events/cli/flows/usage_viewer_spec.rb b/spec/scripts/internal_events/cli/flows/usage_viewer_spec.rb index 007893bc790..f537045e064 100644 --- a/spec/scripts/internal_events/cli/flows/usage_viewer_spec.rb +++ b/spec/scripts/internal_events/cli/flows/usage_viewer_spec.rb @@ -100,37 +100,74 @@ RSpec.describe 'InternalEventsCli::Flows::UsageViewer', :aggregate_failures, fea TEXT end - before do - File.write(event1_filepath, File.read(event1_content)) - File.write( - 'config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml', - File.read('spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml') - ) + context 'for single metric in string time_frame format' do + before do + File.write(event1_filepath, File.read(event1_content)) + File.write( + 'config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml', + File.read('spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml') + ) + end + + it 'shows backend examples' do + queue_cli_inputs([ + "3\n", # Enum-select: View Usage -- look at code examples for an existing event + 'internal_events_cli_used', # Filters to this event + "\n", # Select: config/events/internal_events_cli_used.yml + "\n", # Select: ruby/rails + "\e[B", # Arrow down to: rspec + "\n", # Select: rspec + "7\n", # Select: Manual testing: check current values of metrics from rails console (any data source) + "8\n", # Select: Data verification in Tableau + "Exit", # Filters to this item + "\n" # select: Exit + ]) + + with_cli_thread do + expect { plain_last_lines(200) }.to eventually_include_cli_text( + expected_example_prompt, + expected_rails_example, + expected_rspec_example, + expected_gdk_example, + expected_tableau_example + ) + end + end end - it 'shows backend examples' do - queue_cli_inputs([ - "3\n", # Enum-select: View Usage -- look at code examples for an existing event - 'internal_events_cli_used', # Filters to this event - "\n", # Select: config/events/internal_events_cli_used.yml - "\n", # Select: ruby/rails - "\e[B", # Arrow down to: rspec - "\n", # Select: rspec - "7\n", # Select: Manual testing: check current values of metrics from rails console (any data source) - "8\n", # Select: Data verification in Tableau - "Exit", # Filters to this item - "\n" # select: Exit - ]) - - with_cli_thread do - expect { plain_last_lines(200) }.to eventually_include_cli_text( - expected_example_prompt, - expected_rails_example, - expected_rspec_example, - expected_gdk_example, - expected_tableau_example + context 'for single metric in array time_frame format' do + before do + File.write(event1_filepath, File.read(event1_content)) + File.write( + 'config/metrics/counts_all/count_distinct_user_id_from_internal_events_cli_used.yml', + File.read('spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event_array_metric.yml') ) end + + it 'shows backend examples' do + queue_cli_inputs([ + "3\n", # Enum-select: View Usage -- look at code examples for an existing event + 'internal_events_cli_used', # Filters to this event + "\n", # Select: config/events/internal_events_cli_used.yml + "\n", # Select: ruby/rails + "\e[B", # Arrow down to: rspec + "\n", # Select: rspec + "7\n", # Select: Manual testing: check current values of metrics from rails console (any data source) + "8\n", # Select: Data verification in Tableau + "Exit", # Filters to this item + "\n" # select: Exit + ]) + + with_cli_thread do + expect { plain_last_lines(200) }.to eventually_include_cli_text( + expected_example_prompt, + expected_rails_example, + expected_rspec_example, + expected_gdk_example, + expected_tableau_example + ) + end + end end end diff --git a/spec/scripts/internal_events/cli/helpers/metric_options_spec.rb b/spec/scripts/internal_events/cli/helpers/metric_options_spec.rb index aa3eb3ba7e3..e4c1837b104 100644 --- a/spec/scripts/internal_events/cli/helpers/metric_options_spec.rb +++ b/spec/scripts/internal_events/cli/helpers/metric_options_spec.rb @@ -12,17 +12,18 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego let(:supported) { true } let(:styling_stub) { Pastel.new } - let(:metrics) do - [instance_double(InternalEventsCli::NewMetric, - time_frame: instance_double(InternalEventsCli::Metric::TimeFrame, description: "time frame"))] + let(:metric) do + instance_double(InternalEventsCli::NewMetric, + time_frame: instance_double(InternalEventsCli::Metric::TimeFrames, description: "Time frame"), + identifier: InternalEventsCli::Metric::Identifier.new(identifier) + ) end subject(:option) do described_class.new( - identifier: identifier, events_name: events_name, filter_name: filter_name, - metrics: metrics, + metric: metric, defined: defined, supported: supported ) @@ -42,7 +43,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego it 'highlights key words in the name' do expect(option.formatted).to eq({ name: "Time frame count of unique users who triggered a list of events", - value: metrics + value: metric }) end @@ -53,7 +54,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego expect(option.formatted).to eq({ name: "Time frame count of unique users who triggered a list of events " \ "where label/prop/anything is...", - value: metrics + value: metric }) end end @@ -65,7 +66,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego it 'formats the option as disabled' do expect(option.formatted).to eq({ name: "Time frame count of unique users who triggered a list of events", - value: metrics, + value: metric, disabled: "(already defined)" }) end @@ -77,7 +78,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego expect(option.formatted).to eq({ name: "Time frame count of unique users who triggered " \ "a list of events where filtered", - value: metrics, + value: metric, disabled: "(already defined)" }) end @@ -90,7 +91,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego it 'formats the option as disabled' do expect(option.formatted).to eq({ name: "Time frame count of unique users who triggered a list of events", - value: metrics, + value: metric, disabled: "(user unavailable)" }) end @@ -102,7 +103,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego expect(option.formatted).to eq({ name: "Time frame count of unique users who triggered " \ "a list of events where filtered", - value: metrics, + value: metric, disabled: "(user unavailable)" }) end @@ -116,7 +117,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego expect(option.formatted).to eq({ name: "Time frame count of unique values for 'label' " \ "from a list of events occurrences", - value: metrics + value: metric }) end end @@ -127,26 +128,7 @@ RSpec.describe InternalEventsCli::Helpers::MetricOptions::Option, feature_catego it 'highlights key words in the name' do expect(option.formatted).to eq({ name: "Time frame count of a list of events occurrences", - value: metrics - }) - end - end - - context 'with multiple metrics' do - let(:metrics) do - [ - instance_double(InternalEventsCli::NewMetric, - time_frame: instance_double(InternalEventsCli::Metric::TimeFrame, description: "time frame 1")), - instance_double(InternalEventsCli::NewMetric, - time_frame: instance_double(InternalEventsCli::Metric::TimeFrame, description: "time frame 2")) - ] - end - - it 'highlights key words in the name' do - expect(option.formatted).to eq({ - name: "Time frame 1/Time frame 2 count of unique users " \ - "who triggered a list of events", - value: metrics + value: metric }) end end diff --git a/spec/scripts/internal_events/cli/metric_spec.rb b/spec/scripts/internal_events/cli/metric_spec.rb index b0a76bfc140..41362b9f5c9 100644 --- a/spec/scripts/internal_events/cli/metric_spec.rb +++ b/spec/scripts/internal_events/cli/metric_spec.rb @@ -5,7 +5,7 @@ require 'fast_spec_helper' require_relative '../../../../scripts/internal_events/cli' RSpec.describe InternalEventsCli::NewMetric, :aggregate_failures, feature_category: :service_ping do - let(:time_frame) { '7d' } + let(:time_frame) { ['7d'] } let(:identifier) { 'user' } let(:actions) { ['action_1'] } let(:filters) { nil } @@ -34,7 +34,7 @@ RSpec.describe InternalEventsCli::NewMetric, :aggregate_failures, feature_catego end context 'when all time' do - let(:time_frame) { 'all' } + let(:time_frame) { ['all'] } it 'has expected description content' do expect(metric.description_prefix).to eq('Total count of unique users') diff --git a/spec/services/ci/abort_pipelines_service_spec.rb b/spec/services/ci/abort_pipelines_service_spec.rb index 335d0cd1635..5ba62576ff3 100644 --- a/spec/services/ci/abort_pipelines_service_spec.rb +++ b/spec/services/ci/abort_pipelines_service_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Ci::AbortPipelinesService, feature_category: :continuous_integrat let_it_be(:non_cancelable_stage, reload: true) { create(:ci_stage, name: 'stageB', status: :success, pipeline: cancelable_pipeline, project: project) } let_it_be(:manual_pipeline_cancelable_build, reload: true) { create(:ci_build, :created, pipeline: manual_pipeline) } - let_it_be(:manual_pipeline_non_cancelable_build, reload: true) { create(:ci_build, :manual, pipeline: manual_pipeline) } + let_it_be(:manual_pipeline_manual_build, reload: true) { create(:ci_build, :manual, pipeline: manual_pipeline) } let_it_be(:manual_pipeline_cancelable_stage, reload: true) { create(:ci_stage, name: 'stageA', status: :created, pipeline: manual_pipeline, project: project) } let_it_be(:manual_pipeline_non_cancelable_stage, reload: true) { create(:ci_stage, name: 'stageB', status: :success, pipeline: manual_pipeline, project: project) } @@ -45,7 +45,7 @@ RSpec.describe Ci::AbortPipelinesService, feature_category: :continuous_integrat expect(manual_pipeline_cancelable_build.finished_at).not_to be_nil expect(non_cancelable_build).not_to be_failed - expect(manual_pipeline_non_cancelable_build).not_to be_failed + expect(manual_pipeline_manual_build).to be_failed end def expect_correct_cancellations diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb index ec7be926df1..04a51557783 100644 --- a/spec/tasks/gitlab/db_rake_spec.rb +++ b/spec/tasks/gitlab/db_rake_spec.rb @@ -27,6 +27,29 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor end end + shared_examples 'topology service unavailable' do |multiple_databases| + it 'skips loading the schema, seeding the database and altering cell sequences range' do + if multiple_databases + expect(Rake::Task['db:schema:load:main']).not_to receive(:invoke) + expect(Rake::Task['db:schema:load:ci']).not_to receive(:invoke) + + expect(Rake::Task['db:migrate:main']).not_to receive(:invoke) + expect(Rake::Task['db:migrate:ci']).not_to receive(:invoke) + else + expect(Rake::Task['db:schema:load']).not_to receive(:invoke) + expect(Rake::Task['db:migrate']).not_to receive(:invoke) + end + + expect(Rake::Task['gitlab:db:lock_writes']).not_to receive(:invoke) + expect(Rake::Task['db:seed_fu']).not_to receive(:invoke) + expect(Rake::Task['gitlab:db:alter_cell_sequences_range']).not_to receive(:invoke) + + expect { run_rake_task('gitlab:db:configure') }.to( + raise_error('Error: Topology Service is `UNAVAILABLE`. Exiting DB configuration.') + ) + end + end + describe 'gitlab:db:sos task' do before do Rake::Task['gitlab:db:sos'].reenable @@ -176,6 +199,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor let(:configured_cell) { true } let(:skip_sequence_alteration) { false } let(:sequence_ranges) { [Gitlab::Cells::TopologyService::SequenceRange.new(minval: 1, maxval: 1000)] } + let(:topology_service_healthy) { true } before do stub_config(cell: { enabled: true, id: 1, database: { skip_sequence_alteration: skip_sequence_alteration } }) @@ -188,6 +212,10 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor before do skip_if_database_exists(:ci) + allow_next_instance_of(Gitlab::TopologyServiceClient::HealthService) do |instance| + allow(instance).to receive(:service_healthy?).and_return(topology_service_healthy) + end + allow_next_instance_of(Gitlab::TopologyServiceClient::CellService) do |instance| allow(instance).to receive(:cell_sequence_ranges).and_return(sequence_ranges) end @@ -316,6 +344,12 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor run_rake_task('gitlab:db:configure') end end + + context 'when has cell configuration but topology service is unavailable' do + let(:topology_service_healthy) { false } + + it_behaves_like 'topology service unavailable', false + end end context 'when geo is configured' do @@ -358,6 +392,10 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor allow(Gitlab::Database).to receive(:database_base_models_with_gitlab_shared).and_return(base_models) + allow_next_instance_of(Gitlab::TopologyServiceClient::HealthService) do |instance| + allow(instance).to receive(:service_healthy?).and_return(topology_service_healthy) + end + allow_next_instance_of(Gitlab::TopologyServiceClient::CellService) do |instance| allow(instance).to receive(:cell_sequence_ranges).and_return(sequence_ranges) end @@ -439,6 +477,10 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor allow(main_model.connection).to receive(:tables).and_return(%w[schema_migrations]) allow(ci_model.connection).to receive(:tables).and_return([]) + allow_next_instance_of(Gitlab::TopologyServiceClient::HealthService) do |instance| + allow(instance).to receive(:service_healthy?).and_return(topology_service_healthy) + end + allow_next_instance_of(Gitlab::TopologyServiceClient::CellService) do |instance| allow(instance).to receive(:cell_sequence_ranges).and_return(nil) end @@ -483,6 +525,17 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor run_rake_task('gitlab:db:configure') end end + + context 'when has cell configuration but topology service is unavailable' do + let(:topology_service_healthy) { false } + + before do + allow(main_model.connection).to receive(:tables).and_return(%w[schema_migrations]) + allow(ci_model.connection).to receive(:tables).and_return([]) + end + + it_behaves_like 'topology service unavailable', true + end end context 'when geo is configured' do @@ -519,6 +572,10 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor allow(ActiveRecord::Base).to receive_message_chain('configurations.configs_for').and_return([main_config]) allow(connection).to receive(:tables).and_return(%w[table1 table2]) allow(Rake::Task['db:migrate']).to receive(:invoke) + + allow_next_instance_of(Gitlab::TopologyServiceClient::HealthService) do |instance| + allow(instance).to receive(:service_healthy?).and_return(topology_service_healthy) + end end it 'migrates clickhouse database' do diff --git a/spec/tooling/danger/change_column_default_spec.rb b/spec/tooling/danger/change_column_default_spec.rb index 8cfcbfa1dc0..350a6caf5e2 100644 --- a/spec/tooling/danger/change_column_default_spec.rb +++ b/spec/tooling/danger/change_column_default_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Tooling::Danger::ChangeColumnDefault, feature_category: :tooling describe '#add_comment_for_change_column_default' do let(:file_lines) { file_diff.map { |line| line.delete_prefix('+').delete_prefix('-') } } - let(:matching_lines) { [7, 9, 11] } + let(:matching_lines) { [5, 7, 9] } before do allow(change_column_default).to receive(:project_helper).and_return(fake_project_helper) diff --git a/spec/tooling/fixtures/change_column_default_migration.txt b/spec/tooling/fixtures/change_column_default_migration.txt index a74c31464ee..87a17838bb8 100644 --- a/spec/tooling/fixtures/change_column_default_migration.txt +++ b/spec/tooling/fixtures/change_column_default_migration.txt @@ -1,8 +1,6 @@ +# frozen_string_literal: true + +class TestMigration < Gitlab::Database::Migration[2.1] -+ enable_lock_retries! -+ + def change + change_column_default('ci_builds', 'partition_id', from: 100, to: 101) +