From f5470434fb74ef90363b13d13545c46c70cdc2df Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 13 Feb 2025 18:12:49 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_todo/gitlab/bounded_contexts.yml | 1 + .rubocop_todo/rspec/receive_messages.yml | 1 - Gemfile | 2 +- Gemfile.checksum | 14 +- Gemfile.lock | 24 ++- Gemfile.next.checksum | 14 +- Gemfile.next.lock | 24 ++- .../search/results/components/blob_chunks.vue | 8 +- .../search/results/components/blob_footer.vue | 2 +- .../vue_shared/components/import/constants.js | 37 ++++ ...t_history_table_row_destination.stories.js | 21 ++ .../import_history_table_row_destination.vue | 64 ++++++ .../projects/ml/experiments_controller.rb | 11 +- app/helpers/projects/ml/experiments_helper.rb | 19 -- .../group_root_ancestor_preloader.rb | 24 +-- .../namespace_root_ancestor_preloader.rb | 29 +++ app/models/user_synced_attributes_metadata.rb | 2 +- app/services/merge_requests/squash_service.rb | 2 +- app/services/todo_service.rb | 23 +-- .../user_settings/profiles/show.html.haml | 12 +- .../development/multiple_todos.yml | 8 - .../17-9-vuln-object-retention-limits.yml | 17 ++ ...nced_to_user_synced_attributes_metadata.rb | 10 + ...er_watermark_event_worker_job_instances.rb | 16 ++ db/schema_migrations/20241213025719 | 1 + db/schema_migrations/20250212132308 | 1 + db/structure.sql | 4 +- .../gitlab_duo_self_hosted/_index.md | 3 + .../configure_duo_features.md | 4 +- .../gitlab_duo_self_hosted/logging.md | 1 - .../supported_llm_serving_platforms.md | 43 +++- .../container_registry_metadata_database.md | 57 +++++- doc/integration/omniauth.md | 90 ++++++++- doc/integration/saml.md | 165 +++++++++++++++ .../integrations/aws_googlecloud_ollama.md | 6 +- doc/update/deprecations.md | 20 ++ .../agent/managed_kubernetes_resources.md | 2 +- doc/user/glql/fields.md | 2 +- .../clusters/migrate_to_gitlab_agent.md | 3 + doc/user/project/pages/_index.md | 2 - lib/gitlab/auth/o_auth/auth_hash.rb | 15 +- lib/web_ide/extensions_marketplace.rb | 4 +- lib/web_ide/settings/default_settings.rb | 6 +- ...tension_marketplace_metadata_generator.rb} | 8 +- ...tension_marketplace_metadata_validator.rb} | 4 +- ....rb => extension_marketplace_validator.rb} | 4 +- ...nsion_marketplace_view_model_generator.rb} | 12 +- lib/web_ide/settings/main.rb | 14 +- lib/web_ide/settings/messages.rb | 4 +- lib/web_ide/settings/settings_initializer.rb | 5 +- locale/gitlab.pot | 23 +++ .../results/components/blob_footer_spec.js | 38 +--- .../import_history_table/mock_data.js | 190 ++++++++++++++++++ spec/helpers/profiles_helper_spec.rb | 8 +- .../projects/ml/experiments_helper_spec.rb | 27 --- spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb | 24 ++- spec/lib/gitlab/auth/o_auth/user_spec.rb | 20 +- ...on_marketplace_metadata_generator_spec.rb} | 2 +- ...on_marketplace_metadata_validator_spec.rb} | 4 +- ...> extension_marketplace_validator_spec.rb} | 4 +- ..._marketplace_view_model_generator_spec.rb} | 8 +- spec/lib/web_ide/settings/main_spec.rb | 26 +-- .../settings/settings_initializer_spec.rb | 6 +- .../settings/settings_integration_spec.rb | 7 +- .../namespace_root_ancestor_preloader_spec.rb | 68 +++++++ .../ml/experiments_controller_spec.rb | 41 ---- spec/services/todo_service_spec.rb | 167 ++------------- 67 files changed, 1086 insertions(+), 442 deletions(-) create mode 100644 app/assets/javascripts/vue_shared/components/import/constants.js create mode 100644 app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.stories.js create mode 100644 app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.vue create mode 100644 app/models/preloaders/namespace_root_ancestor_preloader.rb delete mode 100644 config/feature_flags/development/multiple_todos.yml create mode 100644 data/deprecations/17-9-vuln-object-retention-limits.yml create mode 100644 db/migrate/20241213025719_add_organization_synced_and_job_title_synced_to_user_synced_attributes_metadata.rb create mode 100644 db/migrate/20250212132308_remove_index_over_watermark_event_worker_job_instances.rb create mode 100644 db/schema_migrations/20241213025719 create mode 100644 db/schema_migrations/20250212132308 rename lib/web_ide/settings/{extensions_gallery_metadata_generator.rb => extension_marketplace_metadata_generator.rb} (93%) rename lib/web_ide/settings/{extensions_gallery_metadata_validator.rb => extension_marketplace_metadata_validator.rb} (94%) rename lib/web_ide/settings/{extensions_gallery_validator.rb => extension_marketplace_validator.rb} (92%) rename lib/web_ide/settings/{extensions_gallery_view_model_generator.rb => extension_marketplace_view_model_generator.rb} (85%) create mode 100644 spec/frontend/vue_shared/components/import_history_table/mock_data.js rename spec/lib/web_ide/settings/{extensions_gallery_metadata_generator_spec.rb => extension_marketplace_metadata_generator_spec.rb} (97%) rename spec/lib/web_ide/settings/{extensions_gallery_metadata_validator_spec.rb => extension_marketplace_metadata_validator_spec.rb} (95%) rename spec/lib/web_ide/settings/{extensions_gallery_validator_spec.rb => extension_marketplace_validator_spec.rb} (95%) rename spec/lib/web_ide/settings/{extensions_gallery_view_model_generator_spec.rb => extension_marketplace_view_model_generator_spec.rb} (88%) create mode 100644 spec/models/preloaders/namespace_root_ancestor_preloader_spec.rb diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml index ab20369d312..ace29133a80 100644 --- a/.rubocop_todo/gitlab/bounded_contexts.yml +++ b/.rubocop_todo/gitlab/bounded_contexts.yml @@ -1126,6 +1126,7 @@ Gitlab/BoundedContexts: - 'app/models/preloaders/environments/deployment_preloader.rb' - 'app/models/preloaders/group_policy_preloader.rb' - 'app/models/preloaders/group_root_ancestor_preloader.rb' + - 'app/models/preloaders/namespace_root_ancestor_preloader.rb' - 'app/models/preloaders/labels_preloader.rb' - 'app/models/preloaders/merge_request_diff_preloader.rb' - 'app/models/preloaders/project_policy_preloader.rb' diff --git a/.rubocop_todo/rspec/receive_messages.yml b/.rubocop_todo/rspec/receive_messages.yml index f245fbef874..3693279dda2 100644 --- a/.rubocop_todo/rspec/receive_messages.yml +++ b/.rubocop_todo/rspec/receive_messages.yml @@ -52,7 +52,6 @@ RSpec/ReceiveMessages: - 'ee/spec/lib/ee/gitlab/ci/parsers/security/validators/schema_validator_spec.rb' - 'ee/spec/lib/ee/gitlab/gon_helper_spec.rb' - 'ee/spec/lib/ee/gitlab/rack_attack/request_spec.rb' - - 'ee/spec/lib/ee/web_ide/settings/extensions_gallery_metadata_generator_spec.rb' - 'ee/spec/lib/elastic/latest/custom_language_analyzers_spec.rb' - 'ee/spec/lib/elastic/latest/git_instance_proxy_spec.rb' - 'ee/spec/lib/elastic/multi_version_class_proxy_spec.rb' diff --git a/Gemfile b/Gemfile index f7a51d3bce7..d55b3b0616c 100644 --- a/Gemfile +++ b/Gemfile @@ -436,7 +436,7 @@ gem 'prometheus-client-mmap', '~> 1.2.8', require: 'prometheus/client', feature_ # Event-driven reactor for Ruby # Required manually in config/initializers/require_async_gem -gem 'async', '~> 2.12.1', require: false, feature_category: :shared +gem 'async', '~> 2.22.0', require: false, feature_category: :shared # Security report schemas used to validate CI job artifacts of security jobs gem 'gitlab-security_report_schemas', '0.1.2.min15.0.0.max15.2.1', feature_category: :vulnerability_management diff --git a/Gemfile.checksum b/Gemfile.checksum index d44710a2f3c..1328a0faa47 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -28,7 +28,7 @@ {"name":"asciidoctor-kroki","version":"0.10.0","platform":"ruby","checksum":"8e4225d88f120e2e7b5d3f5ddb67c5e69496d7344a16c57db5036ac900123062"}, {"name":"asciidoctor-plantuml","version":"0.0.16","platform":"ruby","checksum":"407e47cd1186ded5ccc75f0c812e5524c26c571d542247c5132abb8f47bd1793"}, {"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"}, -{"name":"async","version":"2.12.1","platform":"ruby","checksum":"146fb3acf6d05ad40abb9ae659dd3b574067a3420fe7d6d5d6a3cf5413de3ea5"}, +{"name":"async","version":"2.22.0","platform":"ruby","checksum":"63abba84615ec0fa31e4e0e1eea1ef26bf7908137a85ae27612fda2c6f51cc2d"}, {"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"}, {"name":"attr_required","version":"1.0.2","platform":"ruby","checksum":"f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99"}, {"name":"awesome_print","version":"1.9.2","platform":"ruby","checksum":"e99b32b704acff16d768b3468680793ced40bfdc4537eb07e06a4be11133786e"}, @@ -86,7 +86,7 @@ {"name":"commonmarker","version":"0.23.11","platform":"ruby","checksum":"9d1d35d358740151bce29235aebfecc63314fb57dd89a83e72d4061b4fe3d2bf"}, {"name":"concurrent-ruby","version":"1.2.3","platform":"ruby","checksum":"82fdd3f8a0816e28d513e637bb2b90a45d7b982bdf4f3a0511722d2e495801e2"}, {"name":"connection_pool","version":"2.5.0","platform":"ruby","checksum":"233b92f8d38e038c1349ccea65dd3772727d669d6d2e71f9897c8bf5cd53ebfc"}, -{"name":"console","version":"1.25.2","platform":"ruby","checksum":"460fbf8c1b0e527b2c275448b76f91c3e9fb72e6bead5d27fb5a638fc191e943"}, +{"name":"console","version":"1.29.2","platform":"ruby","checksum":"afd9b75a1b047059dda22df0e3c0a386e96f50f6752c87c4b00b1a9fcbe77cd6"}, {"name":"cork","version":"0.3.0","platform":"ruby","checksum":"a0a0ac50e262f8514d1abe0a14e95e71c98b24e3378690e5d044daf0013ad4bc"}, {"name":"cose","version":"1.3.0","platform":"ruby","checksum":"63247c66a5bc76e53926756574fe3724cc0a88707e358c90532ae2a320e98601"}, {"name":"countries","version":"4.0.1","platform":"ruby","checksum":"d32e8a3c0b22949f1a41ea6d9005f5168ffce226f8fe077d1d6be785fffa81c5"}, @@ -216,7 +216,7 @@ {"name":"get_process_mem","version":"0.2.7","platform":"ruby","checksum":"4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba"}, {"name":"gettext","version":"3.5.1","platform":"ruby","checksum":"03ec7f71ea7e2cf1fdcd5e08682e98b81601922fdbee890b7bc6f63b0e1a512a"}, {"name":"gettext_i18n_rails","version":"1.13.0","platform":"ruby","checksum":"d4a4739d928b6ce52a2d694d33a831dcb06c7c8e197b3172fc73dfaa20ac8ee6"}, -{"name":"git","version":"1.18.0","platform":"ruby","checksum":"c9b80462e4565cd3d7a9ba8440c41d2c52244b17b0dad0bfddb46de70630c465"}, +{"name":"git","version":"1.19.1","platform":"ruby","checksum":"b0a422d9f6517353c48a330d6114de4db9e0c82dbe7202964a1d9f1fbc827d70"}, {"name":"gitaly","version":"17.8.1","platform":"ruby","checksum":"6268ff47ea4a7480ca116f16dadaeca626eda345a04a0cfef812d2072797d6ed"}, {"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"}, {"name":"gitlab-chronic","version":"0.10.6","platform":"ruby","checksum":"a244d11a1396d2aac6ae9b2f326adf1605ec1ad20c29f06e8b672047d415a9ac"}, @@ -340,7 +340,7 @@ {"name":"imagen","version":"0.1.8","platform":"ruby","checksum":"fde7b727d4fe79c6bb5ac46c1f7184bf87a6d54df54d712ad2be039d2f93a162"}, {"name":"influxdb-client","version":"3.2.0","platform":"ruby","checksum":"dc1e8ec80542f64c9f31af6d9bfa4c147474bf32b9179a7f0cab970793b8e1f2"}, {"name":"invisible_captcha","version":"2.1.0","platform":"ruby","checksum":"02b452f3eb1b691d155ba3e8e97e1be0e6b6be62e8bc94957234b9cde0852b1e"}, -{"name":"io-event","version":"1.6.5","platform":"ruby","checksum":"5da4c044ac5f411563da1a4743d28c8d30d7802e29370db42139a52b807b4ce2"}, +{"name":"io-event","version":"1.9.0","platform":"ruby","checksum":"4c262b6610ad643a2be75e892135aca4fa67edc67d1944c0ae6b6e5dd73f4fc1"}, {"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"}, {"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"}, {"name":"jaro_winkler","version":"1.5.6","platform":"java","checksum":"3262aea433861fec3179184e9adc1933cca8bc15665957a143b56816f1a22f74"}, @@ -388,6 +388,7 @@ {"name":"matrix","version":"0.4.2","platform":"ruby","checksum":"71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0"}, {"name":"memory_profiler","version":"1.0.1","platform":"ruby","checksum":"38cdb42f22d9100df2eba0365c199724b58b05c38e765cd764a07392916901b1"}, {"name":"method_source","version":"1.0.0","platform":"ruby","checksum":"d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede"}, +{"name":"metrics","version":"0.12.1","platform":"ruby","checksum":"42ec8eeadb92a57549a72bdd1baf86d4270089bc598917b93cf9cb6f95fcc29c"}, {"name":"mime-types","version":"3.5.1","platform":"ruby","checksum":"85d772fb6cf21f999ac8085998192fb9dd5d16e86ec4c69c5e79ac3003420d61"}, {"name":"mime-types-data","version":"3.2023.1003","platform":"ruby","checksum":"0f7b96d4e54d17752ed78398dca9402359ccaeb391aa0c0e5b305bedaf025b7a"}, {"name":"mini_histogram","version":"0.3.1","platform":"ruby","checksum":"6a114b504e4618b0e076cc672996036870f7cc6f16b8e5c25c0c637726d2dd94"}, @@ -414,7 +415,7 @@ {"name":"nap","version":"1.1.0","platform":"ruby","checksum":"949691660f9d041d75be611bb2a8d2fd559c467537deac241f4097d9b5eea576"}, {"name":"nenv","version":"0.3.0","platform":"ruby","checksum":"d9de6d8fb7072228463bf61843159419c969edb34b3cef51832b516ae7972765"}, {"name":"net-http","version":"0.6.0","platform":"ruby","checksum":"9621b20c137898af9d890556848c93603716cab516dc2c89b01a38b894e259fb"}, -{"name":"net-http-persistent","version":"4.0.1","platform":"ruby","checksum":"2752f4cce05fd1c45e0537c6f3a98fa5a4899efd5f88e63c104ed5f05cbddef9"}, +{"name":"net-http-persistent","version":"4.0.5","platform":"ruby","checksum":"6e42880b347e650ffeaf679ae59c9d5a6ed8a22cda6e1b959d9c270050aefa8e"}, {"name":"net-imap","version":"0.3.4","platform":"ruby","checksum":"a82a59e2a429433dc54cae5a8b2979ffe49da8c66085740811bfa337dc3729b5"}, {"name":"net-ldap","version":"0.17.1","platform":"ruby","checksum":"52571b55f9157120833ac1667f2969ce0139251811d0a9b64657c1c135069cf9"}, {"name":"net-ntp","version":"2.1.3","platform":"ruby","checksum":"5bc73f4102bde0d1872bd3b293608ae99d9f5007d744f21919c6a565eda9267d"}, @@ -743,6 +744,7 @@ {"name":"toml-rb","version":"2.2.0","platform":"ruby","checksum":"a1e2c54ac3cc9d49861004f75f0648b3622ac03a76abe105358c31553227d9a6"}, {"name":"tomlrb","version":"1.3.0","platform":"ruby","checksum":"68666bf53fa70ba686a48a7435ce7e086f5227c58c4c993bd9792f4760f2a503"}, {"name":"tpm-key_attestation","version":"0.12.0","platform":"ruby","checksum":"e133d80cf24fef0e7a7dfad00fd6aeff01fc79875fbfc66cd8537bbd622b1e6d"}, +{"name":"traces","version":"0.15.2","platform":"ruby","checksum":"d2547834b7248bb8c8f4f6532c6b9ba80ef8e2d6068ce16e7873575d7b802d81"}, {"name":"trailblazer-option","version":"0.1.2","platform":"ruby","checksum":"20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3"}, {"name":"train-core","version":"3.10.8","platform":"ruby","checksum":"8493da02015fbe9b11840d22ba879ef18a0aa2633cb0c04eac3f07dd9b87223b"}, {"name":"truncato","version":"0.7.12","platform":"ruby","checksum":"fed9e8a04fa35fd1a64506cd2089761bae4adfe47e756c3ce98a5c43856c9c4c"}, @@ -777,7 +779,7 @@ {"name":"version_sorter","version":"2.3.0","platform":"ruby","checksum":"2147f2a1a3804fbb8f60d268b7d7c1ec717e6dd727ffe2c165b4e05e82efe1da"}, {"name":"view_component","version":"3.21.0","platform":"ruby","checksum":"7f5a77bca29e7385495fad2b7c1acdcd2c581b3cd2e573a831a9808f6710df5c"}, {"name":"virtus","version":"2.0.0","platform":"ruby","checksum":"8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2"}, -{"name":"vite_rails","version":"3.0.17","platform":"ruby","checksum":"b90e85a3e55802981cbdb43a4101d944b1e7055bfe85599d9cb7de0f1ea58bcc"}, +{"name":"vite_rails","version":"3.0.19","platform":"ruby","checksum":"195c44677bc05c1f94e7a69f1264e49d4bad2729ab06538ee858c2962f5bb500"}, {"name":"vite_ruby","version":"3.8.2","platform":"ruby","checksum":"f3f1460d5b61d20be76270ceb61f1cde32f6d22ec954933a1391f742605690b9"}, {"name":"vmstat","version":"2.3.1","platform":"ruby","checksum":"5587cb430a54dbfc4a5c29dd01bd6a4031b2ff4c1d387504d74ff246f3b39104"}, {"name":"warden","version":"1.2.9","platform":"ruby","checksum":"46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0"}, diff --git a/Gemfile.lock b/Gemfile.lock index 77f78a9ec6e..ea6ad6bdbb5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -328,10 +328,12 @@ GEM asciidoctor-plantuml (0.0.16) asciidoctor (>= 2.0.17, < 3.0.0) ast (2.4.2) - async (2.12.1) - console (~> 1.25, >= 1.25.2) + async (2.22.0) + console (~> 1.29) fiber-annotation - io-event (~> 1.6, >= 1.6.5) + io-event (~> 1.7) + metrics (~> 0.12) + traces (~> 0.15) atlassian-jwt (0.2.1) jwt (~> 2.1) attr_required (1.0.2) @@ -440,7 +442,7 @@ GEM commonmarker (0.23.11) concurrent-ruby (1.2.3) connection_pool (2.5.0) - console (1.25.2) + console (1.29.2) fiber-annotation fiber-local (~> 1.1) json @@ -718,7 +720,7 @@ GEM text (>= 1.3.0) gettext_i18n_rails (1.13.0) fast_gettext (>= 0.9.0) - git (1.18.0) + git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) gitaly (17.8.1) @@ -1029,7 +1031,7 @@ GEM csv invisible_captcha (2.1.0) rails (>= 5.2) - io-event (1.6.5) + io-event (1.9.0) ipaddress (0.8.3) jaeger-client (1.1.0) opentracing (~> 0.3) @@ -1157,6 +1159,7 @@ GEM matrix (0.4.2) memory_profiler (1.0.1) method_source (1.0.0) + metrics (0.12.1) mime-types (3.5.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.1003) @@ -1186,7 +1189,7 @@ GEM nenv (0.3.0) net-http (0.6.0) uri - net-http-persistent (4.0.1) + net-http-persistent (4.0.5) connection_pool (~> 2.2) net-imap (0.3.4) date @@ -1863,6 +1866,7 @@ GEM bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) + traces (0.15.2) trailblazer-option (0.1.2) train-core (3.10.8) addressable (~> 2.5) @@ -1935,8 +1939,8 @@ GEM axiom-types (~> 0.1) coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) - vite_rails (3.0.17) - railties (>= 5.1, < 8) + vite_rails (3.0.19) + railties (>= 5.1, < 9) vite_ruby (~> 3.0, >= 3.2.2) vite_ruby (3.8.2) dry-cli (>= 0.7, < 2) @@ -2001,7 +2005,7 @@ DEPENDENCIES asciidoctor-include-ext (~> 0.4.0) asciidoctor-kroki (~> 0.10.0) asciidoctor-plantuml (~> 0.0.16) - async (~> 2.12.1) + async (~> 2.22.0) atlassian-jwt (~> 0.2.1) attr_encrypted (~> 3.2.4)! awesome_print diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index ca29c9d9742..8d26a2cb5dd 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -28,7 +28,7 @@ {"name":"asciidoctor-kroki","version":"0.10.0","platform":"ruby","checksum":"8e4225d88f120e2e7b5d3f5ddb67c5e69496d7344a16c57db5036ac900123062"}, {"name":"asciidoctor-plantuml","version":"0.0.16","platform":"ruby","checksum":"407e47cd1186ded5ccc75f0c812e5524c26c571d542247c5132abb8f47bd1793"}, {"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"}, -{"name":"async","version":"2.12.1","platform":"ruby","checksum":"146fb3acf6d05ad40abb9ae659dd3b574067a3420fe7d6d5d6a3cf5413de3ea5"}, +{"name":"async","version":"2.22.0","platform":"ruby","checksum":"63abba84615ec0fa31e4e0e1eea1ef26bf7908137a85ae27612fda2c6f51cc2d"}, {"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"}, {"name":"attr_required","version":"1.0.2","platform":"ruby","checksum":"f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99"}, {"name":"awesome_print","version":"1.9.2","platform":"ruby","checksum":"e99b32b704acff16d768b3468680793ced40bfdc4537eb07e06a4be11133786e"}, @@ -86,7 +86,7 @@ {"name":"commonmarker","version":"0.23.11","platform":"ruby","checksum":"9d1d35d358740151bce29235aebfecc63314fb57dd89a83e72d4061b4fe3d2bf"}, {"name":"concurrent-ruby","version":"1.2.3","platform":"ruby","checksum":"82fdd3f8a0816e28d513e637bb2b90a45d7b982bdf4f3a0511722d2e495801e2"}, {"name":"connection_pool","version":"2.5.0","platform":"ruby","checksum":"233b92f8d38e038c1349ccea65dd3772727d669d6d2e71f9897c8bf5cd53ebfc"}, -{"name":"console","version":"1.25.2","platform":"ruby","checksum":"460fbf8c1b0e527b2c275448b76f91c3e9fb72e6bead5d27fb5a638fc191e943"}, +{"name":"console","version":"1.29.2","platform":"ruby","checksum":"afd9b75a1b047059dda22df0e3c0a386e96f50f6752c87c4b00b1a9fcbe77cd6"}, {"name":"cork","version":"0.3.0","platform":"ruby","checksum":"a0a0ac50e262f8514d1abe0a14e95e71c98b24e3378690e5d044daf0013ad4bc"}, {"name":"cose","version":"1.3.0","platform":"ruby","checksum":"63247c66a5bc76e53926756574fe3724cc0a88707e358c90532ae2a320e98601"}, {"name":"countries","version":"4.0.1","platform":"ruby","checksum":"d32e8a3c0b22949f1a41ea6d9005f5168ffce226f8fe077d1d6be785fffa81c5"}, @@ -216,7 +216,7 @@ {"name":"get_process_mem","version":"0.2.7","platform":"ruby","checksum":"4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba"}, {"name":"gettext","version":"3.5.1","platform":"ruby","checksum":"03ec7f71ea7e2cf1fdcd5e08682e98b81601922fdbee890b7bc6f63b0e1a512a"}, {"name":"gettext_i18n_rails","version":"1.13.0","platform":"ruby","checksum":"d4a4739d928b6ce52a2d694d33a831dcb06c7c8e197b3172fc73dfaa20ac8ee6"}, -{"name":"git","version":"1.18.0","platform":"ruby","checksum":"c9b80462e4565cd3d7a9ba8440c41d2c52244b17b0dad0bfddb46de70630c465"}, +{"name":"git","version":"1.19.1","platform":"ruby","checksum":"b0a422d9f6517353c48a330d6114de4db9e0c82dbe7202964a1d9f1fbc827d70"}, {"name":"gitaly","version":"17.8.1","platform":"ruby","checksum":"6268ff47ea4a7480ca116f16dadaeca626eda345a04a0cfef812d2072797d6ed"}, {"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"}, {"name":"gitlab-chronic","version":"0.10.6","platform":"ruby","checksum":"a244d11a1396d2aac6ae9b2f326adf1605ec1ad20c29f06e8b672047d415a9ac"}, @@ -342,7 +342,7 @@ {"name":"invisible_captcha","version":"2.1.0","platform":"ruby","checksum":"02b452f3eb1b691d155ba3e8e97e1be0e6b6be62e8bc94957234b9cde0852b1e"}, {"name":"io-console","version":"0.8.0","platform":"java","checksum":"3cc6fd5c66e587145c1fdf8dc40c2e3d851e90722a5d0cc3f38da352f06fe1bd"}, {"name":"io-console","version":"0.8.0","platform":"ruby","checksum":"cd6a9facbc69871d69b2cb8b926fc6ea7ef06f06e505e81a64f14a470fddefa2"}, -{"name":"io-event","version":"1.6.5","platform":"ruby","checksum":"5da4c044ac5f411563da1a4743d28c8d30d7802e29370db42139a52b807b4ce2"}, +{"name":"io-event","version":"1.9.0","platform":"ruby","checksum":"4c262b6610ad643a2be75e892135aca4fa67edc67d1944c0ae6b6e5dd73f4fc1"}, {"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"}, {"name":"irb","version":"1.15.1","platform":"ruby","checksum":"d9bca745ac4207a8b728a52b98b766ca909b86ff1a504bcde3d6f8c84faae890"}, {"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"}, @@ -391,6 +391,7 @@ {"name":"matrix","version":"0.4.2","platform":"ruby","checksum":"71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0"}, {"name":"memory_profiler","version":"1.0.1","platform":"ruby","checksum":"38cdb42f22d9100df2eba0365c199724b58b05c38e765cd764a07392916901b1"}, {"name":"method_source","version":"1.0.0","platform":"ruby","checksum":"d779455a2b5666a079ce58577bfad8534f571af7cec8107f4dce328f0981dede"}, +{"name":"metrics","version":"0.12.1","platform":"ruby","checksum":"42ec8eeadb92a57549a72bdd1baf86d4270089bc598917b93cf9cb6f95fcc29c"}, {"name":"mime-types","version":"3.5.1","platform":"ruby","checksum":"85d772fb6cf21f999ac8085998192fb9dd5d16e86ec4c69c5e79ac3003420d61"}, {"name":"mime-types-data","version":"3.2023.1003","platform":"ruby","checksum":"0f7b96d4e54d17752ed78398dca9402359ccaeb391aa0c0e5b305bedaf025b7a"}, {"name":"mini_histogram","version":"0.3.1","platform":"ruby","checksum":"6a114b504e4618b0e076cc672996036870f7cc6f16b8e5c25c0c637726d2dd94"}, @@ -417,7 +418,7 @@ {"name":"nap","version":"1.1.0","platform":"ruby","checksum":"949691660f9d041d75be611bb2a8d2fd559c467537deac241f4097d9b5eea576"}, {"name":"nenv","version":"0.3.0","platform":"ruby","checksum":"d9de6d8fb7072228463bf61843159419c969edb34b3cef51832b516ae7972765"}, {"name":"net-http","version":"0.6.0","platform":"ruby","checksum":"9621b20c137898af9d890556848c93603716cab516dc2c89b01a38b894e259fb"}, -{"name":"net-http-persistent","version":"4.0.1","platform":"ruby","checksum":"2752f4cce05fd1c45e0537c6f3a98fa5a4899efd5f88e63c104ed5f05cbddef9"}, +{"name":"net-http-persistent","version":"4.0.5","platform":"ruby","checksum":"6e42880b347e650ffeaf679ae59c9d5a6ed8a22cda6e1b959d9c270050aefa8e"}, {"name":"net-imap","version":"0.3.4","platform":"ruby","checksum":"a82a59e2a429433dc54cae5a8b2979ffe49da8c66085740811bfa337dc3729b5"}, {"name":"net-ldap","version":"0.17.1","platform":"ruby","checksum":"52571b55f9157120833ac1667f2969ce0139251811d0a9b64657c1c135069cf9"}, {"name":"net-ntp","version":"2.1.3","platform":"ruby","checksum":"5bc73f4102bde0d1872bd3b293608ae99d9f5007d744f21919c6a565eda9267d"}, @@ -756,6 +757,7 @@ {"name":"toml-rb","version":"2.2.0","platform":"ruby","checksum":"a1e2c54ac3cc9d49861004f75f0648b3622ac03a76abe105358c31553227d9a6"}, {"name":"tomlrb","version":"1.3.0","platform":"ruby","checksum":"68666bf53fa70ba686a48a7435ce7e086f5227c58c4c993bd9792f4760f2a503"}, {"name":"tpm-key_attestation","version":"0.12.0","platform":"ruby","checksum":"e133d80cf24fef0e7a7dfad00fd6aeff01fc79875fbfc66cd8537bbd622b1e6d"}, +{"name":"traces","version":"0.15.2","platform":"ruby","checksum":"d2547834b7248bb8c8f4f6532c6b9ba80ef8e2d6068ce16e7873575d7b802d81"}, {"name":"trailblazer-option","version":"0.1.2","platform":"ruby","checksum":"20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3"}, {"name":"train-core","version":"3.10.8","platform":"ruby","checksum":"8493da02015fbe9b11840d22ba879ef18a0aa2633cb0c04eac3f07dd9b87223b"}, {"name":"truncato","version":"0.7.12","platform":"ruby","checksum":"fed9e8a04fa35fd1a64506cd2089761bae4adfe47e756c3ce98a5c43856c9c4c"}, @@ -790,7 +792,7 @@ {"name":"version_sorter","version":"2.3.0","platform":"ruby","checksum":"2147f2a1a3804fbb8f60d268b7d7c1ec717e6dd727ffe2c165b4e05e82efe1da"}, {"name":"view_component","version":"3.21.0","platform":"ruby","checksum":"7f5a77bca29e7385495fad2b7c1acdcd2c581b3cd2e573a831a9808f6710df5c"}, {"name":"virtus","version":"2.0.0","platform":"ruby","checksum":"8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2"}, -{"name":"vite_rails","version":"3.0.17","platform":"ruby","checksum":"b90e85a3e55802981cbdb43a4101d944b1e7055bfe85599d9cb7de0f1ea58bcc"}, +{"name":"vite_rails","version":"3.0.19","platform":"ruby","checksum":"195c44677bc05c1f94e7a69f1264e49d4bad2729ab06538ee858c2962f5bb500"}, {"name":"vite_ruby","version":"3.8.2","platform":"ruby","checksum":"f3f1460d5b61d20be76270ceb61f1cde32f6d22ec954933a1391f742605690b9"}, {"name":"vmstat","version":"2.3.1","platform":"ruby","checksum":"5587cb430a54dbfc4a5c29dd01bd6a4031b2ff4c1d387504d74ff246f3b39104"}, {"name":"warden","version":"1.2.9","platform":"ruby","checksum":"46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index a4720ca1df8..0c79ff42277 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -340,10 +340,12 @@ GEM asciidoctor-plantuml (0.0.16) asciidoctor (>= 2.0.17, < 3.0.0) ast (2.4.2) - async (2.12.1) - console (~> 1.25, >= 1.25.2) + async (2.22.0) + console (~> 1.29) fiber-annotation - io-event (~> 1.6, >= 1.6.5) + io-event (~> 1.7) + metrics (~> 0.12) + traces (~> 0.15) atlassian-jwt (0.2.1) jwt (~> 2.1) attr_required (1.0.2) @@ -452,7 +454,7 @@ GEM commonmarker (0.23.11) concurrent-ruby (1.2.3) connection_pool (2.5.0) - console (1.25.2) + console (1.29.2) fiber-annotation fiber-local (~> 1.1) json @@ -730,7 +732,7 @@ GEM text (>= 1.3.0) gettext_i18n_rails (1.13.0) fast_gettext (>= 0.9.0) - git (1.18.0) + git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) gitaly (17.8.1) @@ -1042,7 +1044,7 @@ GEM invisible_captcha (2.1.0) rails (>= 5.2) io-console (0.8.0) - io-event (1.6.5) + io-event (1.9.0) ipaddress (0.8.3) irb (1.15.1) pp (>= 0.6.0) @@ -1174,6 +1176,7 @@ GEM matrix (0.4.2) memory_profiler (1.0.1) method_source (1.0.0) + metrics (0.12.1) mime-types (3.5.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.1003) @@ -1203,7 +1206,7 @@ GEM nenv (0.3.0) net-http (0.6.0) uri - net-http-persistent (4.0.1) + net-http-persistent (4.0.5) connection_pool (~> 2.2) net-imap (0.3.4) date @@ -1897,6 +1900,7 @@ GEM bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) + traces (0.15.2) trailblazer-option (0.1.2) train-core (3.10.8) addressable (~> 2.5) @@ -1969,8 +1973,8 @@ GEM axiom-types (~> 0.1) coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) - vite_rails (3.0.17) - railties (>= 5.1, < 8) + vite_rails (3.0.19) + railties (>= 5.1, < 9) vite_ruby (~> 3.0, >= 3.2.2) vite_ruby (3.8.2) dry-cli (>= 0.7, < 2) @@ -2036,7 +2040,7 @@ DEPENDENCIES asciidoctor-include-ext (~> 0.4.0) asciidoctor-kroki (~> 0.10.0) asciidoctor-plantuml (~> 0.0.16) - async (~> 2.12.1) + async (~> 2.22.0) atlassian-jwt (~> 0.2.1) attr_encrypted (~> 3.2.4)! awesome_print diff --git a/app/assets/javascripts/search/results/components/blob_chunks.vue b/app/assets/javascripts/search/results/components/blob_chunks.vue index 539ac1e6107..719dd1ce216 100644 --- a/app/assets/javascripts/search/results/components/blob_chunks.vue +++ b/app/assets/javascripts/search/results/components/blob_chunks.vue @@ -133,13 +133,17 @@ export default {
           
           
         
-
+        
           
             
           
diff --git a/app/assets/javascripts/search/results/components/blob_footer.vue b/app/assets/javascripts/search/results/components/blob_footer.vue
index 0b83ba9820a..8f4e3f2157d 100644
--- a/app/assets/javascripts/search/results/components/blob_footer.vue
+++ b/app/assets/javascripts/search/results/components/blob_footer.vue
@@ -38,7 +38,7 @@ export default {
       showMore: false,
       filePath: this.file?.path,
       projectPath: this.file?.projectPath,
-      fileLink: this.file.url,
+      fileLink: this.file.fileUrl,
       fileMatchCountTotal: this.file.matchCountTotal,
     };
   },
diff --git a/app/assets/javascripts/vue_shared/components/import/constants.js b/app/assets/javascripts/vue_shared/components/import/constants.js
new file mode 100644
index 00000000000..0767753d88c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/import/constants.js
@@ -0,0 +1,37 @@
+import { s__ } from '~/locale';
+
+export const IMPORT_HISTORY_TABLE_STATUS = {
+  inProgress: 'started',
+  complete: 'finished',
+  failed: 'failed',
+  timeout: 'timeout',
+  unstarted: 'created',
+};
+
+export const IMPORT_HISTORY_TABLE_STATUS_DATA = {
+  [IMPORT_HISTORY_TABLE_STATUS.unstarted]: {
+    label: s__('Import|Not started'),
+    variant: 'neutral',
+    icon: 'status-waiting',
+  },
+  [IMPORT_HISTORY_TABLE_STATUS.inProgress]: {
+    label: s__('Import|In progress'),
+    variant: 'warning',
+    icon: 'status-running',
+  },
+  [IMPORT_HISTORY_TABLE_STATUS.complete]: {
+    label: s__('Import|Complete'),
+    variant: 'success',
+    icon: 'status-success',
+  },
+  [IMPORT_HISTORY_TABLE_STATUS.failed]: {
+    label: s__('Import|Failed'),
+    variant: 'danger',
+    icon: 'status-failed',
+  },
+  [IMPORT_HISTORY_TABLE_STATUS.timeout]: {
+    label: s__('Import|Failed'),
+    variant: 'danger',
+    icon: 'status-failed',
+  },
+};
diff --git a/app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.stories.js b/app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.stories.js
new file mode 100644
index 00000000000..1fc15de7053
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.stories.js
@@ -0,0 +1,21 @@
+import { basic } from 'jest/vue_shared/components/import_history_table/mock_data';
+
+import ImportHistoryTableRowDestination from './import_history_table_row_destination.vue';
+
+export default {
+  component: ImportHistoryTableRowDestination,
+  title: 'vue_shared/import/import_history_table_row_destination',
+};
+
+const defaultProps = {
+  item: basic.items[0],
+};
+
+const Template = (args, { argTypes }) => ({
+  components: { ImportHistoryTableRowDestination },
+  props: Object.keys(argTypes),
+  template: ``,
+});
+
+export const Default = Template.bind({});
+Default.args = defaultProps;
diff --git a/app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.vue b/app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.vue
new file mode 100644
index 00000000000..ad636e0a8e0
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/import/import_history_table_row_destination.vue
@@ -0,0 +1,64 @@
+
+
+
diff --git a/app/controllers/projects/ml/experiments_controller.rb b/app/controllers/projects/ml/experiments_controller.rb
index 1aa74f94905..cf31ba3de4d 100644
--- a/app/controllers/projects/ml/experiments_controller.rb
+++ b/app/controllers/projects/ml/experiments_controller.rb
@@ -11,18 +11,9 @@ module Projects
 
       feature_category :mlops
 
-      MAX_EXPERIMENTS_PER_PAGE = 20
       MAX_CANDIDATES_PER_PAGE = 30
 
-      def index
-        paginator = Projects::Ml::ExperimentFinder
-          .new(@project, { with_candidate_count: true })
-          .execute
-          .keyset_paginate(cursor: params[:cursor], per_page: MAX_EXPERIMENTS_PER_PAGE)
-
-        @experiments = paginator.records
-        @page_info = page_info(paginator)
-      end
+      def index; end
 
       def show
         find_params = params
diff --git a/app/helpers/projects/ml/experiments_helper.rb b/app/helpers/projects/ml/experiments_helper.rb
index 979a46e9913..907e8ea5531 100644
--- a/app/helpers/projects/ml/experiments_helper.rb
+++ b/app/helpers/projects/ml/experiments_helper.rb
@@ -44,25 +44,6 @@ module Projects
         Gitlab::Json.generate(candidates.flat_map(&selector).map(&:name).uniq)
       end
 
-      def experiments_as_data(project, experiments)
-        data = experiments.map do |experiment|
-          {
-            name: experiment.name,
-            path: link_to_experiment(project, experiment),
-            candidate_count: experiment.candidate_count,
-            updated_at: experiment.updated_at,
-            user: {
-              id: experiment.user&.id,
-              name: experiment.user&.name,
-              path: experiment&.user ? user_path(experiment&.user) : nil,
-              avatar_url: experiment.user&.avatar_url
-            }
-          }
-        end
-
-        Gitlab::Json.generate(data)
-      end
-
       def page_info(paginator)
         {
           has_next_page: paginator.has_next_page?,
diff --git a/app/models/preloaders/group_root_ancestor_preloader.rb b/app/models/preloaders/group_root_ancestor_preloader.rb
index 410f48c8176..0fcf22d7560 100644
--- a/app/models/preloaders/group_root_ancestor_preloader.rb
+++ b/app/models/preloaders/group_root_ancestor_preloader.rb
@@ -1,30 +1,14 @@
 # frozen_string_literal: true
 
 module Preloaders
-  class GroupRootAncestorPreloader
-    def initialize(groups, root_ancestor_preloads = [])
-      @groups = groups
-      @root_ancestor_preloads = root_ancestor_preloads
-    end
-
-    def execute
-      # type == 'Group' condition located on subquery to prevent a filter in the query
-      root_query = Namespace.joins("INNER JOIN (#{join_sql}) as root_query ON root_query.root_id = namespaces.id")
-                        .select('namespaces.*, root_query.id as source_id')
-
-      root_query = root_query.preload(*@root_ancestor_preloads) if @root_ancestor_preloads.any?
-
-      root_ancestors_by_id = root_query.group_by(&:source_id)
-
-      @groups.each do |group|
-        group.root_ancestor = root_ancestors_by_id[group.id].first
-      end
-    end
+  class GroupRootAncestorPreloader < NamespaceRootAncestorPreloader
+    extend Gitlab::Utils::Override
 
     private
 
+    override :join_sql
     def join_sql
-      Group.select('id, traversal_ids[1] as root_id').where(id: @groups.map(&:id)).to_sql
+      Group.select('id, traversal_ids[1] as root_id').where(id: @namespaces.map(&:id)).to_sql
     end
   end
 end
diff --git a/app/models/preloaders/namespace_root_ancestor_preloader.rb b/app/models/preloaders/namespace_root_ancestor_preloader.rb
new file mode 100644
index 00000000000..b599d1d8e86
--- /dev/null
+++ b/app/models/preloaders/namespace_root_ancestor_preloader.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Preloaders
+  class NamespaceRootAncestorPreloader
+    def initialize(namespaces, root_ancestor_preloads = [])
+      @namespaces = namespaces
+      @root_ancestor_preloads = root_ancestor_preloads
+    end
+
+    def execute
+      root_query = Namespace.joins("INNER JOIN (#{join_sql}) as root_query ON root_query.root_id = namespaces.id")
+                        .select('namespaces.*, root_query.id as source_id')
+
+      root_query = root_query.preload(*@root_ancestor_preloads) if @root_ancestor_preloads.any?
+
+      root_ancestors_by_id = root_query.group_by(&:source_id)
+
+      @namespaces.each do |namespace|
+        namespace.root_ancestor = root_ancestors_by_id[namespace.id].first
+      end
+    end
+
+    private
+
+    def join_sql
+      Namespace.select('id, traversal_ids[1] as root_id').where(id: @namespaces.map(&:id)).to_sql
+    end
+  end
+end
diff --git a/app/models/user_synced_attributes_metadata.rb b/app/models/user_synced_attributes_metadata.rb
index 6b23bce6406..be7720de31c 100644
--- a/app/models/user_synced_attributes_metadata.rb
+++ b/app/models/user_synced_attributes_metadata.rb
@@ -5,7 +5,7 @@ class UserSyncedAttributesMetadata < ApplicationRecord
 
   validates :user, presence: true
 
-  SYNCABLE_ATTRIBUTES = %i[name email location].freeze
+  SYNCABLE_ATTRIBUTES = %i[name email location organization job_title].freeze
 
   def read_only?(attribute)
     sync_profile_from_provider? && synced?(attribute)
diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb
index 0b1aefb30d7..d7104feb6e0 100644
--- a/app/services/merge_requests/squash_service.rb
+++ b/app/services/merge_requests/squash_service.rb
@@ -39,7 +39,7 @@ module MergeRequests
     end
 
     def squash_forbidden?
-      target_project.squash_never?
+      merge_request.squash_never?
     end
 
     def repository
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 754ccd5e6ce..8b768c4e28b 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -332,24 +332,13 @@ class TodoService
   end
 
   def excluded_user_ids(users, attributes)
-    users_single_todos, users_multiple_todos = users.partition { |u| Feature.disabled?(:multiple_todos, u) }
-    excluded_user_ids = []
+    return [] unless users.present?
+    return [] if Todo::ACTIONS_MULTIPLE_ALLOWED.include?(attributes.fetch(:action))
 
-    if users_single_todos.present?
-      excluded_user_ids += pending_todos(
-        users_single_todos,
-        attributes.slice(:project_id, :target_id, :target_type, :commit_id, :discussion)
-      ).distinct_user_ids
-    end
-
-    if users_multiple_todos.present? && Todo::ACTIONS_MULTIPLE_ALLOWED.exclude?(attributes.fetch(:action))
-      excluded_user_ids += pending_todos(
-        users_multiple_todos,
-        attributes.slice(:project_id, :target_id, :target_type, :commit_id, :discussion, :action)
-      ).distinct_user_ids
-    end
-
-    excluded_user_ids
+    pending_todos(
+      users,
+      attributes.slice(:project_id, :target_id, :target_type, :commit_id, :discussion, :action)
+    ).distinct_user_ids
   end
 
   def bulk_insert_todos(users, attributes)
diff --git a/app/views/user_settings/profiles/show.html.haml b/app/views/user_settings/profiles/show.html.haml
index c015f069017..bb4a6c354ee 100644
--- a/app/views/user_settings/profiles/show.html.haml
+++ b/app/views/user_settings/profiles/show.html.haml
@@ -136,12 +136,18 @@
             = f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|City, country")
         .form-group.gl-form-group
           = f.label :job_title, s_('Profiles|Job title')
-          = f.text_field :job_title, class: 'gl-form-input form-control gl-md-form-input-lg'
+          = f.text_field :job_title, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: @user.read_only_attribute?(:job_title)
+          - if @user.read_only_attribute?(:job_title)
+            .form-text.gl-text-subtle
+              = s_("Profiles|Your job title was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:job_title) }
         .form-group.gl-form-group
           = f.label :organization, s_('Profiles|Organization')
-          = f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg'
+          = f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: @user.read_only_attribute?(:organization)
           .form-text.gl-text-subtle
-            = s_("Profiles|Who you represent or work for.")
+            - if @user.read_only_attribute?(:organization)
+              = s_("Profiles|Your organization was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:organization) }
+            - else
+              = s_("Profiles|Who you represent or work for.")
         .form-group.gl-form-group.gl-mb-6.gl-max-w-80
           = f.label :bio, s_('Profiles|Bio')
           = f.text_area :bio, class: 'gl-form-input gl-form-textarea form-control', rows: 4, maxlength: 250
diff --git a/config/feature_flags/development/multiple_todos.yml b/config/feature_flags/development/multiple_todos.yml
deleted file mode 100644
index 68270c45c4e..00000000000
--- a/config/feature_flags/development/multiple_todos.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: multiple_todos
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47629
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/28355
-milestone: '13.8'
-type: development
-group: group::personal productivity
-default_enabled: true
diff --git a/data/deprecations/17-9-vuln-object-retention-limits.yml b/data/deprecations/17-9-vuln-object-retention-limits.yml
new file mode 100644
index 00000000000..96109eaf8d6
--- /dev/null
+++ b/data/deprecations/17-9-vuln-object-retention-limits.yml
@@ -0,0 +1,17 @@
+- title: "New data retention limits for vulnerabilities on GitLab.com"
+  announcement_milestone: "17.9"
+  removal_milestone: "18.0"
+  breaking_change: true
+  reporter: smeadzinger
+  stage: security_risk_management
+  issue_url: https://gitlab.com/groups/gitlab-org/-/epics/16629 # Replace with actual issue
+  impact: medium
+  scope: instance
+  resolution_role: Developer
+  manual_task: true
+  body: |
+    In GitLab 18.0, we are introducing a new data retention limit for GitLab.com Ultimate customers to improve system performance and reliability. The data retention limit affects how long your vulnerability data is stored for. Vulnerabilities older than 12 months that have not been updated are automatically moved to cold storage archives. These archives:
+
+    - Remain accessible and downloadable through the GitLab UI.
+    - Are retained for 3 years.
+    - Are permanently deleted after 3 years.
diff --git a/db/migrate/20241213025719_add_organization_synced_and_job_title_synced_to_user_synced_attributes_metadata.rb b/db/migrate/20241213025719_add_organization_synced_and_job_title_synced_to_user_synced_attributes_metadata.rb
new file mode 100644
index 00000000000..0fc9798bcc3
--- /dev/null
+++ b/db/migrate/20241213025719_add_organization_synced_and_job_title_synced_to_user_synced_attributes_metadata.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddOrganizationSyncedAndJobTitleSyncedToUserSyncedAttributesMetadata < Gitlab::Database::Migration[2.2]
+  milestone '17.8'
+
+  def change
+    add_column :user_synced_attributes_metadata, :organization_synced, :boolean, default: false
+    add_column :user_synced_attributes_metadata, :job_title_synced, :boolean, default: false
+  end
+end
diff --git a/db/migrate/20250212132308_remove_index_over_watermark_event_worker_job_instances.rb b/db/migrate/20250212132308_remove_index_over_watermark_event_worker_job_instances.rb
new file mode 100644
index 00000000000..062e40e5298
--- /dev/null
+++ b/db/migrate/20250212132308_remove_index_over_watermark_event_worker_job_instances.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class RemoveIndexOverWatermarkEventWorkerJobInstances < Gitlab::Database::Migration[2.2]
+  disable_ddl_transaction!
+  milestone '17.9'
+
+  DEPRECATED_JOB_CLASSES = %w[Search::Zoekt::IndexOverWatermarkEventWorker]
+
+  def up
+    sidekiq_remove_jobs(job_klasses: DEPRECATED_JOB_CLASSES)
+  end
+
+  def down
+    # This migration removes instances of a deprecated worker and cannot be undone.
+  end
+end
diff --git a/db/schema_migrations/20241213025719 b/db/schema_migrations/20241213025719
new file mode 100644
index 00000000000..729e289a661
--- /dev/null
+++ b/db/schema_migrations/20241213025719
@@ -0,0 +1 @@
+2eb91fc5c8a497713a93d43b8b2f15459225359989d139c89b597bf931ab5a2a
\ No newline at end of file
diff --git a/db/schema_migrations/20250212132308 b/db/schema_migrations/20250212132308
new file mode 100644
index 00000000000..b99e7af451c
--- /dev/null
+++ b/db/schema_migrations/20250212132308
@@ -0,0 +1 @@
+a232a5814fe7cfc9911f6c18beee12603aaf3ef608c0d38b10c3e529ffc6499b
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 83217cc0d97..96878096286 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -22939,7 +22939,9 @@ CREATE TABLE user_synced_attributes_metadata (
     email_synced boolean DEFAULT false,
     location_synced boolean DEFAULT false,
     user_id bigint NOT NULL,
-    provider character varying
+    provider character varying,
+    organization_synced boolean DEFAULT false,
+    job_title_synced boolean DEFAULT false
 );
 
 CREATE SEQUENCE user_synced_attributes_metadata_id_seq
diff --git a/doc/administration/gitlab_duo_self_hosted/_index.md b/doc/administration/gitlab_duo_self_hosted/_index.md
index e52d03a4b43..c866f01c1ee 100644
--- a/doc/administration/gitlab_duo_self_hosted/_index.md
+++ b/doc/administration/gitlab_duo_self_hosted/_index.md
@@ -20,6 +20,9 @@ To maintain full control over your data privacy, security, and the deployment of
 
 By deploying GitLab Duo Self-Hosted, you can manage the entire lifecycle of requests made to LLM backends for GitLab Duo features, ensuring that all requests stay in your enterprise network, and avoiding external dependencies.
 
+For a click-through demo, see [GitLab Duo Self-Hosted prooduct tour](https://gitlab.navattic.com/gitlab-duo-self-hosted).
+
+
 ## Why use GitLab Duo Self-Hosted
 
 With GitLab Duo Self-Hosted, you can:
diff --git a/doc/administration/gitlab_duo_self_hosted/configure_duo_features.md b/doc/administration/gitlab_duo_self_hosted/configure_duo_features.md
index dbe32318082..d124dc89afd 100644
--- a/doc/administration/gitlab_duo_self_hosted/configure_duo_features.md
+++ b/doc/administration/gitlab_duo_self_hosted/configure_duo_features.md
@@ -59,7 +59,7 @@ To configure a self-hosted model:
    - **Model family**: Select the model family the deployment belongs to. Only GitLab-approved models
      are in this list.
    - **Endpoint**: Enter the URL where the model is hosted.
-     - For models hosted through vLLM, you must suffix the URL with `/v1`. The default port is `8000`, so the default endpoint URL is `https://:8000/v1`.
+     - For more information about configuring the endpoint for models deployed through vLLM, see the [vLLM documentation](supported_llm_serving_platforms.md#endpoint-configuration).
    - **API key**: Optional. Add an API key if you need one to access the model.
    - **Model identifier**: This is a required field if your deployment method is vLLM, Bedrock or Azure. The value of this field is based on your deployment method, and should match the following structure:
 
@@ -70,6 +70,8 @@ To configure a self-hosted model:
      | Azure OpenAI | `azure/` | `azure/gpt-35-turbo` |
      | Others | The field is optional |  |
 
+     For more information about configuring the model identifier for models deployed through vLLM, see the [vLLM documentation](supported_llm_serving_platforms.md#finding-the-model-name).
+
 1. Select **Create self-hosted model**.
 
 ## Configure self-hosted beta models
diff --git a/doc/administration/gitlab_duo_self_hosted/logging.md b/doc/administration/gitlab_duo_self_hosted/logging.md
index cfc75580f6e..b7852ab070e 100644
--- a/doc/administration/gitlab_duo_self_hosted/logging.md
+++ b/doc/administration/gitlab_duo_self_hosted/logging.md
@@ -105,7 +105,6 @@ To specify the location of logs generated by AI gateway, run:
 
 ```shell
 docker run -e AIGW_GITLAB_URL= \
- -e AIGW_GITLAB_API_URL=https:///api/v4/ \
  -e AIGW_GITLAB_API_URL=https:///api/v4/ \
  -e AIGW_LOGGING__TO_FILE="aigateway.log" \
  -v :"aigateway.log"
diff --git a/doc/administration/gitlab_duo_self_hosted/supported_llm_serving_platforms.md b/doc/administration/gitlab_duo_self_hosted/supported_llm_serving_platforms.md
index 33a993e6e7f..6156d300292 100644
--- a/doc/administration/gitlab_duo_self_hosted/supported_llm_serving_platforms.md
+++ b/doc/administration/gitlab_duo_self_hosted/supported_llm_serving_platforms.md
@@ -26,6 +26,47 @@ There are multiple platforms available to host your self-hosted Large Language M
 
 To install vLLM, see the [vLLM Installation Guide](https://docs.vllm.ai/en/latest/getting_started/installation.html). You should install [version v0.6.4.post1](https://github.com/vllm-project/vllm/releases/tag/v0.6.4.post1) or later.
 
+#### Endpoint Configuration
+
+When configuring the endpoint URL for any OpenAI API compatible platforms (such as vLLM) in GitLab:
+
+- The URL must be suffixed with `/v1`
+- If using the default vLLM configuration, the endpoint URL would be `https://:8000/v1`
+- If your server is configured behind a proxy or load balancer, you might not need to specify the port, in which case the URL would be `https:///v1`
+
+#### Finding the model name
+
+After the model has been deployed, you can obtain the model name for the model identifier field in GitLab by querying the vLLM server's `/v1/models` endpoint:
+
+```shell
+curl \
+  --header "Authorization: Bearer API_KEY" \
+  --header "Content-Type: application/json" \
+  http://your-vllm-server:8000/v1/models
+```
+
+The model name is the value of the `data.id` field in the response.
+
+Example response:
+
+```json
+{
+  "object": "list",
+  "data": [
+    {
+      "id": "Mixtral-8x22B-Instruct-v0.1",
+      "object": "model",
+      "created": 1739421415,
+      "owned_by": "vllm",
+      "root": "mistralai/Mixtral-8x22B-Instruct-v0.1",
+      // ... other fields ...
+    }
+  ]
+}
+```
+
+In this example, if the model's `id` is `Mixtral-8x22B-Instruct-v0.1`, you would set the model identifier in GitLab as `custom_openai/Mixtral-8x22B-Instruct-v0.1`.
+
 For more information on:
 
 - vLLM supported models, see the [vLLM Supported Models documentation](https://docs.vllm.ai/en/latest/models/supported_models.html).
@@ -77,7 +118,7 @@ Examples:
      --served_model_name  \
      --tokenizer_mode mistral \
      --load_format safetensors \
-     --tokenizer /Mixtral-8x7B-Instruct-v0.1/
+     --tokenizer /Mixtral-8x7B-Instruct-v0.1
    ```
 
 ## For cloud-hosted model deployments
diff --git a/doc/administration/packages/container_registry_metadata_database.md b/doc/administration/packages/container_registry_metadata_database.md
index d58d0fef867..dbba08e966a 100644
--- a/doc/administration/packages/container_registry_metadata_database.md
+++ b/doc/administration/packages/container_registry_metadata_database.md
@@ -33,7 +33,7 @@ in the Helm Charts documentation.
 ## Known Limitations
 
 - No support for online migrations.
-- Geo Support is not confirmed.
+- Geo functionality is limited. Additional features are proposed in [epic 15325](https://gitlab.com/groups/gitlab-org/-/epics/15325).
 - Registry database migrations must be run manually when upgrading versions.
 - No guarantee for registry [zero downtime during upgrades](../../update/zero_downtime.md) on multi-node Omnibus GitLab environments.
 
@@ -503,6 +503,61 @@ to each other. To restore the registry, you must apply both backups together.
 To downgrade the registry to a previous version after the migration is complete,
 you must restore to a backup of the desired version in order to downgrade.
 
+## Database architecture with Geo
+
+When using GitLab Geo with the container registry, you must configure separate database and
+object storage stacks for the registry at each site. Geo replication to the
+container registry uses events generated from registry notifications,
+rather than by database replication.
+
+### Prerequisites
+
+Each Geo site requires a separate, site-specific:
+
+1. PostgreSQL instance for the container registry database.
+1. Object storage instance for the container registry.
+1. Container registry configured to use these site-specific resources.
+
+This diagram illustrates the data flow and basic architecture:
+
+```mermaid
+flowchart TB
+    subgraph "Primary site"
+        P_Rails[GitLab Rails]
+        P_Reg[Container registry]
+        P_RegDB[(Registry database)]
+        P_Obj[(Object storage)]
+        P_Reg --> P_RegDB
+        P_RegDB --> P_Obj
+    end
+
+    subgraph "Secondary site"
+        S_Rails[GitLab Rails]
+        S_Reg[Container registry]
+        S_RegDB[(Registry database)]
+        S_Obj[(Object storage)]
+        S_Reg --> S_RegDB
+        S_RegDB --> S_Obj
+    end
+
+    P_Reg -- "Notifications" --> P_Rails
+    P_Rails -- "Events" --> S_Rails
+    S_Rails --> S_Reg
+
+    classDef primary fill:#d1f7c4
+    classDef secondary fill:#b8d4ff
+
+    class P_Rails,P_Reg,P_MainDB,P_RegDB,P_Obj primary
+    class S_Rails,S_Reg,S_MainDB,S_RegDB,S_Obj secondary
+```
+
+Use separate database instances on each site because:
+
+1. The main GitLab database is replicated to the secondary site as read-only.
+1. This replication cannot be selectively disabled for the registry database.
+1. The container registry requires write access to its database at both sites.
+1. Homogeneous setups ensure the greatest parity between Geo sites.
+
 ## Troubleshooting
 
 ### Error: `there are pending database migrations`
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 54cab0c3057..e35f8cbc829 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -356,12 +356,19 @@ omniauth:
 
 ## Keep OmniAuth user profiles up to date
 
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/505575) `job_title` and `organization` attributes in GitLab 17.8.
+
+NOTE:
+Some providers require additional configuration to synchronize these attributes. For example, SAML providers require [mapping profile attributes](saml.md#map-profile-attributes).
+
 You can enable profile syncing from selected OmniAuth providers.
 You can sync any combination of the following user attributes:
 
 - `name`
 - `email`
+- `job_title`
 - `location`
+- `organization`
 
 When authenticating using LDAP, the user's name and email are always synced.
 
@@ -369,18 +376,83 @@ When authenticating using LDAP, the user's name and email are always synced.
 
 :::TabTitle Linux package (Omnibus)
 
-```ruby
-gitlab_rails['omniauth_sync_profile_from_provider'] = ['saml', 'google_oauth2']
-gitlab_rails['omniauth_sync_profile_attributes'] = ['name', 'email', 'location']
-```
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+   ```ruby
+   gitlab_rails['omniauth_sync_profile_from_provider'] = ['saml', 'google_oauth2']
+   gitlab_rails['omniauth_sync_profile_attributes'] = ['name', 'email', 'job_title', 'location', 'organization']
+   ```
+
+1. Save the file and reconfigure GitLab:
+
+   ```shell
+   sudo gitlab-ctl reconfigure
+   ```
+
+:::TabTitle Helm chart (Kubernetes)
+
+1. Export the Helm values:
+
+   ```shell
+   helm get values gitlab > values.yaml
+   ```
+
+1. Edit `values.yaml`:
+
+   ```yaml
+   global:
+     appConfig:
+       omniauth:
+         syncProfileFromProvider: ['saml', 'google_oauth2']
+         syncProfileAttributes: ['name', 'email', 'job_title', 'location', 'organization']
+   ```
+
+1. Save the file and apply the new values:
+
+   ```shell
+   helm upgrade -f values.yaml gitlab gitlab/gitlab
+   ```
+
+:::TabTitle Docker
+
+1. Edit `docker-compose.yml`:
+
+   ```yaml
+   version: "3.6"
+   services:
+     gitlab:
+       environment:
+         GITLAB_OMNIBUS_CONFIG: |
+           gitlab_rails['omniauth_sync_profile_from_provider'] = ['saml', 'google_oauth2']
+           gitlab_rails['omniauth_sync_profile_attributes'] = ['name', 'email', 'job_title', 'location', 'organization']
+   ```
+
+1. Save the file and restart GitLab:
+
+   ```shell
+   docker compose up -d
+   ```
 
 :::TabTitle Self-compiled (source)
 
-```yaml
-omniauth:
-  sync_profile_from_provider: ['saml', 'google_oauth2']
-  sync_profile_attributes: ['email', 'location']
-```
+1. Edit `/home/git/gitlab/config/gitlab.yml`:
+
+   ```yaml
+   production: &base
+     omniauth:
+       sync_profile_from_provider: ['saml', 'google_oauth2']
+       sync_profile_attributes: ['name', 'email', 'job_title', 'location', 'organization']
+   ```
+
+1. Save the file and restart GitLab:
+
+   ```shell
+   # For systems running systemd
+   sudo systemctl restart gitlab.target
+
+   # For systems running SysV init
+   sudo service gitlab restart
+   ```
 
 ::EndTabs
 
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 6b7306c8fd7..931245cf378 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -2399,6 +2399,171 @@ Configure [`username` or `nickname`](omniauth.md#per-provider-configuration) in
 
 This also sets the `username` attribute in your SAML Response to the username in GitLab.
 
+#### Map profile attributes
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/505575) `job_title` and `organization` attributes in GitLab 17.8.
+
+To sync profile information from your SAML provider, you must configure `attribute_statements` to map these attributes.
+
+The supported profile attributes are:
+
+- `job_title`
+- `organization`
+
+These attributes have no default mappings and do not sync unless explicitly configured.
+
+::Tabs
+
+:::TabTitle Linux package (Omnibus)
+
+1. [Configure OmniAuth to sync the desired attributes](omniauth.md#keep-omniauth-user-profiles-up-to-date).
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+   ```ruby
+   gitlab_rails['omniauth_providers'] = [
+     { name: 'saml',
+       label: 'Our SAML Provider',
+       args: {
+               assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+               idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+               idp_sso_target_url: 'https://login.example.com/idp',
+               issuer: 'https://gitlab.example.com',
+               name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+               attribute_statements: {
+                 organization: ['organization'],
+                 job_title: ['job_title']
+               }
+       }
+     }
+   ]
+   ```
+
+1. Save the file and reconfigure GitLab:
+
+   ```shell
+   sudo gitlab-ctl reconfigure
+   ```
+
+:::TabTitle Helm chart (Kubernetes)
+
+1. [Configure OmniAuth to sync the desired attributes](omniauth.md#keep-omniauth-user-profiles-up-to-date).
+1. Save the following YAML content in a file named `saml.yaml` to be used as a
+   [Kubernetes Secret](https://docs.gitlab.com/charts/charts/globals.html#providers):
+
+   ```yaml
+   name: 'saml'
+   label: 'Our SAML Provider'
+   args:
+     assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback'
+     idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8'
+     idp_sso_target_url: 'https://login.example.com/idp'
+     issuer: 'https://gitlab.example.com'
+     name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
+     attribute_statements:
+       organization: ['organization']
+       job_title: ['job_title']
+   ```
+
+1. Create the Kubernetes Secret:
+
+   ```shell
+   kubectl create secret generic -n  gitlab-saml --from-file=provider=saml.yaml
+   ```
+
+1. Export the Helm values:
+
+   ```shell
+   helm get values gitlab > gitlab_values.yaml
+   ```
+
+1. Edit `gitlab_values.yaml`:
+
+   ```yaml
+   global:
+     appConfig:
+       omniauth:
+         providers:
+           - secret: gitlab-saml
+   ```
+
+1. Save the file and apply the new values:
+
+   ```shell
+   helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
+   ```
+
+:::TabTitle Docker
+
+1. [Configure OmniAuth to sync the desired attributes](omniauth.md#keep-omniauth-user-profiles-up-to-date).
+1. Edit `docker-compose.yml`:
+
+   ```yaml
+   version: "3.6"
+   services:
+     gitlab:
+       environment:
+         GITLAB_OMNIBUS_CONFIG: |
+           gitlab_rails['omniauth_providers'] = [
+              { name: 'saml',
+                label: 'Our SAML Provider',
+                args: {
+                        assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+                        idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+                        idp_sso_target_url: 'https://login.example.com/idp',
+                        issuer: 'https://gitlab.example.com',
+                        name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+                        attribute_statements: {
+                          organization: ['organization'],
+                          job_title: ['job_title']
+                        }
+                }
+              }
+           ]
+   ```
+
+1. Save the file and restart GitLab:
+
+   ```shell
+   docker compose up -d
+   ```
+
+:::TabTitle Self-compiled (source)
+
+1. [Configure OmniAuth to sync the desired attributes](omniauth.md#keep-omniauth-user-profiles-up-to-date).
+1. Edit `/home/git/gitlab/config/gitlab.yml`:
+
+   ```yaml
+   production: &base
+     omniauth:
+       providers:
+         - { name: 'saml',
+             label: 'Our SAML Provider',
+             args: {
+                     assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+                     idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+                     idp_sso_target_url: 'https://login.example.com/idp',
+                     issuer: 'https://gitlab.example.com',
+                     name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+                     attribute_statements: {
+                       organization: ['organization'],
+                       job_title: ['job_title']
+                     }
+             }
+           }
+   ```
+
+1. Save the file and restart GitLab:
+
+   ```shell
+   # For systems running systemd
+   sudo systemctl restart gitlab.target
+
+   # For systems running SysV init
+   sudo service gitlab restart
+   ```
+
+::EndTabs
+
 ### Allow for clock drift
 
 The clock of the IdP may drift slightly ahead of your system clocks.
diff --git a/doc/solutions/integrations/aws_googlecloud_ollama.md b/doc/solutions/integrations/aws_googlecloud_ollama.md
index 80b1892b59d..3af5c1075a0 100644
--- a/doc/solutions/integrations/aws_googlecloud_ollama.md
+++ b/doc/solutions/integrations/aws_googlecloud_ollama.md
@@ -2,7 +2,7 @@
 stage: Solutions Architecture
 group: Solutions Architecture
 info: This page is owned by the Solutions Architecture team.
-title: 'Self-Hosted Model: Complete AWS/Google Cloud Deployment Guide with Ollama Integration'
+title: 'GitLab Duo Self-Hosted: Complete AWS/Google Cloud Deployment Guide with Ollama Integration'
 ---
 
 DETAILS:
@@ -85,7 +85,7 @@ The rest of this guide assumes you already have a instance of GitLab up and runn
 
 #### Licensing
 
-Operating the Self-Hosted Model requires both a GitLab Ultimate license and a GitLab Duo Enterprise license. The GitLab Ultimate license works with either online or offline licensing options. This documentation assumes that both licenses have been previously obtained and are available for implementation.
+Operating GitLab Duo Self-Hosted requires both a GitLab Ultimate license and a GitLab Duo Enterprise license. The GitLab Ultimate license works with either online or offline licensing options. This documentation assumes that both licenses have been previously obtained and are available for implementation.
 
 ![License screenshot](img/self_hosted_model/license_ultimate_onlinelicense.png)
 
@@ -119,7 +119,7 @@ For details, refer to the [documentation](https://docs.gitlab.com/omnibus/settin
 
 ## Introduction
 
-Before setting up a Self-Hosted Model, it's important to understand how AI works. AI model is the AI's brain trained with data. This brain needs a framework to operate, which is called an LLM Serving Platform or simply "Serving Platform." In AWS, this is "Amazon Bedrock," in Azure, it's "Azure OpenAI Service," and for ChatGPT, it's their platform. For Anthropic, it's "Claude.". For self-hosing models, Ollama is a common choice.
+Before setting up GitLab Duo Self-Hosted, it's important to understand how AI works. AI model is the AI's brain trained with data. This brain needs a framework to operate, which is called an LLM Serving Platform or simply "Serving Platform." In AWS, this is "Amazon Bedrock," in Azure, it's "Azure OpenAI Service," and for ChatGPT, it's their platform. For Anthropic, it's "Claude.". For self-hosing models, Ollama is a common choice.
 
 For example:
 
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 08172ee7f57..f2bfb1c9305 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -1210,6 +1210,26 @@ for more information.
 
 
+### New data retention limits for vulnerabilities on GitLab.com + +
+ +- Announced in GitLab 17.9 +- Removal in GitLab 18.0 ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change)) +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/groups/gitlab-org/-/epics/16629). + +
+ +In GitLab 18.0, we are introducing a new data retention limit for GitLab.com Ultimate customers to improve system performance and reliability. The data retention limit affects how long your vulnerability data is stored for. Vulnerabilities older than 12 months that have not been updated are automatically moved to cold storage archives. These archives: + +- Remain accessible and downloadable through the GitLab UI. +- Are retained for 3 years. +- Are permanently deleted after 3 years. + +
+ +
+ ### OpenTofu CI/CD template
diff --git a/doc/user/clusters/agent/managed_kubernetes_resources.md b/doc/user/clusters/agent/managed_kubernetes_resources.md index e758ae40bf2..6558806e44c 100644 --- a/doc/user/clusters/agent/managed_kubernetes_resources.md +++ b/doc/user/clusters/agent/managed_kubernetes_resources.md @@ -6,7 +6,7 @@ title: GitLab-managed Kubernetes resources --- DETAILS: -**Tier:** Free, Premium, Ultimate +**Tier:** Premium, Ultimate **Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/16130) in GitLab 17.9 diff --git a/doc/user/glql/fields.md b/doc/user/glql/fields.md index 4ea477691e6..91af588e1c5 100644 --- a/doc/user/glql/fields.md +++ b/doc/user/glql/fields.md @@ -66,7 +66,7 @@ This page lists fields available to use as filters when querying issues or work ```plaintext type = MergeRequest and assignee = currentUser() - `` + ``` ## Approved by user diff --git a/doc/user/infrastructure/clusters/migrate_to_gitlab_agent.md b/doc/user/infrastructure/clusters/migrate_to_gitlab_agent.md index 82785443f2a..6c65c44ff9e 100644 --- a/doc/user/infrastructure/clusters/migrate_to_gitlab_agent.md +++ b/doc/user/infrastructure/clusters/migrate_to_gitlab_agent.md @@ -58,6 +58,9 @@ To migrate generic deployments: ## Migrate from GitLab-managed clusters to Kubernetes resources +DETAILS: +**Tier:** Premium, Ultimate + With GitLab-managed clusters, GitLab creates separate service accounts and namespaces for every branch and deploys by using these resources. Now, you can use [GitLab-managed Kubernetes resources](../../clusters/agent/managed_kubernetes_resources.md) to self-serve resources with enhanced security controls. diff --git a/doc/user/project/pages/_index.md b/doc/user/project/pages/_index.md index ce0143662ea..012778f7d92 100644 --- a/doc/user/project/pages/_index.md +++ b/doc/user/project/pages/_index.md @@ -252,8 +252,6 @@ To recover a stopped deployment that has not yet been deleted: DETAILS: **Tier:** Premium, Ultimate -**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated -**Status:** Beta > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129534) in GitLab 16.7 as an [experiment](../../../policy/development_stages_support.md) [with a flag](../../feature_flags.md) named `pages_multiple_versions_setting`. Disabled by default. > - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/480195) from "multiple deployments" to "parallel deployments" in GitLab 17.4. diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb index 0b9306d6b38..c409729edb6 100644 --- a/lib/gitlab/auth/o_auth/auth_hash.rb +++ b/lib/gitlab/auth/o_auth/auth_hash.rb @@ -47,9 +47,22 @@ module Gitlab end end + def organization + get_info(:organization) + end + + def job_title + get_info(:job_title) + end + def has_attribute?(attribute) - if attribute == :location + case attribute + when :location get_info(:address).present? + when :organization + get_info(:organization).present? + when :job_title + get_info(:job_title).present? else get_info(attribute).present? end diff --git a/lib/web_ide/extensions_marketplace.rb b/lib/web_ide/extensions_marketplace.rb index 18e3caf9760..85ccf4ed349 100644 --- a/lib/web_ide/extensions_marketplace.rb +++ b/lib/web_ide/extensions_marketplace.rb @@ -41,10 +41,10 @@ module WebIde # @return [Hash] def self.webide_extensions_gallery_settings(user:) Settings.get( - [:vscode_extensions_gallery_view_model], + [:vscode_extension_marketplace_view_model], user: user, vscode_extension_marketplace_feature_flag_enabled: feature_enabled?(user: user) - ).fetch(:vscode_extensions_gallery_view_model) + ).fetch(:vscode_extension_marketplace_view_model) end # Returns true if the given flag is enabled for any actor diff --git a/lib/web_ide/settings/default_settings.rb b/lib/web_ide/settings/default_settings.rb index 870e85ee09a..e3d03cbe19d 100644 --- a/lib/web_ide/settings/default_settings.rb +++ b/lib/web_ide/settings/default_settings.rb @@ -3,10 +3,6 @@ module WebIde module Settings class DefaultSettings - SETTINGS_DEPENDENCIES = { - vscode_extensions_gallery_view_model: [:vscode_extension_marketplace_metadata, :vscode_extension_marketplace] - }.freeze - # ALL WEB IDE SETTINGS ARE DECLARED HERE. # @return [Hash] def self.default_settings @@ -28,7 +24,7 @@ module WebIde { enabled: false, disabled_reason: :instance_disabled }, Hash ], - vscode_extensions_gallery_view_model: [ + vscode_extension_marketplace_view_model: [ { enabled: false, reason: :instance_disabled, help_url: '' }, Hash ] diff --git a/lib/web_ide/settings/extensions_gallery_metadata_generator.rb b/lib/web_ide/settings/extension_marketplace_metadata_generator.rb similarity index 93% rename from lib/web_ide/settings/extensions_gallery_metadata_generator.rb rename to lib/web_ide/settings/extension_marketplace_metadata_generator.rb index 219643080eb..af71de6b0c8 100644 --- a/lib/web_ide/settings/extensions_gallery_metadata_generator.rb +++ b/lib/web_ide/settings/extension_marketplace_metadata_generator.rb @@ -2,7 +2,7 @@ module WebIde module Settings - class ExtensionsGalleryMetadataGenerator + class ExtensionMarketplaceMetadataGenerator include Messages # NOTE: These `disabled_reason` enumeration values are also referenced/consumed in @@ -28,12 +28,12 @@ module WebIde options_with_defaults => { user: ::User | NilClass => user, vscode_extension_marketplace_feature_flag_enabled: TrueClass | FalseClass | NilClass => - extensions_marketplace_feature_flag_enabled + extension_marketplace_feature_flag_enabled } extension_marketplace_metadata = build_metadata( user: user, - flag_enabled: extensions_marketplace_feature_flag_enabled + flag_enabled: extension_marketplace_feature_flag_enabled ) context[:settings][:vscode_extension_marketplace_metadata] = extension_marketplace_metadata @@ -94,4 +94,4 @@ module WebIde end end -WebIde::Settings::ExtensionsGalleryMetadataGenerator.prepend_mod +WebIde::Settings::ExtensionMarketplaceMetadataGenerator.prepend_mod diff --git a/lib/web_ide/settings/extensions_gallery_metadata_validator.rb b/lib/web_ide/settings/extension_marketplace_metadata_validator.rb similarity index 94% rename from lib/web_ide/settings/extensions_gallery_metadata_validator.rb rename to lib/web_ide/settings/extension_marketplace_metadata_validator.rb index 4f0f57cd681..1d9eae4299c 100644 --- a/lib/web_ide/settings/extensions_gallery_metadata_validator.rb +++ b/lib/web_ide/settings/extension_marketplace_metadata_validator.rb @@ -2,7 +2,7 @@ module WebIde module Settings - class ExtensionsGalleryMetadataValidator + class ExtensionMarketplaceMetadataValidator include Messages # @param [Hash] context @@ -22,7 +22,7 @@ module WebIde Gitlab::Fp::Result.ok(context) else Gitlab::Fp::Result.err( - SettingsVscodeExtensionsGalleryMetadataValidationFailed.new(details: errors.join(". ")) + SettingsVscodeExtensionMarketplaceMetadataValidationFailed.new(details: errors.join(". ")) ) end end diff --git a/lib/web_ide/settings/extensions_gallery_validator.rb b/lib/web_ide/settings/extension_marketplace_validator.rb similarity index 92% rename from lib/web_ide/settings/extensions_gallery_validator.rb rename to lib/web_ide/settings/extension_marketplace_validator.rb index 750679ae18d..2fdb8d18a54 100644 --- a/lib/web_ide/settings/extensions_gallery_validator.rb +++ b/lib/web_ide/settings/extension_marketplace_validator.rb @@ -2,7 +2,7 @@ module WebIde module Settings - class ExtensionsGalleryValidator + class ExtensionMarketplaceValidator include Messages # @param [Hash] context @@ -23,7 +23,7 @@ module WebIde if errors.none? Gitlab::Fp::Result.ok(context) else - Gitlab::Fp::Result.err(SettingsVscodeExtensionsGalleryValidationFailed.new(details: errors.join(". "))) + Gitlab::Fp::Result.err(SettingsVscodeExtensionMarketplaceValidationFailed.new(details: errors.join(". "))) end end diff --git a/lib/web_ide/settings/extensions_gallery_view_model_generator.rb b/lib/web_ide/settings/extension_marketplace_view_model_generator.rb similarity index 85% rename from lib/web_ide/settings/extensions_gallery_view_model_generator.rb rename to lib/web_ide/settings/extension_marketplace_view_model_generator.rb index 418e5c3c741..2b252de6dcb 100644 --- a/lib/web_ide/settings/extensions_gallery_view_model_generator.rb +++ b/lib/web_ide/settings/extension_marketplace_view_model_generator.rb @@ -2,21 +2,21 @@ module WebIde module Settings - class ExtensionsGalleryViewModelGenerator + class ExtensionMarketplaceViewModelGenerator # @param [Hash] context # @return [Hash] def self.generate(context) - return context unless context.fetch(:requested_setting_names).include?(:vscode_extensions_gallery_view_model) + return context unless context.fetch(:requested_setting_names).include?(:vscode_extension_marketplace_view_model) - context[:settings][:vscode_extensions_gallery_view_model] = build_view_model(context) + context[:settings][:vscode_extension_marketplace_view_model] = build_view_model(context) context end - # Builds the value for :vscode_extensions_gallery_view_model + # Builds the value for :vscode_extension_marketplace_view_model # # @param [Hash] context The settings railway context - # @return [Hash] value for :vscode_extensions_gallery_view_model + # @return [Hash] value for :vscode_extension_marketplace_view_model def self.build_view_model(context) context => { options: { @@ -74,4 +74,4 @@ module WebIde end end -WebIde::Settings::ExtensionsGalleryViewModelGenerator.prepend_mod +WebIde::Settings::ExtensionMarketplaceViewModelGenerator.prepend_mod diff --git a/lib/web_ide/settings/main.rb b/lib/web_ide/settings/main.rb index 9d57692e8ae..49fb2656f80 100644 --- a/lib/web_ide/settings/main.rb +++ b/lib/web_ide/settings/main.rb @@ -12,19 +12,19 @@ module WebIde def self.get_settings(context) initial_result = Gitlab::Fp::Result.ok(context) - # TODO: Add instance-level setting for extensions gallery settings. + # TODO: Add instance-level setting for extension marketplace settings. # See https://gitlab.com/gitlab-org/gitlab/-/issues/451871 result = initial_result .map(SettingsInitializer.method(:init)) - .map(ExtensionsGalleryMetadataGenerator.method(:generate)) + .map(ExtensionMarketplaceMetadataGenerator.method(:generate)) # NOTE: EnvVarOverrideProcessor is inserted here to easily override settings for local or temporary testing # it should happen **before** validators. .and_then(Gitlab::Fp::Settings::EnvVarOverrideProcessor.method(:process)) - .and_then(ExtensionsGalleryValidator.method(:validate)) - .and_then(ExtensionsGalleryMetadataValidator.method(:validate)) + .and_then(ExtensionMarketplaceValidator.method(:validate)) + .and_then(ExtensionMarketplaceMetadataValidator.method(:validate)) # NOTE: ViewModel generator happens near the end since it depends on other settings. - .map(ExtensionsGalleryViewModelGenerator.method(:generate)) + .map(ExtensionMarketplaceViewModelGenerator.method(:generate)) .map( # As the final step, return the settings in a SettingsGetSuccessful message ->(context) do @@ -35,9 +35,9 @@ module WebIde case result in { err: SettingsEnvironmentVariableOverrideFailed => message } generate_error_response_from_message(message: message, reason: :internal_server_error) - in { err: SettingsVscodeExtensionsGalleryValidationFailed => message } + in { err: SettingsVscodeExtensionMarketplaceValidationFailed => message } generate_error_response_from_message(message: message, reason: :internal_server_error) - in { err: SettingsVscodeExtensionsGalleryMetadataValidationFailed => message } + in { err: SettingsVscodeExtensionMarketplaceMetadataValidationFailed => message } generate_error_response_from_message(message: message, reason: :internal_server_error) in { ok: SettingsGetSuccessful => message } { settings: message.content.fetch(:settings), status: :success } diff --git a/lib/web_ide/settings/messages.rb b/lib/web_ide/settings/messages.rb index ddbed575f83..819b8db932d 100644 --- a/lib/web_ide/settings/messages.rb +++ b/lib/web_ide/settings/messages.rb @@ -12,8 +12,8 @@ module WebIde SettingsCurrentSettingsReadFailed = Class.new(Gitlab::Fp::Message) SettingsEnvironmentVariableOverrideFailed = Class.new(Gitlab::Fp::Message) - SettingsVscodeExtensionsGalleryValidationFailed = Class.new(Gitlab::Fp::Message) - SettingsVscodeExtensionsGalleryMetadataValidationFailed = Class.new(Gitlab::Fp::Message) + SettingsVscodeExtensionMarketplaceValidationFailed = Class.new(Gitlab::Fp::Message) + SettingsVscodeExtensionMarketplaceMetadataValidationFailed = Class.new(Gitlab::Fp::Message) #--------------------------------------------------------- # Domain Events - message name should describe the outcome diff --git a/lib/web_ide/settings/settings_initializer.rb b/lib/web_ide/settings/settings_initializer.rb index 325b735af17..ec29038f4aa 100644 --- a/lib/web_ide/settings/settings_initializer.rb +++ b/lib/web_ide/settings/settings_initializer.rb @@ -4,7 +4,10 @@ module WebIde module Settings class SettingsInitializer SETTINGS_DEPENDENCIES = { - vscode_extensions_gallery_view_model: [:vscode_extension_marketplace_metadata, :vscode_extension_marketplace] + vscode_extension_marketplace_view_model: [ + :vscode_extension_marketplace_metadata, + :vscode_extension_marketplace + ] }.freeze # @param [Hash] context diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b6926d549d5..bdc817cefe0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -504,6 +504,11 @@ msgid_plural "%d stars" msgstr[0] "" msgstr[1] "" +msgid "%d subgroup" +msgid_plural "%d subgroups" +msgstr[0] "" +msgstr[1] "" + msgid "%d tag" msgid_plural "%d tags" msgstr[0] "" @@ -29793,6 +29798,12 @@ msgstr "" msgid "Import|An error occurred while fetching import details." msgstr "" +msgid "Import|Complete" +msgstr "" + +msgid "Import|Failed" +msgstr "" + msgid "Import|Failures for %{id}" msgstr "" @@ -29808,6 +29819,9 @@ msgstr "" msgid "Import|Import source user has an invalid status for this operation" msgstr "" +msgid "Import|In progress" +msgstr "" + msgid "Import|Maximum decompressed file size for archives from imports (MiB)" msgstr "" @@ -29835,6 +29849,9 @@ msgstr "" msgid "Import|No import details" msgstr "" +msgid "Import|Not started" +msgstr "" + msgid "Import|Partially completed" msgstr "" @@ -44456,6 +44473,9 @@ msgstr "" msgid "Profiles|Your email address was automatically set based on your %{provider_label} account" msgstr "" +msgid "Profiles|Your job title was automatically set based on your %{provider_label} account" +msgstr "" + msgid "Profiles|Your location was automatically set based on your %{provider_label} account" msgstr "" @@ -44465,6 +44485,9 @@ msgstr "" msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you." msgstr "" +msgid "Profiles|Your organization was automatically set based on your %{provider_label} account" +msgstr "" + msgid "Profiles|https://website.com" msgstr "" diff --git a/spec/frontend/search/results/components/blob_footer_spec.js b/spec/frontend/search/results/components/blob_footer_spec.js index f072d0d0a96..46f3629905b 100644 --- a/spec/frontend/search/results/components/blob_footer_spec.js +++ b/spec/frontend/search/results/components/blob_footer_spec.js @@ -54,36 +54,14 @@ describe('BlobFooter', () => { describe('component with too many results', () => { beforeEach(() => { + const chunks = []; + for (let i = 0; i < 25; i += 1) { + chunks.push(...mockDataForBlobBody.chunks); + } createComponent({ file: { ...mockDataForBlobBody, - chunks: [ - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ...mockDataForBlobBody.chunks, - ], + chunks, matchCountTotal: 200, }, position: 1, @@ -104,6 +82,12 @@ describe('BlobFooter', () => { ); }); + it(`has correct link in open state`, async () => { + findGlButton().vm.$emit('click'); + await nextTick(); + expect(findGlLink().attributes('href')).toBe('https://gitlab.com/file/test.js'); + }); + it(`tracks show more or less click`, () => { const { trackEventSpy } = bindInternalEventDocument(wrapper.element); findGlButton().vm.$emit('click', { value: 1 }); diff --git a/spec/frontend/vue_shared/components/import_history_table/mock_data.js b/spec/frontend/vue_shared/components/import_history_table/mock_data.js new file mode 100644 index 00000000000..84c851b4937 --- /dev/null +++ b/spec/frontend/vue_shared/components/import_history_table/mock_data.js @@ -0,0 +1,190 @@ +import { IMPORT_HISTORY_TABLE_STATUS } from '~/vue_shared/components/import/constants'; + +const sixMonthsAgo = () => new Date(new Date().getTime() - 190 * 24 * 60 * 60 * 1000); +const oneMonthAgo = () => new Date(new Date().getTime() - 31 * 24 * 60 * 60 * 1000); +const fiveMinutesAgo = () => new Date(new Date().getTime() - 5 * 60 * 1000); +const inFiveHours = () => new Date(new Date().getTime() + 6 * 60 * 60 * 1000); + +export const apiItems = [ + { + id: 0, + bulk_import_id: 0, + status_name: IMPORT_HISTORY_TABLE_STATUS.unstarted, + entity_type: 'project', + source_full_path: 'https://github.com/name/project.git', + full_path: 'my-group/project', + destination_name: 'project', + destination_slug: 'project', + destination_namespace: 'my-group', + parent_id: 0, + namespace_id: 2, + project_id: 0, + created_at: inFiveHours(), + migrate_projects: true, + migrate_memberships: true, + has_failures: false, + stats: { + labels: { + source: 10, + fetched: 10, + imported: 0, + }, + milestones: { + source: 10, + fetched: 10, + imported: 0, + }, + }, + }, + { + id: 1, + bulk_import_id: 1, + status_name: IMPORT_HISTORY_TABLE_STATUS.inProgress, + entity_type: 'group', + source_full_path: 'https://another.gitlab.com/groupname', + full_path: 'groupname', + destination_name: 'groupname', + destination_slug: 'groupname', + destination_namespace: 'groupname', + parent_id: 0, + namespace_id: 2, + project_id: 0, + created_at: fiveMinutesAgo(), + updated_at: fiveMinutesAgo(), + failures: [ + { + relation: 'design', + exception_message: 'custom error message', + exception_class: 'Exception', + correlation_id_value: 'dfcf583058ed4508e4c7c617bd7f0edd', + source_url: 'https://github.com/name/project.git', + source_title: 'some title', + }, + ], + migrate_projects: true, + migrate_memberships: true, + has_failures: true, + stats: { + labels: { + source: 10, + fetched: 10, + imported: 3, + }, + milestones: { + source: 10, + fetched: 10, + imported: 9, + }, + }, + nestedRow: { + id: 214, + bulk_import_id: 0, + status_name: IMPORT_HISTORY_TABLE_STATUS.inProgress, + entity_type: 'project', + source_full_path: 'https://another.gitlab.com/groupname/project.git', + full_path: 'my-group/groupname/project', + destination_name: 'project', + destination_slug: 'project', + destination_namespace: 'my-group/groupname', + parent_id: 0, + namespace_id: 2, + project_id: 0, + created_at: fiveMinutesAgo(), + updated_at: fiveMinutesAgo(), + failures: [ + { + relation: 'design', + exception_message: 'custom error message', + exception_class: 'Exception', + correlation_id_value: 'dfcf583058ed4508e4c7c617bd7f0edd', + source_url: 'https://github.com/name/project.git', + source_title: 'some title', + }, + ], + migrate_projects: true, + migrate_memberships: true, + has_failures: true, + stats: { + design: { + source: 10, + fetched: 10, + imported: 3, + }, + }, + }, + }, + // a project imported from a file + { + id: 2, + bulk_import_id: 2, + status_name: IMPORT_HISTORY_TABLE_STATUS.complete, + entity_type: 'file', + fileName: 'project2.gz', + full_path: 'my-group/project2', + destination_name: 'project2', + destination_slug: 'project2', + destination_namespace: 'my-group', + parent_id: 0, + namespace_id: 2, + project_id: 0, + created_at: oneMonthAgo(), + updated_at: oneMonthAgo(), + migrate_projects: true, + migrate_memberships: true, + has_failures: false, + stats: { + labels: { + source: 1096, + fetched: 1096, + imported: 1096, + }, + milestones: { + source: 10, + fetched: 10, + imported: 10, + }, + uploads: { + source: 123, + fetched: 123, + imported: 123, + }, + }, + }, + // A project imported from a file that errored out + { + id: 3, + bulk_import_id: 3, + status_name: IMPORT_HISTORY_TABLE_STATUS.failed, + entity_type: 'file', + fileName: 'project3.gz', + full_path: 'my-group/project3', + destination_name: 'project3', + destination_slug: 'project3', + destination_namespace: 'my-group', + parent_id: 0, + namespace_id: 2, + project_id: 0, + created_at: sixMonthsAgo(), + updated_at: sixMonthsAgo(), + migrate_projects: true, + migrate_memberships: true, + has_failures: true, + stats: {}, + failures: [ + { + relation: 'design', + exception_message: `At this point, we don't know what's happened and how to fix it. But you can try to troubleshoot it with documentation.`, + exception_class: 'Exception', + correlation_id_value: 'dfcf583058ed4508e4c7c617bd7f0edd', + source_url: 'https://github.com/name/project.git', + source_title: 'some title', + }, + ], + }, +]; + +export const basic = { + items: apiItems, + // eslint-disable-next-line no-restricted-syntax + detailsPath: 'http://docs.gitlab.com/ee/user/project/import/import_file.html', +}; diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb index 8dfa264fe94..af45c3b56d7 100644 --- a/spec/helpers/profiles_helper_spec.rb +++ b/spec/helpers/profiles_helper_spec.rb @@ -39,12 +39,14 @@ RSpec.describe ProfilesHelper, feature_category: :user_profile do stub_omniauth_setting(sync_profile_attributes: true) stub_auth0_omniauth_provider auth0_user = create(:omniauth_user, provider: example_omniauth_provider) - auth0_user.create_user_synced_attributes_metadata(provider: example_omniauth_provider, name_synced: true, email_synced: true, location_synced: true) + auth0_user.create_user_synced_attributes_metadata(provider: example_omniauth_provider, name_synced: true, email_synced: true, location_synced: true, organization_synced: true, job_title_synced: true) allow(helper).to receive(:current_user).and_return(auth0_user) expect(helper.attribute_provider_label(:email)).to eq(example_omniauth_provider_label) expect(helper.attribute_provider_label(:name)).to eq(example_omniauth_provider_label) expect(helper.attribute_provider_label(:location)).to eq(example_omniauth_provider_label) + expect(helper.attribute_provider_label(:organization)).to eq(example_omniauth_provider_label) + expect(helper.attribute_provider_label(:job_title)).to eq(example_omniauth_provider_label) end it "returns the correct omniauth provider label for users with some external attributes" do @@ -52,12 +54,14 @@ RSpec.describe ProfilesHelper, feature_category: :user_profile do stub_omniauth_setting(sync_profile_attributes: true) stub_auth0_omniauth_provider auth0_user = create(:omniauth_user, provider: example_omniauth_provider) - auth0_user.create_user_synced_attributes_metadata(provider: example_omniauth_provider, name_synced: false, email_synced: true, location_synced: false) + auth0_user.create_user_synced_attributes_metadata(provider: example_omniauth_provider, name_synced: false, email_synced: true, location_synced: false, organization_synced: false, job_title_synced: false) allow(helper).to receive(:current_user).and_return(auth0_user) expect(helper.attribute_provider_label(:name)).to be_nil expect(helper.attribute_provider_label(:email)).to eq(example_omniauth_provider_label) expect(helper.attribute_provider_label(:location)).to be_nil + expect(helper.attribute_provider_label(:organization)).to be_nil + expect(helper.attribute_provider_label(:job_title)).to be_nil end it "returns 'LDAP' for users with external email but no email provider" do diff --git a/spec/helpers/projects/ml/experiments_helper_spec.rb b/spec/helpers/projects/ml/experiments_helper_spec.rb index d7cf22469fa..4443c441557 100644 --- a/spec/helpers/projects/ml/experiments_helper_spec.rb +++ b/spec/helpers/projects/ml/experiments_helper_spec.rb @@ -144,33 +144,6 @@ RSpec.describe Projects::Ml::ExperimentsHelper, feature_category: :mlops do end end - describe '#experiments_as_data' do - let(:experiments) { [experiment] } - - subject { Gitlab::Json.parse(helper.experiments_as_data(project, experiments)) } - - before do - allow(experiment).to receive(:candidate_count).and_return(2) - end - - it 'generates the correct info' do - expected_info = { - "name" => experiment.name, - "path" => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}", - "candidate_count" => 2, - "updated_at" => experiment.updated_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), - "user" => { - "avatar_url" => experiment.user.avatar_url, - "id" => experiment.user_id, - "name" => experiment.user.name, - "path" => user_path(experiment.user) - } - } - - expect(subject[0]).to eq(expected_info) - end - end - describe '#page_info' do def paginator(cursor = nil) experiment.candidates.keyset_paginate(cursor: cursor, per_page: 1) diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb index 9c0fba63c76..e72b093fb8d 100644 --- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# This spec requires many let statements to properly test OAuth auth_hash attributes +# and their various encodings (ASCII and UTF-8). +# rubocop:disable RSpec/MultipleMemoizedHelpers -- Complex OAuth attribute testing requires multiple let statements require 'spec_helper' RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_category: :user_management do @@ -29,6 +32,8 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_categ let(:last_name_raw) { +"K\xC3\xBC\xC3\xA7\xC3\xBCk" } let(:name_raw) { +"Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" } let(:username_claim_raw) { +'onur.partner' } + let(:organization_raw) { +'GitLab' } + let(:job_title_raw) { +'Software Engineer' } let(:uid_ascii) { uid_raw.force_encoding(Encoding::ASCII_8BIT) } let(:email_ascii) { email_raw.force_encoding(Encoding::ASCII_8BIT) } @@ -36,6 +41,8 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_categ let(:first_name_ascii) { first_name_raw.force_encoding(Encoding::ASCII_8BIT) } let(:last_name_ascii) { last_name_raw.force_encoding(Encoding::ASCII_8BIT) } let(:name_ascii) { name_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:organization_ascii) { organization_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:job_title_ascii) { job_title_raw.force_encoding(Encoding::ASCII_8BIT) } let(:uid_utf8) { uid_ascii.force_encoding(Encoding::UTF_8) } let(:email_utf8) { email_ascii.force_encoding(Encoding::UTF_8) } @@ -43,6 +50,8 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_categ let(:name_utf8) { name_ascii.force_encoding(Encoding::UTF_8) } let(:first_name_utf8) { first_name_ascii.force_encoding(Encoding::UTF_8) } let(:username_claim_utf8) { username_claim_raw.force_encoding(Encoding::ASCII_8BIT) } + let(:organization_utf8) { organization_ascii.force_encoding(Encoding::UTF_8) } + let(:job_title_utf8) { job_title_ascii.force_encoding(Encoding::UTF_8) } let(:info_hash) do { @@ -55,7 +64,9 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_categ address: { locality: 'some locality', country: 'some country' - } + }, + organization: organization_ascii, + job_title: job_title_ascii } end @@ -67,6 +78,8 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_categ it { expect(auth_hash.name).to eql name_utf8 } it { expect(auth_hash.password).not_to be_empty } it { expect(auth_hash.location).to eq 'some locality, some country' } + it { expect(auth_hash.organization).to eq organization_utf8 } + it { expect(auth_hash.job_title).to eq job_title_utf8 } it { expect(auth_hash.errors).to be_empty } end @@ -183,6 +196,14 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_categ it 'forces utf8 encoding on password' do expect(auth_hash.password.encoding).to eql Encoding::UTF_8 end + + it 'forces utf8 encoding on organization' do + expect(auth_hash.organization.encoding).to eql Encoding::UTF_8 + end + + it 'forces utf8 encoding on job_title' do + expect(auth_hash.job_title.encoding).to eql Encoding::UTF_8 + end end context 'for email address length validation prior to generating a username' do @@ -301,3 +322,4 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, :aggregate_failures, feature_categ end end end +# rubocop:enable RSpec/MultipleMemoizedHelpers diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb index 02f18961fb9..ec2fb6b84ec 100644 --- a/spec/lib/gitlab/auth/o_auth/user_spec.rb +++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb @@ -24,7 +24,9 @@ RSpec.describe Gitlab::Auth::OAuth::User, :aggregate_failures, feature_category: address: { locality: 'locality', country: 'country' - } + }, + organization: 'GitLab', + job_title: 'Software Engineer' } end @@ -1146,12 +1148,19 @@ RSpec.describe Gitlab::Auth::OAuth::User, :aggregate_failures, feature_category: it "sets my-provider as the attributes provider" do expect(gl_user.user_synced_attributes_metadata.provider).to eql('my-provider') end + + it "updates the user organization and job title" do + expect(gl_user.organization).to eq(info_hash[:organization]) + expect(gl_user.job_title).to eq(info_hash[:job_title]) + expect(gl_user.user_synced_attributes_metadata.organization_synced).to be(true) + expect(gl_user.user_synced_attributes_metadata.job_title_synced).to be(true) + end end context "update only requested info" do before do stub_omniauth_setting(sync_profile_from_provider: ['my-provider']) - stub_omniauth_setting(sync_profile_attributes: %w[name location]) + stub_omniauth_setting(sync_profile_attributes: %w[name location organization job_title]) end it "updates the user name" do @@ -1167,6 +1176,13 @@ RSpec.describe Gitlab::Auth::OAuth::User, :aggregate_failures, feature_category: it "does not update the user email" do expect(gl_user.user_synced_attributes_metadata.email_synced).to be(false) end + + it "updates the user organization and job title" do + expect(gl_user.organization).to eq(info_hash[:organization]) + expect(gl_user.job_title).to eq(info_hash[:job_title]) + expect(gl_user.user_synced_attributes_metadata.organization_synced).to be(true) + expect(gl_user.user_synced_attributes_metadata.job_title_synced).to be(true) + end end context "update default_scope" do diff --git a/spec/lib/web_ide/settings/extensions_gallery_metadata_generator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_metadata_generator_spec.rb similarity index 97% rename from spec/lib/web_ide/settings/extensions_gallery_metadata_generator_spec.rb rename to spec/lib/web_ide/settings/extension_marketplace_metadata_generator_spec.rb index e5a8c12c960..1e3f42e267e 100644 --- a/spec/lib/web_ide/settings/extensions_gallery_metadata_generator_spec.rb +++ b/spec/lib/web_ide/settings/extension_marketplace_metadata_generator_spec.rb @@ -2,7 +2,7 @@ require "fast_spec_helper" -RSpec.describe WebIde::Settings::ExtensionsGalleryMetadataGenerator, feature_category: :web_ide do +RSpec.describe WebIde::Settings::ExtensionMarketplaceMetadataGenerator, feature_category: :web_ide do using RSpec::Parameterized::TableSyntax let(:input_context) do diff --git a/spec/lib/web_ide/settings/extensions_gallery_metadata_validator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_metadata_validator_spec.rb similarity index 95% rename from spec/lib/web_ide/settings/extensions_gallery_metadata_validator_spec.rb rename to spec/lib/web_ide/settings/extension_marketplace_metadata_validator_spec.rb index af8fb1b382a..6337e3b893a 100644 --- a/spec/lib/web_ide/settings/extensions_gallery_metadata_validator_spec.rb +++ b/spec/lib/web_ide/settings/extension_marketplace_metadata_validator_spec.rb @@ -2,7 +2,7 @@ require "fast_spec_helper" -RSpec.describe WebIde::Settings::ExtensionsGalleryMetadataValidator, feature_category: :web_ide do +RSpec.describe WebIde::Settings::ExtensionMarketplaceMetadataValidator, feature_category: :web_ide do include ResultMatchers let(:context) do @@ -52,7 +52,7 @@ RSpec.describe WebIde::Settings::ExtensionsGalleryMetadataValidator, feature_cat it "returns an err Result containing error details" do expect(result).to be_err_result do |message| expect(message) - .to be_a(WebIde::Settings::Messages::SettingsVscodeExtensionsGalleryMetadataValidationFailed) + .to be_a(WebIde::Settings::Messages::SettingsVscodeExtensionMarketplaceMetadataValidationFailed) message.content => { details: String => error_details } expect(error_details).to eq(expected_error_details) end diff --git a/spec/lib/web_ide/settings/extensions_gallery_validator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb similarity index 95% rename from spec/lib/web_ide/settings/extensions_gallery_validator_spec.rb rename to spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb index 9c8822c286f..5ce2522db48 100644 --- a/spec/lib/web_ide/settings/extensions_gallery_validator_spec.rb +++ b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb @@ -2,7 +2,7 @@ require "fast_spec_helper" -RSpec.describe WebIde::Settings::ExtensionsGalleryValidator, feature_category: :web_ide do +RSpec.describe WebIde::Settings::ExtensionMarketplaceValidator, feature_category: :web_ide do include ResultMatchers let(:service_url) { "https://open-vsx.org/vscode/gallery" } @@ -55,7 +55,7 @@ RSpec.describe WebIde::Settings::ExtensionsGalleryValidator, feature_category: : shared_examples "err result" do |expected_error_details:| it "returns an err Result containing error details" do expect(result).to be_err_result do |message| - expect(message).to be_a WebIde::Settings::Messages::SettingsVscodeExtensionsGalleryValidationFailed + expect(message).to be_a WebIde::Settings::Messages::SettingsVscodeExtensionMarketplaceValidationFailed message.content => { details: String => error_details } expect(error_details).to eq(expected_error_details) end diff --git a/spec/lib/web_ide/settings/extensions_gallery_view_model_generator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_view_model_generator_spec.rb similarity index 88% rename from spec/lib/web_ide/settings/extensions_gallery_view_model_generator_spec.rb rename to spec/lib/web_ide/settings/extension_marketplace_view_model_generator_spec.rb index db0c8ad8c36..0444e47354d 100644 --- a/spec/lib/web_ide/settings/extensions_gallery_view_model_generator_spec.rb +++ b/spec/lib/web_ide/settings/extension_marketplace_view_model_generator_spec.rb @@ -2,12 +2,12 @@ require "fast_spec_helper" -RSpec.describe WebIde::Settings::ExtensionsGalleryViewModelGenerator, feature_category: :web_ide do +RSpec.describe WebIde::Settings::ExtensionMarketplaceViewModelGenerator, feature_category: :web_ide do using RSpec::Parameterized::TableSyntax let(:user_class) { stub_const('User', Class.new) } let(:user) { user_class.new } - let(:requested_setting_names) { [:vscode_extensions_gallery_view_model] } + let(:requested_setting_names) { [:vscode_extension_marketplace_view_model] } let(:vscode_extension_marketplace) { { item_url: 'https://example.com/vscode/is/cooler/than/rubymine' } } let(:vscode_extension_marketplace_metadata) { { enabled: true } } let(:context) do @@ -25,7 +25,7 @@ RSpec.describe WebIde::Settings::ExtensionsGalleryViewModelGenerator, feature_ca before do # why: Stubs necessary for fast_spec_helper. See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167495#note_2290309350 - # The `spec/lib/web_ide/extensions_marketplace_spec.rb` covers everything in integration, so we should be good. + # The `spec/lib/web_ide/extension_marketplace_spec.rb` covers everything in integration, so we should be good. allow(::Gitlab::Routing).to receive_message_chain(:url_helpers, :profile_preferences_url) .with(anchor: 'integrations') .and_return('http://gdk.test/profile_preferences_url#integrations') @@ -37,7 +37,7 @@ RSpec.describe WebIde::Settings::ExtensionsGalleryViewModelGenerator, feature_ca describe '.generate' do subject(:settings_result) do - described_class.generate(context).dig(:settings, :vscode_extensions_gallery_view_model) + described_class.generate(context).dig(:settings, :vscode_extension_marketplace_view_model) end it 'by default, setting is enabled with vscode_settings' do diff --git a/spec/lib/web_ide/settings/main_spec.rb b/spec/lib/web_ide/settings/main_spec.rb index b1b2ff2df1f..3629c4b7109 100644 --- a/spec/lib/web_ide/settings/main_spec.rb +++ b/spec/lib/web_ide/settings/main_spec.rb @@ -9,11 +9,11 @@ RSpec.describe WebIde::Settings::Main, feature_category: :web_ide do let(:rop_steps) do [ [WebIde::Settings::SettingsInitializer, :map], - [WebIde::Settings::ExtensionsGalleryMetadataGenerator, :map], + [WebIde::Settings::ExtensionMarketplaceMetadataGenerator, :map], [Gitlab::Fp::Settings::EnvVarOverrideProcessor, :and_then], - [WebIde::Settings::ExtensionsGalleryValidator, :and_then], - [WebIde::Settings::ExtensionsGalleryMetadataValidator, :and_then], - [WebIde::Settings::ExtensionsGalleryViewModelGenerator, :map] + [WebIde::Settings::ExtensionMarketplaceValidator, :and_then], + [WebIde::Settings::ExtensionMarketplaceMetadataValidator, :and_then], + [WebIde::Settings::ExtensionMarketplaceViewModelGenerator, :map] ] end @@ -60,26 +60,26 @@ RSpec.describe WebIde::Settings::Main, feature_category: :web_ide do where(:case_name, :err_result_for_step, :expected_response) do [ [ - "when ExtensionsGalleryValidator returns SettingsVscodeExtensionsGalleryValidationFailed", + "when ExtensionMarketplaceValidator returns SettingsVscodeExtensionMarketplaceValidationFailed", { - step_class: WebIde::Settings::ExtensionsGalleryValidator, - returned_message: lazy { WebIde::Settings::Messages::SettingsVscodeExtensionsGalleryValidationFailed.new(err_message_content) } + step_class: WebIde::Settings::ExtensionMarketplaceValidator, + returned_message: lazy { WebIde::Settings::Messages::SettingsVscodeExtensionMarketplaceValidationFailed.new(err_message_content) } }, { status: :error, - message: lazy { "Settings VSCode extensions gallery validation failed: #{error_details}" }, + message: lazy { "Settings VSCode extension marketplace validation failed: #{error_details}" }, reason: :internal_server_error }, ], [ - "when ExtensionsGalleryMetadataValidator returns SettingsVscodeExtensionsGalleryMetadataValidationFailed", + "when ExtensionMarketplaceMetadataValidator returns SettingsVscodeExtensionMarketplaceMetadataValidationFailed", { - step_class: WebIde::Settings::ExtensionsGalleryMetadataValidator, - returned_message: lazy { WebIde::Settings::Messages::SettingsVscodeExtensionsGalleryMetadataValidationFailed.new(err_message_content) } + step_class: WebIde::Settings::ExtensionMarketplaceMetadataValidator, + returned_message: lazy { WebIde::Settings::Messages::SettingsVscodeExtensionMarketplaceMetadataValidationFailed.new(err_message_content) } }, { status: :error, - message: lazy { "Settings VSCode extensions gallery metadata validation failed: #{error_details}" }, + message: lazy { "Settings VSCode extension marketplace metadata validation failed: #{error_details}" }, reason: :internal_server_error }, ], @@ -99,7 +99,7 @@ RSpec.describe WebIde::Settings::Main, feature_category: :web_ide do [ "when an unmatched error is returned, an exception is raised", { - step_class: WebIde::Settings::ExtensionsGalleryValidator, + step_class: WebIde::Settings::ExtensionMarketplaceValidator, returned_message: lazy { Class.new(Gitlab::Fp::Message).new(err_message_content) } }, Gitlab::Fp::UnmatchedResultError diff --git a/spec/lib/web_ide/settings/settings_initializer_spec.rb b/spec/lib/web_ide/settings/settings_initializer_spec.rb index b26384b15d6..34bdb4b2cc6 100644 --- a/spec/lib/web_ide/settings/settings_initializer_spec.rb +++ b/spec/lib/web_ide/settings/settings_initializer_spec.rb @@ -18,7 +18,7 @@ RSpec.describe WebIde::Settings::SettingsInitializer, feature_category: :web_ide requested_setting_names: [ :vscode_extension_marketplace, :vscode_extension_marketplace_metadata, - :vscode_extensions_gallery_view_model + :vscode_extension_marketplace_view_model ], settings: { vscode_extension_marketplace: { @@ -33,7 +33,7 @@ RSpec.describe WebIde::Settings::SettingsInitializer, feature_category: :web_ide enabled: false, disabled_reason: :instance_disabled }, - vscode_extensions_gallery_view_model: { + vscode_extension_marketplace_view_model: { enabled: false, reason: :instance_disabled, help_url: '' @@ -42,7 +42,7 @@ RSpec.describe WebIde::Settings::SettingsInitializer, feature_category: :web_ide setting_types: { vscode_extension_marketplace: Hash, vscode_extension_marketplace_metadata: Hash, - vscode_extensions_gallery_view_model: Hash + vscode_extension_marketplace_view_model: Hash }, env_var_prefix: "GITLAB_WEB_IDE", env_var_failed_message_class: WebIde::Settings::Messages::SettingsEnvironmentVariableOverrideFailed diff --git a/spec/lib/web_ide/settings/settings_integration_spec.rb b/spec/lib/web_ide/settings/settings_integration_spec.rb index 607499863a9..65ca3d43ebd 100644 --- a/spec/lib/web_ide/settings/settings_integration_spec.rb +++ b/spec/lib/web_ide/settings/settings_integration_spec.rb @@ -118,8 +118,9 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab end it "raises an error" do - expected_err_msg = "Settings VSCode extensions gallery validation failed: root is missing required keys: " \ - "service_url, item_url, resource_url_template" + expected_err_msg = + "Settings VSCode extension marketplace validation failed: root is missing required keys: " \ + "service_url, item_url, resource_url_template" expect { vscode_extension_marketplace_setting } .to raise_error(expected_err_msg) end @@ -149,7 +150,7 @@ RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disab end it "raises an error" do - expected_err_msg = "Settings VSCode extensions gallery metadata validation failed: " \ + expected_err_msg = "Settings VSCode extension marketplace metadata validation failed: " \ "root is missing required keys: enabled" expect { vscode_extension_marketplace_metadata_setting } .to raise_error(expected_err_msg) diff --git a/spec/models/preloaders/namespace_root_ancestor_preloader_spec.rb b/spec/models/preloaders/namespace_root_ancestor_preloader_spec.rb new file mode 100644 index 00000000000..70c9d09373b --- /dev/null +++ b/spec/models/preloaders/namespace_root_ancestor_preloader_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Preloaders::NamespaceRootAncestorPreloader, feature_category: :groups_and_projects do + let_it_be(:parent_public_group) { create(:group) } + let_it_be(:parent_private_group) { create(:group, :private) } + let_it_be(:project_namespace) { create(:project_namespace, parent: parent_public_group) } + let_it_be(:public_group) { create(:group, :private, parent: parent_public_group) } + let_it_be(:private_group) { create(:group, :private, project_creation_level: nil) } + + let(:root_query_regex) do + if Feature.enabled?(:use_sql_functions_for_primary_key_lookups, Feature.current_request) + /\ASELECT.+ FROM find_namespaces_by_id\(\d+\)/ + else + /\ASELECT.+FROM "namespaces" WHERE "namespaces"."id" = \d+/ + end + end + + let(:additional_preloads) { [] } + let(:namespaces) { [project_namespace, public_group, private_group] } + let(:pristine_namespaces) { Namespace.where(id: namespaces) } + + shared_examples 'executes N matching DB queries' do |expected_query_count, query_method = nil| + it 'executes the specified root_ancestor queries' do + expect do + pristine_namespaces.each do |namespace| + root_ancestor = namespace.root_ancestor + + root_ancestor.public_send(query_method) if query_method.present? + end + end.to make_queries_matching(root_query_regex, expected_query_count) + end + + it 'strong_memoizes the correct root_ancestor' do + pristine_namespaces.each do |namespace| + expected_parent_id = namespace.root_ancestor.id == namespace.id ? nil : namespace.root_ancestor.id + + expect(namespace.parent_id).to eq(expected_parent_id) + end + end + end + + context 'when the preloader is used' do + before do + preload_ancestors + end + + context 'when no additional preloads are provided' do + it_behaves_like 'executes N matching DB queries', 0 + end + + context 'when additional preloads are provided' do + let(:additional_preloads) { [:route] } + let(:root_query_regex) { /\ASELECT.+FROM "routes" WHERE "routes"."source_id" = \d+/ } + + it_behaves_like 'executes N matching DB queries', 0, :full_path + end + end + + context 'when the preloader is not used' do + it_behaves_like 'executes N matching DB queries', 2 + end + + def preload_ancestors + described_class.new(pristine_namespaces, additional_preloads).execute + end +end diff --git a/spec/requests/projects/ml/experiments_controller_spec.rb b/spec/requests/projects/ml/experiments_controller_spec.rb index f97c7505c4f..6ab08adc333 100644 --- a/spec/requests/projects/ml/experiments_controller_spec.rb +++ b/spec/requests/projects/ml/experiments_controller_spec.rb @@ -61,47 +61,6 @@ RSpec.describe Projects::Ml::ExperimentsController, feature_category: :mlops do it 'renders the template' do expect(response).to render_template('projects/ml/experiments/index') end - - it 'does not perform N+1 sql queries' do - control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { list_experiments } - - create_list(:ml_experiments, 2, project: project, user: user) - - expect { list_experiments }.not_to exceed_all_query_limit(control_count) - end - end - - describe 'pagination' do - let_it_be(:experiments) do - create_list(:ml_experiments, 3, project: project) - end - - let(:params) { basic_params.merge(id: experiment.iid) } - - before do - stub_const("Projects::Ml::ExperimentsController::MAX_EXPERIMENTS_PER_PAGE", 2) - - list_experiments - end - - it 'fetches only MAX_CANDIDATES_PER_PAGE candidates' do - expect(assigns(:experiments).size).to eq(2) - end - - it 'paginates', :aggregate_failures do - page = assigns(:experiments) - - expect(page.first).to eq(experiments.last) - expect(page.last).to eq(experiments[1]) - - new_params = params.merge(cursor: assigns(:page_info)[:end_cursor]) - - list_experiments(new_params) - - new_page = assigns(:experiments) - - expect(new_page.first).to eq(experiments.first) - end end it_behaves_like 'requires read_model_experiments' do diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 15227103673..72af514a768 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -259,34 +259,6 @@ RSpec.describe TodoService, feature_category: :notifications do should_not_create_todo(user: skipped, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED) end - it 'does not create a todo if user was already mentioned and todo is pending' do - stub_feature_flags(multiple_todos: false) - - create(:todo, :mentioned, user: member, project: project, target: issue, author: author) - - expect { service.update_issue(issue, author, skip_users) }.not_to change(member.todos, :count) - end - - it 'does not create a todo if user was already mentioned and todo is done' do - create(:todo, :mentioned, :done, user: skipped, project: project, target: issue, author: author) - - expect { service.update_issue(issue, author, skip_users) }.not_to change(skipped.todos, :count) - end - - it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do - stub_feature_flags(multiple_todos: false) - - create(:todo, :directly_addressed, user: member, project: project, target: addressed_issue, author: author) - - expect { service.update_issue(addressed_issue, author, skip_users) }.not_to change(member.todos, :count) - end - - it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do - create(:todo, :directly_addressed, :done, user: skipped, project: project, target: addressed_issue, author: author) - - expect { service.update_issue(addressed_issue, author, skip_users) }.not_to change(skipped.todos, :count) - end - it 'does not create todo if user can not see the issue when issue is confidential' do service.update_issue(confidential_issue, john_doe) @@ -723,44 +695,38 @@ RSpec.describe TodoService, feature_category: :notifications do end end - context 'when multiple_todos are enabled' do - before do - stub_feature_flags(multiple_todos: true) - end + it 'creates a MENTIONED todo even if user already has a pending MENTIONED todo' do + create(:todo, :mentioned, user: member, project: project, target: issue, author: author) - it 'creates a MENTIONED todo even if user already has a pending MENTIONED todo' do - create(:todo, :mentioned, user: member, project: project, target: issue, author: author) + expect { service.update_issue(issue, author) }.to change(member.todos, :count) + end - expect { service.update_issue(issue, author) }.to change(member.todos, :count) - end + it 'creates a DIRECTLY_ADDRESSED todo even if user already has a pending DIRECTLY_ADDRESSED todo' do + create(:todo, :directly_addressed, user: member, project: project, target: issue, author: author) - it 'creates a DIRECTLY_ADDRESSED todo even if user already has a pending DIRECTLY_ADDRESSED todo' do - create(:todo, :directly_addressed, user: member, project: project, target: issue, author: author) + issue.update!(description: "#{member.to_reference}, what do you think?") - issue.update!(description: "#{member.to_reference}, what do you think?") + expect { service.update_issue(issue, author) }.to change(member.todos, :count) + end - expect { service.update_issue(issue, author) }.to change(member.todos, :count) - end + it 'creates an ASSIGNED todo even if user already has a pending MARKED todo' do + create(:todo, :marked, user: john_doe, project: project, target: assigned_issue, author: author) - it 'creates an ASSIGNED todo even if user already has a pending MARKED todo' do - create(:todo, :marked, user: john_doe, project: project, target: assigned_issue, author: author) + expect { service.reassigned_assignable(assigned_issue, author) }.to change(john_doe.todos, :count) + end - expect { service.reassigned_assignable(assigned_issue, author) }.to change(john_doe.todos, :count) - end + it 'does not create an ASSIGNED todo if user already has an ASSIGNED todo' do + create(:todo, :assigned, user: john_doe, project: project, target: assigned_issue, author: author) - it 'does not create an ASSIGNED todo if user already has an ASSIGNED todo' do - create(:todo, :assigned, user: john_doe, project: project, target: assigned_issue, author: author) + expect { service.reassigned_assignable(assigned_issue, author) }.not_to change(john_doe.todos, :count) + end - expect { service.reassigned_assignable(assigned_issue, author) }.not_to change(john_doe.todos, :count) - end + it 'creates multiple todos if a user is assigned and mentioned in a new issue' do + assigned_issue.description = mentions + service.new_issue(assigned_issue, author) - it 'creates multiple todos if a user is assigned and mentioned in a new issue' do - assigned_issue.description = mentions - service.new_issue(assigned_issue, author) - - should_create_todo(user: john_doe, target: assigned_issue, action: Todo::ASSIGNED) - should_create_todo(user: john_doe, target: assigned_issue, action: Todo::MENTIONED) - end + should_create_todo(user: john_doe, target: assigned_issue, action: Todo::ASSIGNED) + should_create_todo(user: john_doe, target: assigned_issue, action: Todo::MENTIONED) end end @@ -988,34 +954,6 @@ RSpec.describe TodoService, feature_category: :notifications do should_not_create_todo(user: skipped, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) end - it 'does not create a todo if user was already mentioned and todo is pending' do - stub_feature_flags(multiple_todos: false) - - create(:todo, :mentioned, user: member, project: project, target: mentioned_mr, author: author) - - expect { service.update_merge_request(mentioned_mr, author) }.not_to change(member.todos, :count) - end - - it 'does not create a todo if user was already mentioned and todo is done' do - create(:todo, :mentioned, :done, user: skipped, project: project, target: mentioned_mr, author: author) - - expect { service.update_merge_request(mentioned_mr, author, skip_users) }.not_to change(skipped.todos, :count) - end - - it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do - stub_feature_flags(multiple_todos: false) - - create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr, author: author) - - expect { service.update_merge_request(addressed_mr, author) }.not_to change(member.todos, :count) - end - - it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do - create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr, author: author) - - expect { service.update_merge_request(addressed_mr, author, skip_users) }.not_to change(skipped.todos, :count) - end - context 'with a task list' do it 'does not create todo when tasks are marked as completed' do mentioned_mr.update!(description: "- [x] Task 1\n- [X] Task 2 #{mentions}") @@ -1299,17 +1237,6 @@ RSpec.describe TodoService, feature_category: :notifications do expect(second_todo.reload).to be_done expect(third_todo.reload).to be_done end - - it 'marks related pending todo to the target MR for the user as done when the multiple_todos feature is off' do - stub_feature_flags(multiple_todos: false) - - only_todo = create(:todo, :pending, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author) - - review = Review.new(merge_request: mentioned_mr) - service.new_review(review, john_doe) - - expect(only_todo.reload).to be_done - end end end @@ -1385,56 +1312,6 @@ RSpec.describe TodoService, feature_category: :notifications do should_not_create_todo(user: non_member, target: noteable, action: Todo::DIRECTLY_ADDRESSED) should_not_create_todo(user: skipped, target: noteable, action: Todo::DIRECTLY_ADDRESSED) end - - context 'users already have pending todos and the multiple_todos feature is off' do - before do - stub_feature_flags(multiple_todos: false) - end - - let_it_be(:pending_todo_for_member) { create(:todo, :mentioned, user: member, project: project, target: noteable) } - let_it_be(:pending_todo_for_guest) { create(:todo, :mentioned, user: guest, project: project, target: noteable) } - let_it_be(:pending_todo_for_admin) { create(:todo, :mentioned, user: admin, project: project, target: noteable) } - let_it_be(:note_mentioning_1_user) do - create(:note, project: project, note: "FYI #{member.to_reference}", noteable: noteable) - end - - let_it_be(:note_mentioning_3_users) do - create(:note, project: project, note: 'FYI: ' + [member, guest, admin].map(&:to_reference).join(' '), noteable: noteable) - end - - it 'does not create a todo if user was already mentioned and todo is pending' do - expect { service.update_note(note_mentioning_1_user, author, skip_users) }.not_to change(member.todos, :count) - end - - it 'does not create N+1 queries for pending todos' do - # Excluding queries for user permissions because those do execute N+1 queries - allow_any_instance_of(User).to receive(:can?).and_return(true) - - control = ActiveRecord::QueryRecorder.new { service.update_note(note_mentioning_1_user, author, skip_users) } - - expect { service.update_note(note_mentioning_3_users, author, skip_users) }.not_to exceed_query_limit(control) - end - end - - it 'does not create a todo if user was already mentioned and todo is done' do - create(:todo, :mentioned, :done, user: skipped, project: project, target: noteable, author: author) - - expect { service.update_note(note, author, skip_users) }.not_to change(skipped.todos, :count) - end - - it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do - stub_feature_flags(multiple_todos: false) - - create(:todo, :directly_addressed, user: member, project: project, target: noteable, author: author) - - expect { service.update_note(addressed_note, author, skip_users) }.not_to change(member.todos, :count) - end - - it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do - create(:todo, :directly_addressed, :done, user: skipped, project: project, target: noteable, author: author) - - expect { service.update_note(addressed_note, author, skip_users) }.not_to change(skipped.todos, :count) - end end it 'updates cached counts when a todo is created' do