From 9d935a8ee1d63da42176db2bfa443affc0ab2261 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 15 May 2025 18:12:13 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/CODEOWNERS | 22 +- .../Security developer workflow.md | 1 + .../cop_description_with_example.yml | 160 +++++++++ GITALY_SERVER_VERSION | 2 +- Gemfile.checksum | 2 +- Gemfile.lock | 2 +- Gemfile.next.checksum | 2 +- Gemfile.next.lock | 2 +- .../boards/components/board_top_bar.vue | 2 + .../edit/components/profile_edit_app.vue | 30 +- .../edit/components/user_main_settings.vue | 173 ++++++++++ .../javascripts/profile/edit/constants.js | 6 + app/assets/javascripts/profile/edit/index.js | 39 ++- .../projects/new_v2/components/app.vue | 3 +- .../components/project_destination_select.vue | 1 - .../shared_project_creation_fields.vue | 94 ++++- .../javascripts/projects/new_v2/index.js | 4 + .../todos/components/todos_pagination.vue | 20 +- .../javascripts/visibility_level/constants.js | 12 + app/controllers/groups_controller.rb | 3 +- .../organizations/groups_controller.rb | 2 +- app/controllers/projects_controller.rb | 3 +- app/graphql/types/group_type.rb | 2 +- app/graphql/types/project_type.rb | 2 +- app/helpers/groups_helper.rb | 34 +- app/helpers/namespaces_helper.rb | 2 +- app/helpers/profiles_helper.rb | 12 + app/helpers/projects_helper.rb | 76 ++--- app/mailers/emails/groups.rb | 2 +- app/mailers/emails/projects.rb | 2 +- .../system_notes/time_tracking_service.rb | 64 ++-- .../settings/_delayed_deletion.html.haml | 6 +- .../settings/_immediately_remove.html.haml | 24 +- .../projects/_delete_card_body.html.haml | 14 +- app/views/projects/_delete_delayed.html.haml | 2 +- .../projects/_delete_immediately.html.haml | 4 - app/views/projects/new.html.haml | 2 + ...estor_marked_for_deletion_notice.html.haml | 3 +- .../settings/_restore.html.haml | 7 +- app/views/shared/projects/_actions.html.haml | 2 +- app/views/shared/projects/_removed.html.haml | 5 +- .../user_settings/profiles/show.html.haml | 2 +- .../remember_me_enabled.yml | 2 +- .../wip/your_work_groups_vue.yml | 2 + .../compliance/audit_event_reports.md | 2 +- .../img/impersonated_audit_events_v15_7.png | Bin .../img/geo_dashboard_v14_0.png | Bin 0 -> 48805 bytes .../geo/disaster_recovery/planned_failover.md | 2 +- .../runbooks/img/geo_dashboard_v14_0.png | Bin 0 -> 48805 bytes .../runbooks/planned_failover_multi_node.md | 2 +- .../runbooks/planned_failover_single_node.md | 2 +- ...eo_uploads_file_missing_details_v17_11.png | Bin .../img/geo_uploads_file_missing_v17_11.png | Bin .../synchronization_verification.md | 4 +- .../setup/img/adding_a_secondary_v15_8.png | Bin 0 -> 14698 bytes .../geo/setup/img/geo_dashboard_v14_0.png | Bin 0 -> 48805 bytes .../two_single_node_external_services.md | 4 +- .../geo/setup/two_single_node_sites.md | 4 +- .../configure_duo_features.md | 4 +- ...duo_self_hosted_disable_feature_v17_11.png | Bin ...lf_hosted_feature_configuration_v17_11.png | Bin .../img/kroki_c4_diagram_v13_7.png | Bin .../img/kroki_graphviz_diagram_v13_7.png | Bin .../img/kroki_nomnoml_diagram_v13_7.png | Bin .../img/kroki_plantuml_diagram_v13_7.png | Bin doc/administration/integration/kroki.md | 8 +- .../settings/account_and_limit_settings.md | 6 +- .../visibility_and_access_controls.md | 8 +- doc/api/invitations.md | 4 +- doc/api/settings.md | 2 +- doc/ci/cloud_deployment/_index.md | 2 +- .../img/cf_ec2_diagram_v13_5.png | Bin doc/ci/environments/_index.md | 6 +- .../configure_kubernetes_deployments.md | 2 +- .../img/deployments_view_v11_10.png | Bin .../environments_deployment_cluster_v12_8.png | Bin .../img/environments_link_url_mr_v10_1.png | Bin .../img/environments_mr_review_app_v11_10.png | Bin doc/ci/jobs/fine_grained_permissions.md | 13 +- doc/ci/runners/hosted_runners/_index.md | 4 +- .../img/build_isolation_v17_9.png | Bin ...tlab-hosted_runners_architecture_v17_0.png | Bin doc/ci/secrets/_index.md | 2 +- .../img/gitlab_vault_workflow_v13_4.png | Bin doc/ci/testing/code_coverage/_index.md | 2 +- ...ipelines_test_coverage_mr_widget_v17_3.png | Bin doc/legal/licensing_policy.md | 4 +- .../components/duo_workflow_codestyle.md | 320 +----------------- doc/topics/autodevops/stages.md | 2 +- .../checks/json_hijacking_check.md | 2 +- .../dast/browser/checks/319.1.md | 2 +- doc/user/gitlab_duo/_index.md | 14 +- doc/user/group/_index.md | 4 +- doc/user/group/manage.md | 8 +- doc/user/project/deploy_boards.md | 22 +- doc/user/project/import/cvs.md | 7 +- doc/user/project/import/tfvc.md | 2 +- .../project/issues/crosslinking_issues.md | 2 +- doc/user/project/members/_index.md | 4 +- .../ssl_tls_concepts.md | 2 +- lib/gitlab/ci/parsers/sbom/component.rb | 2 +- lib/gitlab/ci/parsers/sbom/license.rb | 33 -- lib/gitlab/ci/parsers/sbom/license/common.rb | 44 +++ .../sbom/license/container_scanning.rb | 23 ++ .../issue_and_merge_request_actions.rb | 2 +- .../spend_time_and_date_separator.rb | 7 +- lib/tasks/gitlab/tw/codeowners.rake | 4 +- locale/gitlab.pot | 15 +- qa/Gemfile | 2 +- qa/Gemfile.lock | 4 +- rubocop/cop/.rubocop.yml | 5 + spec/controllers/groups_controller_spec.rb | 3 +- .../edit/components/profile_edit_app_spec.js | 62 ++++ .../components/user_main_settings_spec.js | 173 ++++++++++ .../shared_project_creation_fields_spec.js | 74 +++- .../todos/components/todos_pagination_spec.js | 28 ++ spec/helpers/groups_helper_spec.rb | 33 ++ spec/helpers/namespaces_helper_spec.rb | 14 +- spec/helpers/profiles_helper_spec.rb | 39 ++- spec/helpers/projects_helper_spec.rb | 90 +---- .../common_spec.rb} | 32 +- .../sbom/license/container_scanning_spec.rb | 27 ++ .../spend_time_and_date_separator_spec.rb | 19 +- .../notes/quick_actions_service_spec.rb | 16 +- .../quick_actions/interpret_service_spec.rb | 8 +- .../time_tracking_service_spec.rb | 48 ++- .../license/sbom_licenses_shared_examples.rb | 59 ++++ .../templates/fine_grained_permissions.md.erb | 13 +- 128 files changed, 1462 insertions(+), 751 deletions(-) create mode 100644 .rubocop_todo/internal_affairs/cop_description_with_example.yml create mode 100644 app/assets/javascripts/profile/edit/components/user_main_settings.vue delete mode 100644 app/views/projects/_delete_immediately.html.haml rename doc/administration/{ => compliance}/img/impersonated_audit_events_v15_7.png (100%) create mode 100644 doc/administration/geo/disaster_recovery/img/geo_dashboard_v14_0.png create mode 100644 doc/administration/geo/disaster_recovery/runbooks/img/geo_dashboard_v14_0.png rename doc/administration/geo/replication/{ => troubleshooting}/img/geo_uploads_file_missing_details_v17_11.png (100%) rename doc/administration/geo/replication/{ => troubleshooting}/img/geo_uploads_file_missing_v17_11.png (100%) create mode 100644 doc/administration/geo/setup/img/adding_a_secondary_v15_8.png create mode 100644 doc/administration/geo/setup/img/geo_dashboard_v14_0.png rename doc/administration/{ => gitlab_duo_self_hosted}/img/gitlab_duo_self_hosted_disable_feature_v17_11.png (100%) rename doc/administration/{ => gitlab_duo_self_hosted}/img/gitlab_duo_self_hosted_feature_configuration_v17_11.png (100%) rename doc/administration/{ => integration}/img/kroki_c4_diagram_v13_7.png (100%) rename doc/administration/{ => integration}/img/kroki_graphviz_diagram_v13_7.png (100%) rename doc/administration/{ => integration}/img/kroki_nomnoml_diagram_v13_7.png (100%) rename doc/administration/{ => integration}/img/kroki_plantuml_diagram_v13_7.png (100%) rename doc/ci/{ => cloud_deployment}/img/cf_ec2_diagram_v13_5.png (100%) rename doc/ci/{ => environments}/img/deployments_view_v11_10.png (100%) rename doc/ci/{ => environments}/img/environments_deployment_cluster_v12_8.png (100%) rename doc/ci/{ => environments}/img/environments_link_url_mr_v10_1.png (100%) rename doc/ci/{ => environments}/img/environments_mr_review_app_v11_10.png (100%) rename doc/ci/runners/{ => hosted_runners}/img/build_isolation_v17_9.png (100%) rename doc/ci/runners/{ => hosted_runners}/img/gitlab-hosted_runners_architecture_v17_0.png (100%) rename doc/ci/{ => secrets}/img/gitlab_vault_workflow_v13_4.png (100%) rename doc/ci/testing/{ => code_coverage}/img/pipelines_test_coverage_mr_widget_v17_3.png (100%) delete mode 100644 lib/gitlab/ci/parsers/sbom/license.rb create mode 100644 lib/gitlab/ci/parsers/sbom/license/common.rb create mode 100644 lib/gitlab/ci/parsers/sbom/license/container_scanning.rb create mode 100644 spec/frontend/profile/edit/components/user_main_settings_spec.js rename spec/lib/gitlab/ci/parsers/sbom/{license_spec.rb => license/common_spec.rb} (61%) create mode 100644 spec/lib/gitlab/ci/parsers/sbom/license/container_scanning_spec.rb create mode 100644 spec/support/shared_examples/lib/gitlab/ci/parsers/sbom/license/sbom_licenses_shared_examples.rb diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index c941242aa07..dfe3bbfed25 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -592,7 +592,7 @@ lib/gitlab/checks/** /doc/administration/credentials_inventory.md @idurham /doc/administration/custom_html_header_tags.md @eread /doc/administration/custom_project_templates.md @brendan777 -/doc/administration/dedicated/ @emily.sahlani +/doc/administration/dedicated/ @lyspin /doc/administration/dedicated/hosted_runners.md @rsarangadharan /doc/administration/diff_limits.md @brendan777 /doc/administration/docs_self_host.md @axil @@ -649,8 +649,7 @@ lib/gitlab/checks/** /doc/administration/operations/gitlab_sshd.md @brendan777 /doc/administration/operations/moving_repositories.md @eread /doc/administration/package_information/ @axil -/doc/administration/packages/ @lyspin -/doc/administration/packages/_index.md @z_painter +/doc/administration/packages/ @z_painter /doc/administration/pages/ @msedlakjakubowski /doc/administration/polling.md @axil /doc/administration/raketasks/ @axil @@ -738,12 +737,12 @@ lib/gitlab/checks/** /doc/api/cluster_discovery.md @z_painter /doc/api/code_suggestions.md @jglassman1 /doc/api/commits.md @brendan777 -/doc/api/container_registry.md @lyspin -/doc/api/container_repository_protection_rules.md @lyspin +/doc/api/container_registry.md @z_painter +/doc/api/container_repository_protection_rules.md @z_painter /doc/api/custom_attributes.md @msedlakjakubowski /doc/api/dependencies.md @rdickenson /doc/api/dependency_list_export.md @rlehmann1 -/doc/api/dependency_proxy.md @lyspin +/doc/api/dependency_proxy.md @z_painter /doc/api/deploy_keys.md @z_painter /doc/api/deploy_tokens.md @z_painter /doc/api/deployments.md @z_painter @@ -933,7 +932,7 @@ lib/gitlab/checks/** /doc/ci/examples/ @lyspin /doc/ci/examples/deployment/ @z_painter /doc/ci/examples/semantic-release.md @z_painter -/doc/ci/gitlab_google_cloud_integration/ @lyspin +/doc/ci/gitlab_google_cloud_integration/ @z_painter /doc/ci/inputs/ @marcel.amirault /doc/ci/interactive_web_terminal/ @rsarangadharan /doc/ci/jobs/ @marcel.amirault @@ -1046,7 +1045,7 @@ lib/gitlab/checks/** /doc/solutions/integrations/servicenow.md @ashrafkhamis /doc/subscriptions/ @lciutacu /doc/subscriptions/gitlab_com/ @lyspin -/doc/subscriptions/gitlab_dedicated/ @emily.sahlani +/doc/subscriptions/gitlab_dedicated/ @lyspin /doc/topics/ @msedlakjakubowski /doc/topics/autodevops/ @z_painter /doc/topics/git/ @brendan777 @@ -1160,9 +1159,6 @@ lib/gitlab/checks/** /doc/user/operations_dashboard/ @z_painter /doc/user/organization/ @phillipwells /doc/user/packages/ @z_painter -/doc/user/packages/container_registry/ @lyspin -/doc/user/packages/dependency_proxy/ @lyspin -/doc/user/packages/harbor_container_registry/ @lyspin /doc/user/permissions.md @idurham /doc/user/profile/_index.md @idurham /doc/user/profile/account/ @idurham @@ -1196,8 +1192,8 @@ lib/gitlab/checks/** /doc/user/project/integrations/confluence.md @msedlakjakubowski /doc/user/project/integrations/git_guardian.md @brendan777 /doc/user/project/integrations/github.md @lyspin -/doc/user/project/integrations/google_artifact_management.md @lyspin -/doc/user/project/integrations/harbor.md @lyspin +/doc/user/project/integrations/google_artifact_management.md @z_painter +/doc/user/project/integrations/harbor.md @z_painter /doc/user/project/integrations/matrix.md @sselhorn /doc/user/project/issue_board.md @msedlakjakubowski /doc/user/project/issues/ @msedlakjakubowski diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index 1b64f496c78..fdc06057228 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -47,6 +47,7 @@ After your merge request has been approved according to our [approval guidelines - [ ] **IMPORTANT**: When this issue is ready for release (Default branch MR and backports are approved and ready to be merged), apply the ~"security-target" label. - The `gitlab-release-tools-bot` evaluates and links issues with the label to the active [Security Tracking Issue]. If the bot finds the issue is not ready to be included in the patch release, it will leave a comment on the issue explaining what needs to be done. - This issue will only be included in a patch release if it is successfully linked to the [Security Tracking Issue]. + - Refer to the ["Release Information" dashboard](https://dashboards.gitlab.net/d/delivery-release_info/delivery3a-release-information?orgId=1) for information about the next patch release, including the targeted versions, expected release date, and current status. ## Documentation and final details diff --git a/.rubocop_todo/internal_affairs/cop_description_with_example.yml b/.rubocop_todo/internal_affairs/cop_description_with_example.yml new file mode 100644 index 00000000000..7b1ceb856ba --- /dev/null +++ b/.rubocop_todo/internal_affairs/cop_description_with_example.yml @@ -0,0 +1,160 @@ +--- +# Cop supports --autocorrect. +InternalAffairs/CopDescriptionWithExample: + Details: grace period + Exclude: + - 'rubocop/cop/active_model_errors_direct_manipulation.rb' + - 'rubocop/cop/active_record_association_reload.rb' + - 'rubocop/cop/api/base.rb' + - 'rubocop/cop/api/class_level_allow_access_with_scope.rb' + - 'rubocop/cop/api/ensure_string_detail.rb' + - 'rubocop/cop/api/grape_array_missing_coerce.rb' + - 'rubocop/cop/avoid_becomes.rb' + - 'rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb' + - 'rubocop/cop/background_migration/dictionary_file.rb' + - 'rubocop/cop/background_migration/feature_category.rb' + - 'rubocop/cop/code_reuse/finder.rb' + - 'rubocop/cop/code_reuse/presenter.rb' + - 'rubocop/cop/code_reuse/serializer.rb' + - 'rubocop/cop/code_reuse/service_class.rb' + - 'rubocop/cop/code_reuse/worker.rb' + - 'rubocop/cop/database/avoid_inheritance_column.rb' + - 'rubocop/cop/database/disable_referential_integrity.rb' + - 'rubocop/cop/database/establish_connection.rb' + - 'rubocop/cop/default_scope.rb' + - 'rubocop/cop/destroy_all.rb' + - 'rubocop/cop/experiments_test_coverage.rb' + - 'rubocop/cop/feature_flag_usage.rb' + - 'rubocop/cop/file_decompression.rb' + - 'rubocop/cop/filename_length.rb' + - 'rubocop/cop/gitlab/ai/order_constants.rb' + - 'rubocop/cop/gitlab/avoid_current_organization.rb' + - 'rubocop/cop/gitlab/avoid_feature_category_not_owned.rb' + - 'rubocop/cop/gitlab/avoid_gitlab_instance_checks.rb' + - 'rubocop/cop/gitlab/avoid_uploaded_file_from_params.rb' + - 'rubocop/cop/gitlab/bounded_contexts.rb' + - 'rubocop/cop/gitlab/bulk_insert.rb' + - 'rubocop/cop/gitlab/change_timezone.rb' + - 'rubocop/cop/gitlab/const_get_inherit_false.rb' + - 'rubocop/cop/gitlab/delegate_predicate_methods.rb' + - 'rubocop/cop/gitlab/documentation_links/hardcoded_url.rb' + - 'rubocop/cop/gitlab/ee_only_class.rb' + - 'rubocop/cop/gitlab/except.rb' + - 'rubocop/cop/gitlab/feature_available_usage.rb' + - 'rubocop/cop/gitlab/feature_flag_without_actor.rb' + - 'rubocop/cop/gitlab/finder_with_find_by.rb' + - 'rubocop/cop/gitlab/hard_delete_calls.rb' + - 'rubocop/cop/gitlab/http_v2.rb' + - 'rubocop/cop/gitlab/httparty.rb' + - 'rubocop/cop/gitlab/intersect.rb' + - 'rubocop/cop/gitlab/json.rb' + - 'rubocop/cop/gitlab/license_available_usage.rb' + - 'rubocop/cop/gitlab/mark_used_feature_flags.rb' + - 'rubocop/cop/gitlab/module_with_instance_variables.rb' + - 'rubocop/cop/gitlab/policy_rule_boolean.rb' + - 'rubocop/cop/gitlab/predicate_memoization.rb' + - 'rubocop/cop/gitlab/rails_logger.rb' + - 'rubocop/cop/gitlab/rspec/avoid_setup.rb' + - 'rubocop/cop/gitlab/service_response.rb' + - 'rubocop/cop/gitlab/strong_memoize_attr.rb' + - 'rubocop/cop/gitlab/union.rb' + - 'rubocop/cop/gitlab/without_reactive_cache.rb' + - 'rubocop/cop/graphql/authorize_types.rb' + - 'rubocop/cop/graphql/descriptions.rb' + - 'rubocop/cop/graphql/enum_names.rb' + - 'rubocop/cop/graphql/enum_values.rb' + - 'rubocop/cop/graphql/graphql_name_position.rb' + - 'rubocop/cop/graphql/id_type.rb' + - 'rubocop/cop/graphql/json_type.rb' + - 'rubocop/cop/graphql/old_types.rb' + - 'rubocop/cop/graphql/resolver_type.rb' + - 'rubocop/cop/group_public_or_visible_to_user.rb' + - 'rubocop/cop/include_sidekiq_worker.rb' + - 'rubocop/cop/inject_enterprise_edition_module.rb' + - 'rubocop/cop/migration/add_concurrent_foreign_key.rb' + - 'rubocop/cop/migration/add_concurrent_index.rb' + - 'rubocop/cop/migration/add_index.rb' + - 'rubocop/cop/migration/add_limit_to_text_columns.rb' + - 'rubocop/cop/migration/add_reference.rb' + - 'rubocop/cop/migration/add_timestamps.rb' + - 'rubocop/cop/migration/async_post_migrate_only.rb' + - 'rubocop/cop/migration/avoid_finalize_background_migration.rb' + - 'rubocop/cop/migration/background_migration_missing_active_concern.rb' + - 'rubocop/cop/migration/background_migration_record.rb' + - 'rubocop/cop/migration/background_migrations.rb' + - 'rubocop/cop/migration/batch_migrations_post_only.rb' + - 'rubocop/cop/migration/batched_migration_base_class.rb' + - 'rubocop/cop/migration/change_column_null_on_high_traffic_table.rb' + - 'rubocop/cop/migration/complex_indexes_require_name.rb' + - 'rubocop/cop/migration/create_table_with_foreign_keys.rb' + - 'rubocop/cop/migration/datetime.rb' + - 'rubocop/cop/migration/drop_table.rb' + - 'rubocop/cop/migration/migration_record.rb' + - 'rubocop/cop/migration/migration_with_milestone.rb' + - 'rubocop/cop/migration/prevent_adding_columns.rb' + - 'rubocop/cop/migration/prevent_feature_flags_usage.rb' + - 'rubocop/cop/migration/prevent_global_enable_lock_retries_with_disable_ddl_transaction.rb' + - 'rubocop/cop/migration/prevent_index_creation.rb' + - 'rubocop/cop/migration/prevent_strings.rb' + - 'rubocop/cop/migration/refer_to_index_by_name.rb' + - 'rubocop/cop/migration/remove_column.rb' + - 'rubocop/cop/migration/remove_concurrent_index.rb' + - 'rubocop/cop/migration/remove_index.rb' + - 'rubocop/cop/migration/safer_boolean_column.rb' + - 'rubocop/cop/migration/schedule_async.rb' + - 'rubocop/cop/migration/schema_addition_methods_no_post.rb' + - 'rubocop/cop/migration/sidekiq_queue_migrate.rb' + - 'rubocop/cop/migration/timestamps.rb' + - 'rubocop/cop/migration/unfinished_dependencies.rb' + - 'rubocop/cop/migration/update_column_in_batches.rb' + - 'rubocop/cop/migration/versioned_migration_class.rb' + - 'rubocop/cop/migration/with_lock_retries_disallowed_method.rb' + - 'rubocop/cop/migration/with_lock_retries_with_change.rb' + - 'rubocop/cop/performance/active_record_subtransaction_methods.rb' + - 'rubocop/cop/performance/active_record_subtransactions.rb' + - 'rubocop/cop/performance/ar_count_each.rb' + - 'rubocop/cop/performance/ar_exists_and_present_blank.rb' + - 'rubocop/cop/performance/readlines_each.rb' + - 'rubocop/cop/project_path_helper.rb' + - 'rubocop/cop/put_group_routes_under_scope.rb' + - 'rubocop/cop/put_project_routes_under_scope.rb' + - 'rubocop/cop/qa/ambiguous_page_object_name.rb' + - 'rubocop/cop/qa/element_with_pattern.rb' + - 'rubocop/cop/qa/fabricate_usage.rb' + - 'rubocop/cop/qa/feature_flags.rb' + - 'rubocop/cop/qa/selector_usage.rb' + - 'rubocop/cop/redis_queue_usage.rb' + - 'rubocop/cop/rspec/any_instance_of.rb' + - 'rubocop/cop/rspec/avoid_conditional_statements.rb' + - 'rubocop/cop/rspec/avoid_test_prof.rb' + - 'rubocop/cop/rspec/be_success_matcher.rb' + - 'rubocop/cop/rspec/before_all.rb' + - 'rubocop/cop/rspec/duplicate_spec_location.rb' + - 'rubocop/cop/rspec/env_assignment.rb' + - 'rubocop/cop/rspec/expect_gitlab_tracking.rb' + - 'rubocop/cop/rspec/factories_in_migration_specs.rb' + - 'rubocop/cop/rspec/factory_bot/avoid_create.rb' + - 'rubocop/cop/rspec/factory_bot/inline_association.rb' + - 'rubocop/cop/rspec/factory_bot/strategy_in_callback.rb' + - 'rubocop/cop/rspec/httparty_basic_auth.rb' + - 'rubocop/cop/rspec/modify_sidekiq_middleware.rb' + - 'rubocop/cop/rspec/top_level_describe_path.rb' + - 'rubocop/cop/rspec/web_mock_enable.rb' + - 'rubocop/cop/safe_params.rb' + - 'rubocop/cop/scalability/bulk_perform_with_context.rb' + - 'rubocop/cop/scalability/cron_worker_context.rb' + - 'rubocop/cop/scalability/file_uploads.rb' + - 'rubocop/cop/scalability/idempotent_worker.rb' + - 'rubocop/cop/scalability/random_cron_schedule.rb' + - 'rubocop/cop/sidekiq/enforce_database_health_signal_deferral.rb' + - 'rubocop/cop/sidekiq_api_usage.rb' + - 'rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb' + - 'rubocop/cop/sidekiq_options_queue.rb' + - 'rubocop/cop/sidekiq_redis_call.rb' + - 'rubocop/cop/static_translation_definition.rb' + - 'rubocop/cop/style/regexp_literal_mixed_preserve.rb' + - 'rubocop/cop/usage_data/distinct_count_by_large_foreign_key.rb' + - 'rubocop/cop/usage_data/histogram_with_large_table.rb' + - 'rubocop/cop/usage_data/instrumentation_superclass.rb' + - 'rubocop/cop/usage_data/large_table.rb' + - 'rubocop/cop/user_admin.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b234d1ef035..527698fc40b 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -bad4daa394053a0f74090abdda47f9035f4e3f9a +a6759e58c21ed1c1238cc9a67abfdb0de9f87bd8 diff --git a/Gemfile.checksum b/Gemfile.checksum index dc2bb2e73d3..b65150eb84d 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -645,7 +645,7 @@ {"name":"rubocop-rspec","version":"3.0.5","platform":"ruby","checksum":"c6a8e29fb1b00d227c32df159e92f5ebb9e0ff734e52955fb13aff5c74977e0f"}, {"name":"rubocop-rspec_rails","version":"2.30.0","platform":"ruby","checksum":"888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46"}, {"name":"ruby-fogbugz","version":"0.3.0","platform":"ruby","checksum":"5e04cde474648f498a71cf1e1a7ab42c66b953862fbe224f793ec0a7a1d5f657"}, -{"name":"ruby-lsp","version":"0.23.15","platform":"ruby","checksum":"5e3dd3e775ba477854e577dc4aa5f0d3d59f32d90f8622787f01080d4e84e09f"}, +{"name":"ruby-lsp","version":"0.23.17","platform":"ruby","checksum":"d2b570a18cf76c24d75439f1a69e40c224ec4c523aba842cc434da4f7cb20e56"}, {"name":"ruby-lsp-rails","version":"0.3.31","platform":"ruby","checksum":"670aed466e54b5632e4907b8dedb91d8b144917c42513e013d656af175bf8c76"}, {"name":"ruby-lsp-rspec","version":"0.1.22","platform":"ruby","checksum":"e982edf5cd6ec1530c3f5fa7e423624ad00532ebeff7fc94e02c7516a9b759c0"}, {"name":"ruby-magic","version":"0.6.0","platform":"ruby","checksum":"7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101"}, diff --git a/Gemfile.lock b/Gemfile.lock index eada15d3db8..7e5efcf2b77 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1739,7 +1739,7 @@ GEM ruby-fogbugz (0.3.0) crack (~> 0.4) multipart-post (~> 2.0) - ruby-lsp (0.23.15) + ruby-lsp (0.23.17) language_server-protocol (~> 3.17.0) prism (>= 1.2, < 2.0) rbs (>= 3, < 4) diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index dc2bb2e73d3..b65150eb84d 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -645,7 +645,7 @@ {"name":"rubocop-rspec","version":"3.0.5","platform":"ruby","checksum":"c6a8e29fb1b00d227c32df159e92f5ebb9e0ff734e52955fb13aff5c74977e0f"}, {"name":"rubocop-rspec_rails","version":"2.30.0","platform":"ruby","checksum":"888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46"}, {"name":"ruby-fogbugz","version":"0.3.0","platform":"ruby","checksum":"5e04cde474648f498a71cf1e1a7ab42c66b953862fbe224f793ec0a7a1d5f657"}, -{"name":"ruby-lsp","version":"0.23.15","platform":"ruby","checksum":"5e3dd3e775ba477854e577dc4aa5f0d3d59f32d90f8622787f01080d4e84e09f"}, +{"name":"ruby-lsp","version":"0.23.17","platform":"ruby","checksum":"d2b570a18cf76c24d75439f1a69e40c224ec4c523aba842cc434da4f7cb20e56"}, {"name":"ruby-lsp-rails","version":"0.3.31","platform":"ruby","checksum":"670aed466e54b5632e4907b8dedb91d8b144917c42513e013d656af175bf8c76"}, {"name":"ruby-lsp-rspec","version":"0.1.22","platform":"ruby","checksum":"e982edf5cd6ec1530c3f5fa7e423624ad00532ebeff7fc94e02c7516a9b759c0"}, {"name":"ruby-magic","version":"0.6.0","platform":"ruby","checksum":"7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index eada15d3db8..7e5efcf2b77 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -1739,7 +1739,7 @@ GEM ruby-fogbugz (0.3.0) crack (~> 0.4) multipart-post (~> 2.0) - ruby-lsp (0.23.15) + ruby-lsp (0.23.17) language_server-protocol (~> 3.17.0) prism (>= 1.2, < 2.0) rbs (>= 3, < 4) diff --git a/app/assets/javascripts/boards/components/board_top_bar.vue b/app/assets/javascripts/boards/components/board_top_bar.vue index 412f2937cf4..2ba468a2d00 100644 --- a/app/assets/javascripts/boards/components/board_top_bar.vue +++ b/app/assets/javascripts/boards/components/board_top_bar.vue @@ -116,12 +116,14 @@ export default { :board="board" :is-swimlanes-on="isSwimlanesOn" :filters="filters" + class="gl-min-w-0" @setFilters="$emit('setFilters', $event)" /> diff --git a/app/assets/javascripts/profile/edit/components/profile_edit_app.vue b/app/assets/javascripts/profile/edit/components/profile_edit_app.vue index 1acc47ff0c1..485796c01f2 100644 --- a/app/assets/javascripts/profile/edit/components/profile_edit_app.vue +++ b/app/assets/javascripts/profile/edit/components/profile_edit_app.vue @@ -3,18 +3,21 @@ import { nextTick } from 'vue'; import { GlForm, GlButton, GlFormGroup } from '@gitlab/ui'; import { VARIANT_DANGER, VARIANT_INFO, createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; +import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils'; import SetStatusForm from '~/set_status_modal/set_status_form.vue'; import SettingsSection from '~/vue_shared/components/settings/settings_section.vue'; import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue'; import { isUserBusy, computedClearStatusAfterValue } from '~/set_status_modal/utils'; import { AVAILABILITY_STATUS } from '~/set_status_modal/constants'; -import { i18n, statusI18n, timezoneI18n } from '../constants'; +import { i18n, statusI18n, timezoneI18n, mainI18n } from '../constants'; import UserAvatar from './user_avatar.vue'; +import UserMainSettings from './user_main_settings.vue'; export default { components: { UserAvatar, + UserMainSettings, GlForm, GlFormGroup, GlButton, @@ -30,7 +33,13 @@ export default { 'currentClearStatusAfter', 'timezones', 'userTimezone', + 'userMainSettings', ], + provide() { + return { + i18n: this.$options.i18n, + }; + }, props: { profilePath: { type: String, @@ -52,6 +61,7 @@ export default { clearStatusAfter: null, }, timezone: this.userTimezone, + userMainSetting: this.userMainSettings, }; }, computed: { @@ -88,6 +98,11 @@ export default { formData.append('user[avatar]', this.avatarBlob, 'avatar.png'); } + const mainSettingForm = convertObjectPropsToSnakeCase(this.userMainSetting); + Object.entries(mainSettingForm).forEach(([key, value]) => { + formData.append(`user[${key}]`, value); + }); + formData.append('user[timezone]', this.timezone); try { @@ -138,11 +153,15 @@ export default { onTimezoneInput(selectedTimezone) { this.timezone = selectedTimezone.identifier || ''; }, + onMainSettingChange(updatedUserSettings) { + this.userMainSetting = updatedUserSettings; + }, }, i18n: { ...i18n, ...statusI18n, ...timezoneI18n, + ...mainI18n, }, }; @@ -175,13 +194,20 @@ export default { :description="$options.i18n.setTimezoneDescription" class="js-search-settings-section" > - + + + + diff --git a/app/assets/javascripts/projects/new_v2/index.js b/app/assets/javascripts/projects/new_v2/index.js index 6b7bbb2ae53..fee9c87089e 100644 --- a/app/assets/javascripts/projects/new_v2/index.js +++ b/app/assets/javascripts/projects/new_v2/index.js @@ -30,6 +30,8 @@ export function initNewProjectForm() { canSelectNamespace, canCreateProject, userProjectLimit, + restrictedVisibilityLevels, + defaultProjectVisibility, importHistoryPath, importGitlabEnabled, importGitlabImportPath, @@ -70,6 +72,8 @@ export function initNewProjectForm() { canSelectNamespace: parseBoolean(canSelectNamespace), canCreateProject: parseBoolean(canCreateProject), userProjectLimit: parseInt(userProjectLimit, 10), + restrictedVisibilityLevels: JSON.parse(restrictedVisibilityLevels), + defaultProjectVisibility, importHistoryPath, importGitlabEnabled: parseBoolean(importGitlabEnabled), importGitlabImportPath, diff --git a/app/assets/javascripts/todos/components/todos_pagination.vue b/app/assets/javascripts/todos/components/todos_pagination.vue index 339dbad9e7f..b00a4a2b5db 100644 --- a/app/assets/javascripts/todos/components/todos_pagination.vue +++ b/app/assets/javascripts/todos/components/todos_pagination.vue @@ -2,11 +2,13 @@ import { GlKeysetPagination } from '@gitlab/ui'; import PageSizeSelector from '~/vue_shared/components/page_size_selector.vue'; import { DEFAULT_PAGE_SIZE } from '~/todos/constants'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; export const CURSOR_CHANGED_EVENT = 'cursor-changed'; export default { components: { + LocalStorageSync, GlKeysetPagination, PageSizeSelector, }, @@ -95,13 +97,15 @@ export default { diff --git a/app/assets/javascripts/visibility_level/constants.js b/app/assets/javascripts/visibility_level/constants.js index be6ea12119d..d0b2f874790 100644 --- a/app/assets/javascripts/visibility_level/constants.js +++ b/app/assets/javascripts/visibility_level/constants.js @@ -57,6 +57,18 @@ export const ORGANIZATION_VISIBILITY_TYPE = { ), }; +export const PROJECT_VISIBILITY_LEVEL_DESCRIPTIONS = { + [VISIBILITY_LEVEL_PUBLIC_STRING]: s__( + 'VisibilityLevel|Project access must be granted explicitly to each user. If this project is part of a group, access is granted to members of the group.', + ), + [VISIBILITY_LEVEL_INTERNAL_STRING]: s__( + 'VisibilityLevel|The project can be accessed by any logged in user except external users.', + ), + [VISIBILITY_LEVEL_PRIVATE_STRING]: s__( + 'VisibilityLevel|The project can be accessed without any authentication.', + ), +}; + export const GROUP_VISIBILITY_LEVEL_DESCRIPTIONS = { [VISIBILITY_LEVEL_PUBLIC_STRING]: s__( 'VisibilityLevel|The group and any public projects can be viewed without any authentication.', diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 5d11faeacf9..efb540176d4 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -193,8 +193,7 @@ class GroupsController < Groups::ApplicationController message: format( _("'%{group_name}' has been scheduled for deletion and will be deleted on %{date}."), group_name: group.name, - # FIXME: Replace `group.marked_for_deletion_on` with `group` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 - date: helpers.permanent_deletion_date_formatted(group.marked_for_deletion_on) + date: helpers.permanent_deletion_date_formatted(group) ) } end diff --git a/app/controllers/organizations/groups_controller.rb b/app/controllers/organizations/groups_controller.rb index 9fff5c495e9..c7aeffbf5f4 100644 --- a/app/controllers/organizations/groups_controller.rb +++ b/app/controllers/organizations/groups_controller.rb @@ -38,7 +38,7 @@ module Organizations result = ::Groups::MarkForDeletionService.new(group, current_user).execute if result[:status] == :success - removal_time = helpers.permanent_deletion_date_formatted(Date.current) + removal_time = helpers.permanent_deletion_date_formatted message = _("'%{group_name}' has been scheduled for removal on %{removal_time}.") render json: { message: format(message, group_name: group.name, removal_time: removal_time) } diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e8ae7b8db19..a050ec19764 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -207,8 +207,7 @@ class ProjectsController < Projects::ApplicationController flash[:toast] = format( _("Deleting project '%{project_name}'. All data will be removed on %{date}."), project_name: @project.full_name, - # FIXME: Replace `project.marked_for_deletion_at` with `project` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 - date: helpers.permanent_deletion_date_formatted(@project.marked_for_deletion_at) + date: helpers.permanent_deletion_date_formatted(@project) ) redirect_to dashboard_projects_path, status: :found end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 3d500c7ff86..052a99e29b6 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -478,7 +478,7 @@ module Types def permanent_deletion_date return unless group.adjourned_deletion? - permanent_deletion_date_formatted(Date.current) + permanent_deletion_date_formatted end private diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 4b25de23c65..1514e226295 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -1050,7 +1050,7 @@ module Types def permanent_deletion_date return unless project.adjourned_deletion_configured? - permanent_deletion_date_formatted(Date.current) + permanent_deletion_date_formatted end private diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 8fd1457560e..16ff7ce89f7 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -90,18 +90,38 @@ module GroupsHelper } end + def delete_delayed_group_message(group) + safe_format( + _("This action will place this group, including its subgroups and projects, in a pending deletion state " \ + "for %{deletion_adjourned_period} days, and delete it permanently on %{date}."), + deletion_adjourned_period: group.deletion_adjourned_period, + date: tag.strong(permanent_deletion_date_formatted) + ) + end + + def delete_immediately_group_scheduled_for_deletion_message(group) + safe_format( + _('This group is scheduled for deletion on %{date}. ' \ + 'This action will permanently delete this group, ' \ + 'including its subgroups and projects, %{strongOpen}immediately%{strongClose}. ' \ + 'This action cannot be undone.'), + date: tag.strong(permanent_deletion_date_formatted(group)), + strongOpen: ''.html_safe, + strongClose: ''.html_safe + ) + end + def remove_group_message(group, permanently_remove) return permanently_delete_group_message(group) if permanently_remove return permanently_delete_group_message(group) unless group.adjourned_deletion? return permanently_delete_group_message(group) if group.marked_for_deletion? - date = permanent_deletion_date_formatted(Date.current) - - message = _("The contents of this group, its subgroups and projects will be permanently deleted after " \ - "%{deletion_adjourned_period} days on %{date}. After this point, your data cannot be recovered.") - ERB::Util.html_escape(message) % { - date: tag.strong(date), deletion_adjourned_period: group.deletion_adjourned_period - } + safe_format( + _("The contents of this group, its subgroups and projects will be permanently deleted after " \ + "%{deletion_adjourned_period} days on %{date}. After this point, your data cannot be recovered."), + deletion_adjourned_period: group.deletion_adjourned_period, + date: tag.strong(permanent_deletion_date_formatted) + ) end def permanently_delete_group_message(group) diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index f64075908d1..d209d6814f4 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -114,7 +114,7 @@ module NamespacesHelper Rails.application.routes.url_helpers.group_usage_quotas_url(group.root_ancestor, *args) end - def permanent_deletion_date_formatted(container_or_date, format: '%F') + def permanent_deletion_date_formatted(container_or_date = Date.current, format: '%F') date = if container_or_date.respond_to?(:self_deletion_scheduled_deletion_created_on) container_or_date.self_deletion_scheduled_deletion_created_on diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index 5ae24e894a3..3b909f4078b 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -77,6 +77,18 @@ module ProfilesHelper user_path: user_path(current_user), timezones: timezone_data_with_unique_identifiers.to_json, user_timezone: user.timezone, + id: user.id, + name: user.name, + pronouns: user.pronouns, + location: user.location, + pronunciation: user.pronunciation, + website_url: user.website_url, + job_title: user.job_title, + organization: user.organization, + bio: user.bio, + include_private_contributions: user.include_private_contributions?.to_s, + achievements_enabled: user.achievements_enabled.to_s, + private_profile: user.private_profile?.to_s, **user_status_properties(user) } end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 15ef37076ee..fdeb64410e3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -117,14 +117,17 @@ module ProjectsHelper def remove_project_message(project) if project.delayed_deletion_ready? - _("Deleting a project places it into a read-only state until %{date}, " \ - "at which point the project will be permanently deleted. Are you ABSOLUTELY sure?" - ) % { date: permanent_deletion_date_formatted(Date.current) } + format( + _("Deleting a project places it into a read-only state until %{date}, " \ + "at which point the project will be permanently deleted. Are you ABSOLUTELY sure?"), + date: permanent_deletion_date_formatted(Date.current) + ) else - _( - "You are going to delete %{project_full_name}. Deleted projects " \ - "CANNOT be restored! Are you ABSOLUTELY sure?" - ) % { project_full_name: project.full_name } + format( + _("You are going to delete %{project_full_name}. Deleted projects " \ + "CANNOT be restored! Are you ABSOLUTELY sure?"), + project_full_name: project.full_name + ) end end @@ -653,7 +656,7 @@ module ProjectsHelper def project_delete_delayed_button_data(project, button_text = nil) project_delete_button_shared_data(project, button_text).merge({ restore_help_path: help_page_path('user/project/working_with_projects.md', anchor: 'restore-a-project'), - delayed_deletion_date: permanent_deletion_date_formatted(Date.current), + delayed_deletion_date: permanent_deletion_date_formatted, form_path: project_path(project) }) end @@ -754,38 +757,24 @@ module ProjectsHelper dashboard_projects_landing_paths.include?(request.path) && !current_user.authorized_projects.exists? end - def scheduled_for_deletion?(project) - project.marked_for_deletion_at.present? + def delete_delayed_project_message(project) + safe_format( + _("This action will place this project, including all its resources, in a pending deletion state " \ + "for %{deletion_adjourned_period} days, and delete it permanently on %{date}."), + deletion_adjourned_period: project.deletion_adjourned_period, + date: tag.strong(permanent_deletion_date_formatted) + ) end - def delete_delayed_message(project) - date = permanent_deletion_date_formatted(Date.current) - - if project.delayed_deletion_ready? - message = _("This action will place this project, including all its resources, in a pending deletion state " \ - "for %{deletion_adjourned_period} days, and delete it permanently on %{date}.") - ERB::Util.html_escape(message) % delete_message_data(project).merge(date: tag.strong(date), - deletion_adjourned_period: project.deletion_adjourned_period) - else - delete_permanently_message - end - end - - def delete_immediately_message(project) - return delete_permanently_message unless project.adjourned_deletion? - return delete_delayed_message(project) unless project.marked_for_deletion_on - - date = permanent_deletion_date_formatted(project.marked_for_deletion_on) - - message = _('This project is scheduled for deletion on %{date}. ' \ - 'This action will permanently delete this project, ' \ - 'including all its resources, %{strongOpen}immediately%{strongClose}. This action cannot be undone.') - - ERB::Util.html_escape(message) % delete_message_data(project).merge(date: tag.strong(date)) - end - - def delete_permanently_message - _('This action will permanently delete this project, including all its resources.') + def delete_immediately_project_scheduled_for_deletion_message(project) + safe_format( + _('This project is scheduled for deletion on %{date}. ' \ + 'This action will permanently delete this project, ' \ + 'including all its resources, %{strongOpen}immediately%{strongClose}. This action cannot be undone.'), + date: tag.strong(permanent_deletion_date_formatted(project)), + strongOpen: ''.html_safe, + strongClose: ''.html_safe + ) end def project_delete_immediately_button_data(project, button_text = nil) @@ -808,17 +797,6 @@ module ProjectsHelper private - def delete_message_data(project) - { - project_path_with_namespace: project.path_with_namespace, - project: project.path, - strongOpen: ''.html_safe, - strongClose: ''.html_safe, - codeOpen: ''.html_safe, - codeClose: ''.html_safe - } - end - def project_delete_button_shared_data(project, button_text = nil) merge_requests_count = Projects::AllMergeRequestsCountService.new(project).count issues_count = Projects::AllIssuesCountService.new(project).count diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb index 505b5e3597d..92ddde02862 100644 --- a/app/mailers/emails/groups.rb +++ b/app/mailers/emails/groups.rb @@ -22,7 +22,7 @@ module Emails @group = ::Group.find(group_id) @user = ::User.find(recipient_id) @deletion_due_in_days = ::Gitlab::CurrentSettings.deletion_adjourned_period.days - @deletion_date = permanent_deletion_date_formatted(@group.marked_for_deletion_on, format: '%B %-d, %Y') + @deletion_date = permanent_deletion_date_formatted(@group, format: '%B %-d, %Y') email_with_layout( to: @user.email, diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 92557cb7450..bc3a77e9a04 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -36,7 +36,7 @@ module Emails @project = ::Project.find(project_id) @user = ::User.find(recipient_id) @deletion_due_in_days = ::Gitlab::CurrentSettings.deletion_adjourned_period.days - @deletion_date = permanent_deletion_date_formatted(@project.marked_for_deletion_on, format: '%B %-d, %Y') + @deletion_date = permanent_deletion_date_formatted(@project, format: '%B %-d, %Y') email_with_layout( to: @user.email, diff --git a/app/services/system_notes/time_tracking_service.rb b/app/services/system_notes/time_tracking_service.rb index 6ebf1215a25..53f4b7e9116 100644 --- a/app/services/system_notes/time_tracking_service.rb +++ b/app/services/system_notes/time_tracking_service.rb @@ -62,25 +62,16 @@ module SystemNotes # # Returns the created Note object def change_time_spent + update_activity_counter time_spent = noteable.time_spent if time_spent == :reset body = "removed time spent" + create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) else - spent_at = noteable.spent_at&.to_date - parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs) - action = time_spent > 0 ? 'added' : 'subtracted' - - text_parts = ["#{action} #{parsed_time} of time spent"] - text_parts << "at #{spent_at}" if spent_at && spent_at != DateTime.current.to_date - body = text_parts.join(' ') + spent_at = noteable.spent_at + time_spent_note(time_spent, spent_at) end - - if noteable.is_a?(Issue) - issue_activity_counter.track_issue_time_spent_changed_action(author: author, project: project) - end - - create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) end # Called when a timelog is added to an issuable @@ -96,35 +87,44 @@ module SystemNotes # Returns the created Note object def created_timelog(timelog) time_spent = timelog.time_spent - spent_at = timelog.spent_at&.to_date - parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs) - action = time_spent > 0 ? 'added' : 'subtracted' + spent_at = timelog.spent_at - text_parts = ["#{action} #{parsed_time} of time spent"] - text_parts << "at #{spent_at}" if spent_at && spent_at != DateTime.current.to_date - body = text_parts.join(' ') - - if noteable.is_a?(Issue) - issue_activity_counter.track_issue_time_spent_changed_action(author: author, project: project) - end - - create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) + update_activity_counter + time_spent_note(time_spent, spent_at) end def remove_timelog(timelog) time_spent = timelog.time_spent - spent_at = timelog.spent_at&.to_date + spent_at = timelog.spent_at parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent) - body = "deleted #{parsed_time} of spent time" - body += " from #{spent_at}" if spent_at - + body = "deleted #{parsed_time} of spent time from #{formatted_spent_at(spent_at)}" create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) end private + def update_activity_counter + return unless noteable.is_a?(Issue) + + issue_activity_counter.track_issue_time_spent_changed_action(author: author, project: project) + end + + def formatted_spent_at(spent_at) + spent_at ||= DateTime.current + timezone = author.timezone || Time.zone.name + spent_at.in_time_zone(timezone) + end + + def time_spent_note(time_spent, spent_at) + parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs) + action = time_spent > 0 ? 'added' : 'subtracted' + + body = "#{action} #{parsed_time} of time spent at #{formatted_spent_at(spent_at)}" + create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) + end + def changed_date_body(changed_dates) %w[start_date due_date].each_with_object([]) do |date_field, word_array| next unless changed_dates.key?(date_field) @@ -157,14 +157,14 @@ module SystemNotes def time_estimate_system_note parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate) previous_estimate = noteable.previous_changes['time_estimate']&.at(0) || 0 - parsed_previous_restimate = Gitlab::TimeTrackingFormatter.output(previous_estimate) + parsed_previous_estimate = Gitlab::TimeTrackingFormatter.output(previous_estimate) if previous_estimate == 0 "added time estimate of #{parsed_time}" elsif noteable.time_estimate == 0 - "removed time estimate of #{parsed_previous_restimate}" + "removed time estimate of #{parsed_previous_estimate}" else - "changed time estimate to #{parsed_time} from #{parsed_previous_restimate}" + "changed time estimate to #{parsed_time} from #{parsed_previous_estimate}" end end end diff --git a/app/views/groups/settings/_delayed_deletion.html.haml b/app/views/groups/settings/_delayed_deletion.html.haml index 4e8a8413da3..33a4cc95453 100644 --- a/app/views/groups/settings/_delayed_deletion.html.haml +++ b/app/views/groups/settings/_delayed_deletion.html.haml @@ -1,6 +1,5 @@ -- return if group.marked_for_deletion? +- return if group.self_deletion_scheduled? - remove_form_id = local_assigns.fetch(:remove_form_id, nil) -- date = permanent_deletion_date_formatted(Date.current) = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - c.with_header do @@ -9,7 +8,6 @@ - c.with_body do = form_tag(group, method: :delete, id: remove_form_id) do - %p - = html_escape(_("This action will place this group, including its subgroups and projects, in a pending deletion state for %{deletion_delayed_period} days, and delete it permanently on %{date}.")) % { date: tag.strong(date), deletion_delayed_period: group.deletion_adjourned_period } + %p= delete_delayed_group_message(group) = render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id diff --git a/app/views/groups/settings/_immediately_remove.html.haml b/app/views/groups/settings/_immediately_remove.html.haml index 747e6cf1148..b9a67c9f862 100644 --- a/app/views/groups/settings/_immediately_remove.html.haml +++ b/app/views/groups/settings/_immediately_remove.html.haml @@ -1,18 +1,16 @@ +- return unless group.self_deletion_scheduled? + - remove_form_id = local_assigns.fetch(:remove_form_id, nil) --# FIXME: Replace `Date.current` with `group` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 -- date = permanent_deletion_date_formatted(Date.current) -- if group.marked_for_deletion? - = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - - c.with_header do - .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0.gl-text-feedback-danger= _('Delete group immediately') += render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| + - c.with_header do + .gl-flex.gl-grow + %h4.gl-text-base.gl-leading-24.gl-m-0.gl-text-feedback-danger= _('Delete group immediately') - - c.with_body do - = form_tag(group, method: :delete, id: remove_form_id) do - %p - = html_escape(_("This group is scheduled for deletion on %{date}. This action will permanently delete this group, including its subgroups and projects, %{strong_open}immediately%{strong_close}. This action cannot be undone.")) % { date: tag.strong(date), strong_open: ''.html_safe, strong_close: ''.html_safe } + - c.with_body do + = form_tag(group, method: :delete, id: remove_form_id) do + %p= delete_immediately_group_scheduled_for_deletion_message(group) - = hidden_field_tag(:permanently_remove, true) + = hidden_field_tag(:permanently_remove, true) - = render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id, button_text: _('Delete group immediately') + = render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id, button_text: _('Delete group immediately') diff --git a/app/views/projects/_delete_card_body.html.haml b/app/views/projects/_delete_card_body.html.haml index 1b03017614d..0a69eb8a578 100644 --- a/app/views/projects/_delete_card_body.html.haml +++ b/app/views/projects/_delete_card_body.html.haml @@ -1,11 +1,9 @@ -- if @project.adjourned_deletion? - -# Adjourned deletion feature is configured and available globally or at the namespace level - - if scheduled_for_deletion?(@project) - -# Project has already been marked for delayed deletion, permanently delete this project - = render 'delete_immediately', button_text: _('Delete project immediately') +- if @project.delayed_deletion_ready? + - if @project.self_deletion_scheduled? + %p= delete_immediately_project_scheduled_for_deletion_message(@project) + #js-project-delete-button{ data: project_delete_immediately_button_data(@project, _('Delete project immediately')) } - else - -# Mark for delayed deletion = render 'delete_delayed' - else - -# Adjourned deletion feature is not available, permanently delete the project - = render 'delete_immediately' + %p= _('This action will permanently delete this project, including all its resources.') + #js-project-delete-button{ data: project_delete_immediately_button_data(@project) } diff --git a/app/views/projects/_delete_delayed.html.haml b/app/views/projects/_delete_delayed.html.haml index 8446707a96b..fc1c370d1b8 100644 --- a/app/views/projects/_delete_delayed.html.haml +++ b/app/views/projects/_delete_delayed.html.haml @@ -1,2 +1,2 @@ -%p= delete_delayed_message(@project) +%p= delete_delayed_project_message(@project) #js-project-delayed-delete-button{ data: project_delete_delayed_button_data(@project) } diff --git a/app/views/projects/_delete_immediately.html.haml b/app/views/projects/_delete_immediately.html.haml deleted file mode 100644 index c5c378b4423..00000000000 --- a/app/views/projects/_delete_immediately.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -- button_text = local_assigns.fetch(:button_text, nil) - -%p= delete_immediately_message(@project) -#js-project-delete-button{ data: project_delete_immediately_button_data(@project, button_text) } diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 4d2bb6b519d..ebfef15ee08 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -25,6 +25,8 @@ can_select_namespace: current_user.can_select_namespace?.to_s, can_create_project: current_user.can_create_project?.to_s, user_project_limit: current_user.projects_limit, + restricted_visibility_levels: restricted_visibility_levels, + default_project_visibility: default_project_visibility, import_history_path: import_history_index_path, import_gitlab_enabled: gitlab_project_import_enabled?.to_s, import_gitlab_import_path: new_import_gitlab_project_path, diff --git a/app/views/shared/groups_projects/_self_or_ancestor_marked_for_deletion_notice.html.haml b/app/views/shared/groups_projects/_self_or_ancestor_marked_for_deletion_notice.html.haml index b78012cadf2..0bf38f0d73b 100644 --- a/app/views/shared/groups_projects/_self_or_ancestor_marked_for_deletion_notice.html.haml +++ b/app/views/shared/groups_projects/_self_or_ancestor_marked_for_deletion_notice.html.haml @@ -2,8 +2,7 @@ - return unless context.scheduled_for_deletion_in_hierarchy_chain? - context_pending_deletion = context.first_scheduled_for_deletion_in_hierarchy_chain --# FIXME: Replace `context_pending_deletion.marked_for_deletion_on` with `context_pending_deletion` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 -- date = permanent_deletion_date_formatted(context_pending_deletion.marked_for_deletion_on) +- date = permanent_deletion_date_formatted(context_pending_deletion) - context_name = context.is_a?(Group) ? _('group') : _('project') - group_marked_for_deletion = _("This group and its subgroups and projects are pending deletion, and will be deleted on %{date}.").html_safe % { date: tag.strong(date) } diff --git a/app/views/shared/groups_projects/settings/_restore.html.haml b/app/views/shared/groups_projects/settings/_restore.html.haml index e0f71b7e215..0d9c727f9f8 100644 --- a/app/views/shared/groups_projects/settings/_restore.html.haml +++ b/app/views/shared/groups_projects/settings/_restore.html.haml @@ -1,8 +1,7 @@ - return unless context.is_a?(Group) || context.is_a?(Project) -- return unless context.marked_for_deletion? +- return unless context.self_deletion_scheduled? --# FIXME: Replace `context.marked_for_deletion_on` with `context` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 -- date = permanent_deletion_date_formatted(context.marked_for_deletion_on) +- date = permanent_deletion_date_formatted(context) - if context.is_a?(Group) - context_name = _('group') @@ -18,6 +17,6 @@ - c.with_body do %p - = (_("This %{context} has been scheduled for deletion on %{strongStart}%{date}%{strongEnd}. To cancel the scheduled deletion, you can restore this %{context}, including all its resources.") % { context: context_name, strongStart: "", strongEnd: "", date: date }).html_safe + = safe_format(_("This %{context} has been scheduled for deletion on %{date}. To cancel the scheduled deletion, you can restore this %{context}, including all its resources."), context: context_name, date: tag.strong(date)) = render Pajamas::ButtonComponent.new(variant: :confirm, method: :post, href: restore_path) do = _('Restore %{context}') % { context: context_name } diff --git a/app/views/shared/projects/_actions.html.haml b/app/views/shared/projects/_actions.html.haml index df563d60654..96577f8245c 100644 --- a/app/views/shared/projects/_actions.html.haml +++ b/app/views/shared/projects/_actions.html.haml @@ -1,4 +1,4 @@ -- if project.adjourned_deletion_configured? && scheduled_for_deletion?(project) +- if project.delayed_deletion_ready? && project.self_deletion_scheduled? = render Pajamas::ButtonComponent.new(category: :tertiary, href: project_restore_path(project), method: :post, diff --git a/app/views/shared/projects/_removed.html.haml b/app/views/shared/projects/_removed.html.haml index 5cbec0e710e..2df86d87f2d 100644 --- a/app/views/shared/projects/_removed.html.haml +++ b/app/views/shared/projects/_removed.html.haml @@ -1,4 +1,4 @@ -- return unless project.adjourned_deletion_configured? && scheduled_for_deletion?(project) +- return unless project.delayed_deletion_ready? && project.self_deletion_scheduled? .gl-flex.gl-items-center.gl-flex-wrap.project-title %span.small @@ -6,6 +6,5 @@ = _("Marked For Deletion At - %{deletion_time}") % { deletion_time: marked_for_deletion_at } .gl-flex.gl-items-center.gl-flex-wrap.project-title %p.small - -# FIXME: Replace `project.marked_for_deletion_at` with `project` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 - - permanent_deletion_time = permanent_deletion_date_formatted(project.marked_for_deletion_at, format: Date::DATE_FORMATS[:medium]) + - permanent_deletion_time = permanent_deletion_date_formatted(project, format: Date::DATE_FORMATS[:medium]) = _("Scheduled Deletion At - %{permanent_deletion_time}") % { permanent_deletion_time: permanent_deletion_time } diff --git a/app/views/user_settings/profiles/show.html.haml b/app/views/user_settings/profiles/show.html.haml index 284b2355fc0..fcd4ee8d8a6 100644 --- a/app/views/user_settings/profiles/show.html.haml +++ b/app/views/user_settings/profiles/show.html.haml @@ -5,7 +5,7 @@ - @force_desktop_expanded_sidebar = true - if Feature.enabled?(:edit_user_profile_vue, current_user) - .js-user-profile{ data: user_profile_data(@user) } + .js-user-profile-edit{ data: user_profile_data(@user) } - else = gitlab_ui_form_for @user, url: user_settings_profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f| = render ::Layouts::SettingsSectionComponent.new(s_("Profiles|Public avatar")) do |c| diff --git a/config/application_setting_columns/remember_me_enabled.yml b/config/application_setting_columns/remember_me_enabled.yml index f09947d234f..f856d893660 100644 --- a/config/application_setting_columns/remember_me_enabled.yml +++ b/config/application_setting_columns/remember_me_enabled.yml @@ -5,7 +5,7 @@ clusterwide: false column: remember_me_enabled db_type: boolean default: 'true' -description: Enable [**Remember me** setting](../administration/settings/account_and_limit_settings.md#turn-remember-me-on-or-off). +description: Enable [**Remember me** setting](../administration/settings/account_and_limit_settings.md#configure-the-remember-me-option). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/369133) in GitLab 16.0. encrypted: false gitlab_com_different_than_default: false diff --git a/config/feature_flags/wip/your_work_groups_vue.yml b/config/feature_flags/wip/your_work_groups_vue.yml index 77238d36c0a..29497188929 100644 --- a/config/feature_flags/wip/your_work_groups_vue.yml +++ b/config/feature_flags/wip/your_work_groups_vue.yml @@ -1,6 +1,8 @@ --- name: your_work_groups_vue feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/502477 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183596 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/542790 milestone: '17.10' group: group::organizations type: wip diff --git a/doc/administration/compliance/audit_event_reports.md b/doc/administration/compliance/audit_event_reports.md index 6f887266ebc..afbf80657e3 100644 --- a/doc/administration/compliance/audit_event_reports.md +++ b/doc/administration/compliance/audit_event_reports.md @@ -98,7 +98,7 @@ When a user is [impersonated](../admin_area.md#user-impersonation), their action - Audit events include information about the impersonating administrator. - Extra audit events are recorded for the start and end of the administrator's impersonation session. -![An audit event with an impersonated user.](../img/impersonated_audit_events_v15_7.png) +![An audit event with an impersonated user.](img/impersonated_audit_events_v15_7.png) ## Time zones diff --git a/doc/administration/img/impersonated_audit_events_v15_7.png b/doc/administration/compliance/img/impersonated_audit_events_v15_7.png similarity index 100% rename from doc/administration/img/impersonated_audit_events_v15_7.png rename to doc/administration/compliance/img/impersonated_audit_events_v15_7.png diff --git a/doc/administration/geo/disaster_recovery/img/geo_dashboard_v14_0.png b/doc/administration/geo/disaster_recovery/img/geo_dashboard_v14_0.png new file mode 100644 index 0000000000000000000000000000000000000000..6d183fc6bd237e5e278584bac3c24c00ce7f4b0d GIT binary patch literal 48805 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 TK*0kG0|3(E3Sw2FhJpVFq}&)< literal 0 HcmV?d00001 diff --git a/doc/administration/geo/disaster_recovery/planned_failover.md b/doc/administration/geo/disaster_recovery/planned_failover.md index 4cbe3b73fa7..63b0a8e2e66 100644 --- a/doc/administration/geo/disaster_recovery/planned_failover.md +++ b/doc/administration/geo/disaster_recovery/planned_failover.md @@ -160,7 +160,7 @@ On the **secondary** site: objects aren't yet replicated (shown in gray), consider giving the site more time to complete - ![Geo admin dashboard showing the synchronization status of a secondary site](../replication/img/geo_dashboard_v14_0.png) + ![Geo admin dashboard showing the synchronization status of a secondary site](img/geo_dashboard_v14_0.png) If any objects are failing to replicate, this should be investigated before scheduling the maintenance window. Following a planned failover, anything that diff --git a/doc/administration/geo/disaster_recovery/runbooks/img/geo_dashboard_v14_0.png b/doc/administration/geo/disaster_recovery/runbooks/img/geo_dashboard_v14_0.png new file mode 100644 index 0000000000000000000000000000000000000000..6d183fc6bd237e5e278584bac3c24c00ce7f4b0d GIT binary patch literal 48805 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 TK*0kG0|3(E3Sw2FhJpVFq}&)< literal 0 HcmV?d00001 diff --git a/doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md index a747938c2db..1a8bfb622c4 100644 --- a/doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md +++ b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_multi_node.md @@ -86,7 +86,7 @@ On the **secondary** site: objects aren't replicated (shown in gray), consider giving the site more time to complete. - ![Replication status](../../replication/img/geo_dashboard_v14_0.png) + ![Replication status](img/geo_dashboard_v14_0.png) If any objects are failing to replicate, this should be investigated before scheduling the maintenance window. After a planned failover, anything that diff --git a/doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md index 4fb8016f0f1..4755235ea8a 100644 --- a/doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md +++ b/doc/administration/geo/disaster_recovery/runbooks/planned_failover_single_node.md @@ -71,7 +71,7 @@ and there should be no failures (shown in red). If a large proportion of objects aren't yet replicated (shown in gray), consider giving the site more time to complete. -![Geo admin dashboard showing the synchronization status of a secondary site.](../../replication/img/geo_dashboard_v14_0.png) +![Geo admin dashboard showing the synchronization status of a secondary site.](img/geo_dashboard_v14_0.png) If any objects are failing to replicate, this should be investigated before scheduling the maintenance window. After a planned failover, anything that diff --git a/doc/administration/geo/replication/img/geo_uploads_file_missing_details_v17_11.png b/doc/administration/geo/replication/troubleshooting/img/geo_uploads_file_missing_details_v17_11.png similarity index 100% rename from doc/administration/geo/replication/img/geo_uploads_file_missing_details_v17_11.png rename to doc/administration/geo/replication/troubleshooting/img/geo_uploads_file_missing_details_v17_11.png diff --git a/doc/administration/geo/replication/img/geo_uploads_file_missing_v17_11.png b/doc/administration/geo/replication/troubleshooting/img/geo_uploads_file_missing_v17_11.png similarity index 100% rename from doc/administration/geo/replication/img/geo_uploads_file_missing_v17_11.png rename to doc/administration/geo/replication/troubleshooting/img/geo_uploads_file_missing_v17_11.png diff --git a/doc/administration/geo/replication/troubleshooting/synchronization_verification.md b/doc/administration/geo/replication/troubleshooting/synchronization_verification.md index 91d7c74f971..6d625fc0f5b 100644 --- a/doc/administration/geo/replication/troubleshooting/synchronization_verification.md +++ b/doc/administration/geo/replication/troubleshooting/synchronization_verification.md @@ -409,9 +409,9 @@ When missing files or inconsistencies are present, you can encounter entries in The same errors are also reflected in the UI under **Admin > Geo > Sites** when reviewing the synchronization status of specific replicables. In this scenario, a specific *upload* is missing: -![The Geo Uploads replicable dashboard displaying all failed errors.](../img/geo_uploads_file_missing_v17_11.png) +![The Geo Uploads replicable dashboard displaying all failed errors.](img/geo_uploads_file_missing_v17_11.png) -![The Geo Uploads replicable dashboard displaying missing file error.](../img/geo_uploads_file_missing_details_v17_11.png) +![The Geo Uploads replicable dashboard displaying missing file error.](img/geo_uploads_file_missing_details_v17_11.png) #### Clean up inconsistencies diff --git a/doc/administration/geo/setup/img/adding_a_secondary_v15_8.png b/doc/administration/geo/setup/img/adding_a_secondary_v15_8.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c99e6551e3920b80e07486ffa4ade1f67768c7 GIT binary patch literal 14698 zcmcI~bx<77x9`FxY;Y2SLvVt-dw?LpHMj(KcU|0ryA#~qVR851?(S~OL)6-UUggOgHu@NV<-7C8Ngi$8ioRyQsymC5H@%Z3u zeQWQeJM&2NuifZ>>MmB8H&9f+|@BDQ&n^nKy^}M$9 z_)S&MI$QRWotc_oH`&TE*=&X!QSg^!q)98bR=S=DE6eq zJtHM!fA>(`z0giUuw-a6yJhLPH&8ilzGHf4XQtzLw={6*g^$~%Z0bbWDs<-P**hfr z^||L^y2!2N{9wGSG$AO}DL6Q(K4unQ6N@`gy0

kEtE%kW6Io{Qq;hV#1 zPoY@AaNqV*dyGxAucx4l)$vT~*?d!mi>8T@IG{EpYWDB!^Tpc0Kz(&qk};QIbn7N-?%C7Dna9#=M#&Jd z|JM5Ulu^!#gP2gv=xLBkVyLA;f?v5_Om|({#LMnZbxm5Z#1Ds31u9PZaO79Hi{j9Js5PBcgOL7pg(X%m2 z%hkddgz6-pyP@(ajSB`Z=HpHFAW)jxm%7372d{9JIrnicH0#UJGv`zCcW>_c-;Y(h z+k=7;v2OkfnI&ZCVqYcvhsVdzLxbF@#dzR>m*rRSQ=hQ%EF&TF-f32>>B+-=vx6tM zDxRn1jGBljwkj%>ucpFdAwMHR9}JOu+E1CSm;v*(2F>y)G_u1;O5Q*9_2=&$h(o@Y z_wyIKyoBnj$VSl54ev>OL|h0!S2OoYR&AO!gPUR{OvPzb;MKm<|7+|t_=U{qrJ!*T zpc}TL+j%aHsR5e03q*Z#LFo_|P@70uRmmkt%Oya{{NmBGV5h38Q<4a^Fh3vs9il%h z*6B$~uJWz1HzjrVaKRXqI@DKLEPL1B&>>WmG@Vd+sK7BwZjn(hvcn;=oky<}`Rd8jaAXs3Y}c`iF2_7za#?V^tyG&iD=AtPJ9tyF8;$pwI0}8ot;b7>$#Uy-EuE;Iw9G52*SW)Uw?u0*X6F26H9N8u%J=K zd1SdgZLfl&7+UCymH_^Ca$P_o+kFC+`4R~Lkm+c>G8IRvmo+oQLWK=I8D-|s)m%43;U1A6)AWo(CUvKm#@YswocAUA`+M* z)AQ<$3A$2^YekG-(0Qs#=3*k-kkOoLYZuJT%ZN!Y2Tv^cd!M@En5MwyXn;O6!>If*!Or{8ID=l zfi$#%5;&Z~82I?5#UHpCUktQzW{_BCz_;D}#2))w?>%fAA|x(nvck08xPUu(;{7Y@ zLTxihrE~>Sb*I8At6E{@<)bwqPgYtQ2J=!2J5ee9Rgwo?o@RicV*fP7`ft5(AkkUuM|Z}QatHa ztQS{!u57iM{s1Oa<5Cu3Qw5Vl0px~CPDbY8_m6OYS{Cx<(4LemJa!g2%P)N_6DR4S zI}6RJqaLx=e^Bsm$ovGbyk;+FJlhHP=cl$V%>?8$em(E1SshB(M><v~~#-+0*7;{`r+FSiB7Hv_|N15u4N zw3OWh&h<(7c#PJj?DeMY*E~5eA8xb~4hIt)P_Gn@-BMQh>u(-(TG0)6t@eXqNWOaF^$bE4YKaezNUe1U;o4` zN%WM3pUOo^J6b;KFvGsz@bo7FNcGmKVydih|JDIb?Qnce0yi(P7Pr5e8goh8Qph5x zvbhAQl-KdAP_+^lzJt-FJ`Ik4#hpP~Dh5)?*1{XE;{qdeg&mhnFDkYPf3X0y)|B87 z${P~_jHaa+GVVzX9NkSTzqBF3R6s|ypq>)p1RP2QFYPf%O?s+lMZ1P@3R3Mi1isnx z>UxeYHF`R{`@4m6FRU_wh{~(I2qxKYSG9A)kgx+SuK<}XIIUs#Huj2*4MusPD7DKiEnN=84serWg(GHTP=UDApfw84^8e$P$LGLN;qsLDPzZF>1 zv8b+EXdx1QGW+G$&Wd1Asp^G$Jplg&#hGul{W)*7U zc69En6O4(Ibji4Q2U3KqmTQ^WZ~mG>P`>S18>mqKL#nB1%?|Dp+gU(5 zT|tCpz|UoUHr*L{y;+!ev-O7Ok}yxh+RIJG%k;!BB|aIU?Fjmg#G+gz@~A8``!Ue)7_kEypzO4CAc8AY4?sFeD}G?D)Gpq&UJt)%B%P0@uz!|zs$Q)2BiS5RXTv)n3bZITi6VV9)*P)Ig}ZB@RdIOBvTFjWsr)Ts*^4TiV8)(EkBQxwMU8R@{GmS68ATjHV} zXOzoUNW4RTC4(`i`;4;WZeDWw#nMRB3gMgXKE*U3bd|oNS{)Z5br;gHy84}?U%=EL!k;?uW z%wLsp<{goJQoOzd1w7DS6HD*9kl-bFK&(}OVJXfRMbM#62{6Qo{RZPL;?A$M z_7GGxrn5K3@6;xw4!7WhjeWg1!>*s&=RTjQWx{beu7pVP7omLiA7FCS%{3%YO<-xP7R(Kywf? z<8f)vJIa}Nog#+u%BAmVu>-~)!DuzGc!xA2s8WdQ;Tjf0o?D#AH{uT)DFCz{WrWQawKZI&EXw+wvU0x!m?V<8nTo z^5e#2=gy%CkJv!*G|S(QciJY?{MN8*&L+1cC6t1md~r~B`4Yrls(<%vE>{HS;a zvP1rShoGfHo3Aq~iMwBSpJ+xM&h-%hv9GR{+pb`})7mLZ$6CFPbJeRiW@(SMAlH?2 zovz}qrw{y6Q?$YEr%j7M=HbhAjo>X~;G8r8i4WX=j~QE^?Em~{xfIbl-TfqUxXs8D zy4dvl>i#r#M;oVO>#8dT?$T8+1+l~VbkSA~_&Z%uQLi)t2Ea?$2kRLzpc9LfjdA%X z*wV~@?tO|Y-WyY9p+Z9z}l1!iAYv#>i5X8VQhuChC1TV(dYHYJDrz zk$k=zXzqSqh!4=EzVhuz&m)@{@P`2?GOzYn7s3M2;M<9!1OX5wc#+f$#YN)6v%<)=8K7{*c`^=K9TPPgxXd6y$2D$elFD%hcB(Y-(jZ9iFKC%qnF5xyPhavG(X* zKgbY5#}4N$=IQE3na_qCpz+a|V0MU0q){lz@#|;|Uar&+@OgY>#bQR6{hTwwBp2&q zJazC3^G=N!vm`TD+3LqbZU(q%v{+ivfpLtx0QGg5z2UtRc;w~R+|dLf&y2BKm~Fi? zf~Ba3$w@-Xwujc`f{rJCzSFZx7C|s4y;%3E8A1Q*wh3SdmFJ4 zct>?jUg6W;Pzz-P@5)_S4)|M3`xW18zfO;pg~jO7CvP8%-^u2KhxVWB&tYQcZIDZs zxUTFhnt!QJN3|GQjc_H&uT-lvcD3I~A`^+h(fox~0}5l^v)i7O(qzsgrI=b-7-Ed2 z+G{`)NL5(OOHA4oSg9LZ~8@xHcF*^Jw>d6ykP9n{n#r915c=wrK9r{X34T8_3mK8vKjqN z0tGHIv*G##v~qCmFe#G*gSOYU?9J~H)@YFg|F3yQ#Z8QJ5EhDZ-D*>5;nRfAiCj%I zQw;|OJ2z-`f+4_ek@plaqUbliL$^7d7_FNTdEk@`|3S?`vID*L0;9{$^N$pv4U>}X zHi#o@@pRy>XB*^ek7a4G+e(h`h?qc+>!?{40bG(l1$F1qoQ%D>pbH~k?!a?L(C7gQfc3;5w*n)G= zMJFZcAMtid-2Hx0U1VveX~8W|yxfMQYmY{&+z3-$z%*p3RJ6SXE>u+M32xu`GKr&Z zqn!j0>nd>0#m1O_L@@N77_l>&d@f945I8sG*Ev2Vh^Zda#*tb>UN`TYy$Yq1W>(|S zHZVZ`%kI$(7xTxT&To*^geZjbB_o%z=K8!MsWV@m&E{5jC(<%egB*FARFmeW$5Wn- zQBUA#^4cHHNylK~`1U5<{jom>oDm9l`&gY%p_u`Y;yi4vj6rNMB^o@*;?n(uIAgNQ zix$iH`+} zSg_(2Ce5-d+3BHD?Zm^`9dSzk*nwU6sSLDMx(JWo>w@ZVgoG;4)86$+|7c>w>L*hJ z=6$s5#A^S5%nPy8OKDu?+%E@jGqq&SDN4SV9t#lrM~SC%&Bml&zrRl7UrdMqu@J+f zl(*1XlAJph(r8YUNluxIo1|33lDkOH8dW>vs7B9+c91=O%y0fa)2XucXu>3m_v!_yC)KR)K$*GAw)F4ZDm$tRqUeK`e1%(hWpG1pajZ$hf?}(j_lB6f z`$C0U+zxzPQVb69I3@L0q3m$KwP|ju%6l$n{hD)4CKYO$B#k<&L4PyYRLLqtU*Cnb z>hmx>uHk7m-~Px!hFp4nbJJ#mYvQaT?fuu%&4kDH!nU6hw{R?k5qaC5MCS%>^YESS zCX*2xUXMM=&>}VmGFeB0N|=k%QjN2C6C+c*j!~foLjZLjEOm6xYiYMzo_a3$auJ)k zEcRB=UT+aygSR`$>32$A)xugT@`5?PuCh?}l?p>mhM{b?G(HmY+uFct9N7UMlFK8K<_*s?v>bRr!A3g=bT6;98?H8?&Hv z8~}Hj>e}mJ8O{=p{lsGt?JPPi=xB+=(m&hhz|h?b4>8zaF++ZUCO@)Y6|~PS+s`+Q zL(ju^NmIVyjI+DKxgb%9;#L&oYo36m8^do5+?Z<#Tw<79cB#~E@ULFs0lEs5Aukg@ zTL7nDO}RHGvg~0AaPF^F_{-k`w53N@2H6Xa+qcoGp7Y<}g#l6#b6qK>hMJ&wux4b-`IW)y9H&d1SYh> zPogq#^!?;ojpqIKXhRCJ6cF8kjDt1VBJ}9PVxIBD+p_mO#*9cJHBqZ^$o=W?SYYkI zVGfdVSwSWJZ*vu(ghsYkyt9G}!o_dGaxg6x#jEkzvVP9cDW{izmm{2*sHmLrXFNHP zZ<_KjdV)aA?YtaY1ASP^`>YEvEtVBiIP@jvVlT z!zb$<4sh+8k}#1Tq3ypdMiW1@7u{16_a$C(`J)v8<(-Vr*?`bZ_7H+l7x3Db zs1bd%_?KI2pbgN5$Of(pnExq;do|$oV;(2&2Hd*fI_-!O`Xsn&>fCuYLEInD2MAVl zwNd7kALUoUaXTX+E^jzwc4BWPS_a^MNP9P4?6V@`2p#Qgn47kaO_}@cZQtIFYEJTx zT%pq@iQ_C15n*&88JEX*|LM!-S*7N{Q6|O3VFfqjKRS##eXg;s$iIbuDIv?)#){0- zmLdfI%Yrpac%jQ{=U>pmNcCbYzFq#csiT%;SNQ1){EvWd)K6t-q7Pc!;GtfP>}z{*q50El>>$|wDrS3 zh>I5u3sZc+zaq5K)Gva>b^s&{4tGdJVqYxw%c2 z)!x;Elp%zRUdAwxC5vXkZ){zV*^jB^le$&bv#T#ZMC%Cd*HVv~esH4sz&K{@x1XD6 zfEH7Z6tPZt=3yAHdbM<6^CR~|Jhvts1??vxKNDHd0FmM3_Q)+sfG)eQmb zf&lzY$i&{UL~ur4w&$5(&Bfz1*c~P4W=D0NWOABbRZsb{C7u~v_7pyRQj&!(<-n~K z&bu<4W-j1dOBq)*?HIT~N48h3)*7q|c$^0x(0N_2cih|@RxK+*HLzk6=W_iJou8a3 zaRFZ3fH9YdXU@2{O75X{NcUPw zN78UQ&Z<{&n_w5%O0oTssr+ewCmCJtPZ@HOZnd0Mi{A7qmv6m8?oz(dw}IvMaG&Qsh%NBbGx*bzIZjQ>T+zShYHdIPFxJDZ8FePZPIx zbvF4cmVTLeKtDXKF{iWyo~W;GYGsDgCMAdt;XBus4d6QJ56h~pGoyOyZd}nr$1Jzm zHw*cu9mP)Fapa{X?s-D3Lx;wJ$Q+;ErwZkC<&_U1*d{nZuX_Uti-6vzk;yP1*a-%d za9r0ElONOyqbS$(c}3YnE4;opYPAU?+z%oiMz`3`<)~gv>`{^6lCWS|h|+B!p8f2s z_~_az->Tuy$9JcK{FvG*-xw^C$?y=@LJfYWryq`IwDVbPo8|`OEDqf?+1mTRn1|M_ zSxzHLc=Njyp7<_+;lHa9WBC^#8vpV51Gi8E^_&$k%Z;+TNko)QB2X>2Qg2L~W3o+c zH(h=5ZA$cRd}Q5EKR5BJH|3Awih4{@${WO|q;Qj{O|Hk_MU^tL=DBOriW2{xzvH@? zanmbbtDMq#wps@gVq! z#zf~jaf|#B_H=YM`v;!ma-LD8f)5%!{qjN=Gm z8y%KWpS%{ng+A!Dy19NCc$DTbb0U(xu0=!%F8iUsAw|6*{Ch@pSfY%Xi()9Su6jqO zSc~|o;+lIH&^t+gqz&`hXh?h|m!WJ*O~%EzQC|6KgN=uH++^8)rU?BE{_D;&R+rjLb#EEp`ft zH_IJMDkv1mfNPqRUE-`fN)8fh7y++bD>8khy%aPDnI-$1=&2A|Dm!HPK1gP-Beptw zPlDyk@Jw0GTz@u+xyFZP5Kz8-l^WOg*NqW>&)HX z&y@pBXT^CXzk_0^w^UV61?L9oddY6TIGQrx5A=7fQqkvE*#vW9ej%q2&JQ111lPM} zdF&`VtP`pnmR$)Rv3)cLh2n0N4b6&#yJpow%yAKy9M`ADPB2}w;s`LwK8NV*wV=c` zo27!fz<}!(V90N~N%dX9@rAH!HM%`a^B-2l3vK~M$|;L0i{Ont6f?q!D#i|%*+vQy z=k-eEaZ(BXCF_B|d){_yo|2$T7E#b8TQulzOHVDuX5XyH{5v&MzY~wFeZiv#$orkg zay&0~l;wQma%S!*>A&d@>@EGga#Gj+fBa9>$_Q26cc{$R3Fk-lfO+p%3ul?@DM-lX zuu4m1jZWJ1LB&PRH)eVVkwzzC37{W}?h5fU8{&8v?)gk&7pXniPRI{rVI+L zQX@RWR@%2nnf>EN3zH$=yLfqDkOA@EmwKD)47qQX<6L<)b8QM8{N8Lnk0vn(slR=W zGdk)DOoD40RduNVm*L_OP^Ou3NWm-J#es00FOxBS?YA5P(eoSL?;=GQXIfY~_bY$9 zpC3g%#=d%JrdgSetmKu`+>EzI`J=CwDkNV9r)8Y>oRp&pXn551(2zl727{dv zoFiU79>Js6CR`4IG@cJ4dn4H5kMeKVYkG}s_A;DCv!84$sw&ep-Ta;^d2B$>hxRDckSR(;+_rYhVJ$E43^^tY=KNoyc0M3SY7rY;NW&dA$+yCQ4 z5Fm^@{`i#Hxaqmqah&chr}z^#2o)R^h2|`!%(BCTvlq|d zPew+J9ZU4wjw5Y%#<#be_B8B#IvAnrM0f)LU|j$6FTg7G)^|dROtIGp#by8SvL!JE zqbdh}_b3K8Z}huEz)wlJacUS_#7DZv&q!F!AM<j~ODY(6eYTjb z2Je`7+hFRe~*6}RI#8!U)i7R2#@@n2lbf3XtFWHtdw30_LEH=N;aSw_sEss-=TJy>H zeY`if?{kL7F_r{oPPpu7#bL5)v40+v^&R%y5Y$!JKQ>AfI2o}vBlTG4wM)5L_&Zw) z(b7+?+Q(F`EX%l-aDQG_G=1qkvyu}3Y*V*VN{R<6P8>gq!yJ)6pJ7lb?*`+np4b*+Uj^{kkx=4RT>95Gg=aC>~G@SPq(Gq)V-B|#s?eI_`I5;?-@s9T~+dy0jYNJdQX$rYp> zYiG24kRbw6>KY}TGF1^ji-W(lDXFf&zwU&;UzH2m4BX1&b%}zY|yQO8!4Gj_qx)6cPN~4_uF5{15L#2 zGOG(-4!r+HUk@0Vn-=fd*O9>&eB?w+Iu_KC1{qaCxIT=)r-|*VXo0L)cLftU{6fpM zgMV@{deGx@t`a1D(fP~EiPcU{^cAa zHMS+<_*myYv1z5U-kuY{w%c_*$WQLhEbUA`@>5nLbEy3Nr+D&+XJ54NnraQ<9F-m8nl2CbhoCuz3#o9*+-up76(>ezr;0 zl;#*NS4@|lvUNq*ArMv4tp+whT(?>61r(WRYz{6$5QK8^3LY&-jE@_bM7-)NIo-}1 zm=r-JYfkcuSLR)GUCL*eK{!Z^@HowY@F_@9dGh)Bm2s@(R?G*$dO$(DO?xCz+NkRC z>op-j_n1)#1pEICX%K7!=4OB@wyf+{1x#i7JU&S;Ks9B+V040<)B%E!ZOob zq+#MlP<_uN1hT;9=0tm2!lC#BVemh!3;w4dg8y%!=i@(|cW>w0bbnWV`lHIqrp!RS zYKVy4*ObOqX+m-uf;R_!n&<>WHeF;X?fI0N!lHtGu%pGd#kbWkt}l4UfSr^d2=G79 z%7{Rugc3T))}iMQX~9j5*yP!MKya_?{*QZ3dZkg3uC?I6x4Q6ebxVazW3jnQ%kv{2tGuZ3KUfgHy`d^tmVrP;U|g+NVDT(65C}dIe(P)4epBgvs_y_^d!F}+ zs!)AAm5In4Pd6BH`>$?AYMvYqxxW**hA_(o8nZUG5#E-=2_A)?);1ynX;uXqm zRXk$Ey++gpnS?n$n>~D>EAB^nYX%%AJjaY6M%MLiOq?Ms7W|uSdu-KV?iCXoVw=L%B$KT;B$IOnoFJo?Q0*2-#=dRV>*Vk6k)l`E%LIjEHM1uC zWa}xJA5;wdQT~qXMa^c+fU8}-D7fVIx|wL(=wQ*!2zF4AT3FI01(V+67YdM&+OI>` zo;_8+8MwKod-Z1VEw{FAvLo?eVZL0+R$yBxyPs75WtFkAa^E*yz1wST%{R4poLg%v zZ-`RdeX78tjcZn_AY{qyJJjSf&dXjl;f^X|c-|LjLs2^FbVK=TPQHbL4^IukwLDQH z*#DIS%-o_q@3mcjM49D+wh$W7G`3%@wfQc0py4T~?0NqnX&OvFkQuTHF5~z3!}BN8 zt@;k`>Ge^WMxgOuH>JpXvq^El&8VhTU}@4IHM{BNROhjC5z^!ellk1TwHWX zWouwkN>*(G&ZyKBP}Pi~qZxs}9|o*4aEOQZYWMV-xpKr!kAgq*P&QBrOgV`VKq;niG8 zXXN{VEpDw@c22|J;_`*mN@sMITz_8Q(rScTT1t!}F<%TobRK_~NUE z73y4D{7%CAm>*C=wq{{?e~5<` zsB5C8FMj86*w8CnZ6ualIF#wRhOB;;OZ*>&0~^^_B^+o^QEzNUFZm9y@79#tW_YjP*qzN0HO<;+erM9uU z{FO(2O1f%uRYYqJoI|~Z3)Z*--EF~xxRF@Mx=AOO#OK#ZO8iAGK zgTi;{0yRzC=TE+$fz`C?>btd*CTQw#Q1wKv(M>q?QrphPB8|~}Y5nm0lLYqU*HhP3 z!oAi#JBA#|MDREV(*W0==h{^}@7W&j_*MJeGO2F!fpX$YyfXP?k^Q|peO%LXP0Kk? zi5(dY*3c~{KlGDa)$`t5AbRJkzia20GK0^GsF%V24uufP6;~CIuckBO%KL)7 z?PDi4B&(K)&?aHUKi{V(d(e3Q^T+GgW8j3(mc4 z!}J#%4t6Cx4d({j3X4mf0apY3ciu-6W1?p{&d<@dS4nHToWYkhVlc)I(39gR14KHA9d|*@3xK^*3G)qa-6b%n)>QX8GMy;vKUG9JqCjonSmfrV8KB|NYDy&FG-}-MA|xrO z4bx*_Q+8El<=09BoO{!$NZ@{xixb)Prf}oYd8t#{^ZM?0eWweLnA!QTUYKFX}@fuF+cLv+@)%hEl+`kJ5fr*`zGkM`uNPpdE%2#>Iw=pVXOD1m@JMGR^pN`#B z;GHiGQ376i5gW6cQ*mQoA8gK?-z4-3mQqdj39<$@#|1pqsDOXjkw=W>!)Vnf;)t#d z7ief2DY=;l=gY^!l>$ofxl=+eb}eD*{RGCs_d%g0T|HfE!m(P`%oFll_lS8olZQIa z(9Uyh7=iH2UY2%@0l~@Jp32)%x>Ds?MC}hp_kd@?t1l-3_;4jLH|D}dCaCqV?k z=o)7g%Bl3M_z{z1V%`?j_c2jet4nq?oUS08PrL)h5+4`i%3Xp1o;yh~s7N^0D%t2C zQ~$6=q9EHzUkCk!S4_=oF0Eq2DID7}9T0T+Qk%F>4UD!gvFm1GY|ep3JkNdA%TlSV zbt-B(P@VQ+$AHWCU`A!7HFe;zbh5Cpz@eDF7Yfu}C+AG_RM;IQTcL;2vENSpL3-P0 z1i~oK{9uUzGt&%=^Mk>4XWgTeNZSDv##tO&U#qI}%vC$$@X2lkcYzN#EU94ddz4yO z_7+v7A*}>Kz4i(m7EIYr2w%UH`zNPkD`?u>7ne^!MD8!G7nMJl8EwbPe0fNg1RCtM zG>UQN=5ecV8hN&QM!oUjFMw=!(bi*YRlQxo8vme!gB=VJw2e*&hjf;3=oHpre~2nU zC+%8c(#^6PG1brFUY%sX{BD$W{9Eu_r*98m1FNA09US$Q3obKbd(ju}K5R}OEAQyr z8^$p2Ze}#kqo&a`uzE{)fHItD!8L~&xJ37ZqAt-@_R2$zg%^?j+E@j2yWs&mv zUs`&WJqH#HK{y(k;*5k|1ifY(g!;NRuvJ-9cdH-3rE(cKh(U(y0g16B58!a{cnD!5 zCc4DIgM~~VM|=-)3}g(=b8t_+uBfN0D`T_7ro)_E_RhVDpaDzxpyIc=xv;(-7#$O_ z0%f;)fP;w=XWyb@nT-9eNef#KC8?@%F?bY$Gjd2YaD7~f?ed!PLC=-72pNMS_|0R{ zf9b`8FG+ZSIE{$3aIk8q=Q9X6h<@Eh1T|0KLl?2%n`I!4Zf3b#u`D9jvo@tFLb-4{ z{dJU&h+(e0$T)?rzt+_>e11m=c^kea=eR^OMO0AR9%Li+ygT9S={nw~YNE(tW-LCO zzELMQ<>-l)gjVjQq=S!xYt<4Y2?X?wc3bf@p0Qq&-hHMlQ5)1BWN5yE@XS-Y(dMmAgtf77#{ zsG0Uyt;;Lll#h7@2k50BA;NJ38#N)*Ci0!b6ApU5g%hO%K!FM+scYcO>@kwmmlNCT zIv8LHZV+Elzaw3hWxS2re98bP{eo(L?X8fv=KMuudE??bUME{LBU0SUM7=3iHy^xXfh`c zurto7>N86XlCkCN6ek{V;p4Q?bcIrn=1Kw8QCHr=*+ZMPpT2_)e4}jp_q?l4`%g5r z*;VIHd6LkG)A+N*ydlhQrCa2Q)sj0*I)f1-5Iiwgd}=J;ld_?jq*gpl(pjfrB(Xgr zC?mdbfo%h!=JQfTebP}^4qE%|`(JuzqO^)`%zvWpI1+z&*V-X#V6Cp0_%lvqmm)fk z4yHFD8Q}4aq+{qR4GVNdNP1{tx8f#Xe;%Z0g6}rSTFFTBv}7XSL!yu|?N{CKduPuB zzlO&mDN#l+@XKBQG}6FvnN?rUB?&9+;dnRI=%0zmTMkp4d-(jL-_d9T6#7Cgg|(F6 z&8nS5`&90+bH^E z)tal*I@fpC=RXVzg$_hbblV5d&i{%it@!n&CH0KVT^FHF|AFk{bE$n;SJv?^vh@9u)%7r|y7pI5f0rxFcB)8+d zx67W|^IQ+6d|j^vD!;0SOOG-flNIMOt4<26C2;5pR11tcLMLko!sI|}TII530vQA% zjjuSsSBAA@8~3Yc!q&SuLKl1dR#tBbCQogb$8Y!0dxynSr`Lh~%_96T2&AY^o~s}l zY3spAH+Wj?EFyU@6v62@RkUYJb+u4-W8S-05cMew`#|Gw!z)FH=@V4YZ1yEaU(>@-wK(Q8z6nA&G;#S<0iNO5-vRw(YCKm)}sKyim3&-=XJ z_t(s1&YsU!uZDGFLRJF=QtI-6UudONzjq5uG4Z^2j5 z(aOV&+}qK?$z9M}gz`TK!B_boH5(=Qe;^)TL@0HYRLLcs-K@xYS=m|HDLSnMUain%gc+^i;LCS&4!IbKtOl?CBvwN%>Ep|NZ@&r-z;O{}su}{l8_s7RdIG zg^h!io$Y_QUtNX&(F&@#*;&0t{)hh)hwy)p{}0`N;|R0;6a4>{%)gcXNBdgpCp2NU z|83hRG(zKzH?N&Ub&=C^2LRsU{PTwcgwwA%L@fi`tJVj=H}+{`3VYz0z$>46HN#rCnp;j8BNYig@%T*va-s^$c&GV-`qXX(9mdWYmbbKSjNo- z1_lld4T*_~&CbrcxVS_|M-vhf`uO;GdV0<;F4WZ2sH&3FPtAgGN$o@LOoumY`Ikoy15Wr%PVHN970`41ZND|o^4EmU8ybf`of$QjZU48|_y-&zG1vb10! zhO5PX$JFp}lK~*HO$l(LoDZ+(m#ARwjVKVB22d$AH8thzo>?8|b2G|eBc1_NZ9iZm ziZ@cBgpxzvA&Mu#UIjTIqBwRNg^)5fqN|EsCP~oKS8|k1zYXBmN_VTFU;vdE2>w9qc-J$M41EUjsaHF6!Zz zLKVO3^7XG<%DiTzH%Z-&~ejtNQ`%f~rJGEPDhP(aSUBn-2g$%nWFsH!@ zmN!J+ZX~u>@4PtcF04Dn46A=Kd}rG7bQe14w0AtX!>HG+Drg`XJz2gn?DIsJ`AmIM z0CO|S^vSW-nMx+1H*bji?fx|E?#o}t5`$To3=3>I3|FnaQ2nSHP7ZzN<=ETSjQ-t6 zjVl|4a1!3H=f@z;_I?NNh+1T$9I)3pmq}vX(4-ynD7W+bq{MF#Nc1?hc@Yh0kM9b0 zwMPwF&lx|hxiR{7CF080_EuH-KG@2}NJdc`1QX^f6r7$M$JFF+b3dR3UVs9th2P}{ z@C}r<`{^+Lj+gDEfFKUnWZcNq`X64zV!z2WZ3$r>1slIJ;4 zw56wDCCJb1Dg#!~qFOSt82Ae+jK;ek4ZaW%upHkQ_filpahi{`(9U|Zh5En?< zCpe6kJUowTpYh|8PW@Xgx+=Rt&&-VsqCWD=$Ytfo>m>L2ZTOo=Wy7BV z#s*38tJ5~CEs~s9LSWY@n^`1!M%rEOx6CbnWLwT`-#kdG(B&XKF}PfqEk9Q3#q{tV zEAjA?q7OZE6bZ{^RDKPozQeNUpbdc@c#TJ7|rcXxTXST^=; zwTg^?^n_wO!NQ)AC|;QcPHAe^RKU_lQ=-_hRY(U&{AH0$v@$lTF+7(v;cK3ID%B1D z4T-zWe%dPA72|ReN%PZ}fDb%|=ot!4?`Adr9`qbkST=0uk9;g8Ex<^4(3=0sdh-z4 zL|Ap!g{Sa#nsyi`qrZEn!?Nd>_&%r1rxGyD;I;!wI3D3NQs`h3(yl4Bu_B~#OC;G_ zc+N_H<2Ej+6P7zpA?CmuF%?1+mbSO~tjPhWY1eO0Vh(RvJ)U#E2D1=%L8VR#hWMR& zJRe)#seiQ92U#*X{~SI4{qrV10s*9oc|1F0(zL8Rs5+NyBpfFp!>tCVnnZF= z=r^H`#0=*sv2&iYdv=b2sk1=Q6|ZpZs(vi&dy#|g=)?d-DGmkDTQL3S4e zTZ5Dx(GEXh$QqY@`?0%}P;$4VocHMTVyT1K^9`2Q1=US$GlkOlg)uO))H zMA7HXn(K*|SyY3G%)a$zGHEm7?eCEdNcDHp7gUa_j$?pn9onvoGvm zNSTD!d>h9UG1UC>HEMX62kU&r?6`esXZ^if^LT+p4Xq7-n)7eQ0;X}yG`b)MAAuXE zr?Zkp>cIk=Y527bJ3L$gyk(#4K{1XZHD~9I!SYAz`O0LgGghkUQqL1|jqYeC;sO#b z$!c5EP^DP;jQnuIAzw*{9>iycB}W>)pZg>>7X1AgHeEZJ>c9=ttRo+WNtD<~nXC7? zCxh62W~f4yeYm+OM-|*mrc|}*<`BZH(a%Ku^EF`_$nI>1V zI4kZ$iZ~SWU38>lot=S62@JU>P%j{2&d;Qk`>@6DPqq=8+q@L9#ocY!1WZ?NT4iD| z?d^#2`4$0IRqXJhg}-qPE2}ns5V4LGPnm<-lAeU{^K7{r3&gR;2?Kp3GXtiH-tc|s zSVtB(rv&Hm0BipQ&d-uNQU?m8-H{ye`g24Vmg!l}sVa_enFKj;PLihUAtFs%#c^oM z6yj!oqx(@{)Dx29Q>XcVGB*DtWV zBbBW&!l7<1+EaSjmg_K_X58lwc9efq4cW*+Dt&EZMI1>X`){e++pQld&ZuQ>8p}GW&M4If!F579ueE8S9 zBfkF>p@%f5{$+jWkTw+aWnx%>{loiaMebf{C=$#vSOUw`28-bbkrs}Xu_E2?AISHr zztLUXyo-nmTG~R<^kb;TxZ%HFmLse%D`=F%?iKqJ-e~!f8dw4wp0oj=<~V8UgJ3=; zj%^~Rhq2%xPfQG-(fL3DaC13*F9KM)_&qSdH>~;rpZ&HO+ulnysw|35XZ~R2>go!r z=HcrBeA*!emi^2#y4+`u`SZ5=#M}=~NWgDzxa)>xdO;5{odkt0zl}3#UF+xd94ejA z0TT3`2(l)kqdHx#I%!Kp1IT02JsLNi=(q?x> z#&KJI!;8(0>HQpNw7dRP$*lPDbkSuWNxyg-E_WOSf9yMXyuIBr`E%hF-OUGn+A!m* zKi0&6m15OPJg_TdZ@p{Rb3 zcw%Zi86|FFJT5{-!99Wl%9EuVVH#P_JMl0oX#yY2Os&&Db~Qv3wJzDqC;Iwl(yq6~ zxn%9xk)mu7e{moeJjs@Q=n2o)&d{n3dtimGa^sCDBa>4o9_4yC4{=WIS|gm2I#vZE zrez8r=tKy|_wGu)8~bC!%NyY7Nomp8D+~sw&dN8SAcf~goHK3`^5{^7gD~ocuV4iqB(3PoGAHr25V(vrxqDAVCK^4_ zLMsrZ7$IyO5;F5oij77rDMRc9lS8TfC+~_lXOi@YiBI!2W~gCl_rb1U4*@CIeZAKo zoT6NkI-FB0c{;K_%#QMRMS6DOBr9M5us878J0XIP=m8mII)#ZWIYjP`1&SvNg$_nV zB&}wjPVC{s!FGid`<)6mcZGD<3Z}_&9}!`=TFT&T#egx<%Q9)Id_PSf31RT$8R9X3 zdmoIuudlUWwD^F|H3Ck+JW9t%E|Ml`xAr*nC1~N3nb3 zzEBWf0Y`sHYFbtvVVKA(*8L)b42`KPYqeb_6TUt&?=5h6jwybk+p!CV56u3&^z#A` zk=JA(l1pm*O}S3FluxdM3l&ss2N2Q(7SFRO;1eOzqb%eceU~gf6w_Z@4b%!BrU-Uv z8WJ{G(G7>P_Akx@z6OU!4F6)M?I>k})S~uj(jfxwNVDa+Dnp#N^&?@5_N)awsfc_X0iu5Ml4yAEL?!kB=9 zVR3&0!6Au3Nw6OA`{vD$$?<56pwDHJH}0k74ytYS(ifkSRjlT-zBhkawlfe{#yiLJ z)S53I*h~V_2td1Ok1KOz4b&l?dSB;U6Sb3EjqC?S?g2s(Vlhr}NK{gT3DJteLp9y| zJ3S_um^YCNJSIFh+g698Ad|i~t%@Q74@xo?%caE!hbt?y)V_^CuZz8bXDNwfx|uZ2z;$G5Gx zRN#h|nULLOh0u}nB|ldaspH=Ue6uh{$5Q6R*uwPd)*QX(`jUC98(v#0m3?uF%y@R7 zYTb-A2j@Tz+T@3b=~aP&D5bP$Rd=QbpKKd^dmURf<+Z^IZaG^}4mSuFP3jF*I{Kom zQJ7V4wp+sP7~xC$1 zec!7O$Orv>ZsQU*Z;X~3{c>m6bfK1f$ZvLBG1hLFt8nI@Qg(nEMyNuxE8VS&q`bky zBI7MP>_v?V`o|LbW<@als}E4oeSK|?{>$($9npmRM&U-BpDgT_p-M{v3ivD9DQ~K5 zkZaT9ZBVnKOVpYyCk8yn8~WH4?An5%XJ-cLXGE~i>GrHTu3GFl4~ro-!Bz;=yj{b4 ze$RYh=pkUWt$c0Qq<78u^EP|oC;EjeYr)_t7`h}V6mTkMiBrLkj3@o8F$ zU?ZphD|RQd?X5Zr9F?pBmOM?k)h2_+kXH<>!Gss*bP(1OWICM@`}DNq)=;)efB#Kk z7|&;@*Qkay0$TQPRTK9s4*iTruFX>e`!0aT?EYNaF7(KaTT^2o|LerIJS%rKI_r+% zR$i5qjk!ppcM*C<#~`*TfBDcwGHI z$QV?dOfIJNtkAi_lvI5`lP>Lb!2FJ3Me*#zc%33zy(U!D#Mc)BE= zA0!3A+BsZvc{B>Ls8h{%;~wS@%sC0tD>G9bjC(UC9F`XpK$4ufj%FKw#Ea!@_J&eY zA@3Gf^02EE$FNcpA9$-$^^#*8eCd9SERQPiY|D{Q6SOfkHq3$FD=T8zbi83%83Rtj z-fEBVnNVN6DJUQ86)ySBvq6F@?U@z*{`CVV{^W#N-$V;Mea)9Sb zA-W1|&+;$lXeMJ81x(?aX4c2?z_(tNF2#d4=_!m^AYwZB@81DP!vgjbtOH^xTMy#h z-DlAPu`w>+~)dK>&lCQ!` zcn3avzPRJU{_+cm(J;6>Wzm0sfDbGi6_R()>{29D$#lG*x2CiZJ69g5T~75<=dbR1 zDW*3lxcV8G^u?vk4g%Vh)+lw9{b&cF0Hs+n29_6YVok4*SS*d-1KT$+gJE-S10*h1 z69WbeD_%Ozw^(iJh$4(azdxTfAc?fgUpu$U^Ll$U4k$H}6`t(3V*DWwEf6|hl)2XN zHG^P$2$az1zF#X$$iIKC^if2FKoNSO$i$y!QX847ZRGEkCDec-IZ*lgRkC%HUKpUR z_vK8fZ7l<>5dq+_0&J{r2z2CqP3XC_xBC#Ar8eyKt%cWon?}c%V%AU2V*1@DD7|fg zw>Yox-_ge-y}U|%p}@z48QNn(%Qt{_BB)U&k@iD`422M&uh^CCRTuoq0CHOH6GTQCBVen4g~zGu7D-P!>YGr1{p2i{6nzVCV>oyJM0Lw_~D@)W~pbs3ED1wMf%_x6BUcc#%l(r)`mUcEyWMiTV2 z?SUK`IzNeU2R5^lp?IH+|8XNXYD^WT8M<#uE&63x)v)`+Hz6~ID3++o2aMSWnydh` zhyjy~!sc4B&DA-{Sn9W{vo^DLI@`=M-b;kRoT>#EX_{9s8~vIa=O<43g1_kGnTo!9 z6GB)1>V4-!zbKdao%)~TObl}*i*QD2!z^zv2Qv4Jer95oDJGFv;zMc=@^vOBI;S7Q zCdolWH%H2%`SHTXXfjiu@_Db0$w9-+LI8;8Ewd)sR41LX4Ovd@>OX|RFu~oHEex@D?suzA8N!hf=ZlGnw?f;2&%v+KQcvid*8J$bt30GDI-4YAqH_1R zt_HWl0OaEsD7`YJf(Eo4p@?FA`|*c9Fiiib^)NN(ipJ(h%ZI+U{9>z?XuMV+O)B$E zVh)qhiwc>8*SE%bn6l{n9mT$8>Z`21;aUat=s*{e93I3<4%tFi1#uhvf=nNwB*>8oMX ziFk=sUqq;d7zqqzHH75~|6qw$NZ1ubE#uu4k%`Ub3FmKIjd|CHeGK|r;Zq_Re_b>s zw#7j+M$0%anV76^h40_qmLI*AcYZ*$I2EKa$r=FJ`R#SOf~H&m*+mwj6mkQTtdFE% z3hxPV^W?`aqpb0T!M< zSgXhSb;CsyLg4bzFZ%t%;Ad-X=2(VnuR+*h0(dJLGCDdc$DC-p6ut;T4N)6QhVxF?4&*-3gR>E&Lax&90Z3xC4ybqinNk}uO zc8>L(ENA-k#K0f#sfX($k(RaTE?S=}%pj8Nt3KhrYc89Ljf25+&GR8nf%}6?d8{R$ z_w&`$@-$gZcgfNQk|~6a2kY5$YR_2~{3&Ha+CYQ+@A}9pSHfo`{E)4;l&v8by>A3o z%HSDRVmQm)ubrV_Pa;S%RoPNFHV8rS(xH>wovG-LhJX3i8RJ3^upP>Tm|DkALJhv z8|{Aavsb2*HDc7|cDhI53T9yH_-6=n)A7pcWcbMgnZ{H7-k1wiMF*J6C!{e)lL&Mg zQhgjjBFc^?1}z8_yq|})BWzVZEiNkL0+dlT|p~?7=C}Z2t`Sv`&aQ06U1^U$>8w=iw+O z#CKy-mKbc7WaA3O2qz~u=4G}rM17F{;qvY@!D?@@DoP>`U9;Oaz}0&7qBT#T!O*BR zpjLpxNyyJPR?13OkM!eq*WY{z~_ zUT)yex#h8JK1zQ|V9PcQM?qC&i*|8jNBt9)kj+Idf1|YPP^Pk;l!RWeI&TAR4<&mr zkx&SF2%?agq$+VgytZPT93D$Uw%poIP|#k+fya$scaCy%8Cvr%|t9S>><2 zb@WAHEGT~UmtAR_b0@Dte=Hz7)E5CTub=v7D6TPHy>zX@YH)O9IdRptW}tO_WWVCp z{E+Wt(5{I{_q;RzUB$ees_0U2;=I{l+YE-Pr?A7D5o#iu{a|0E^5&T8`{ON9)r*a0 z_x!edK5OD*p2us3@WDkpM|B7MtvoxP#+Q}2nU_ANu(rDab*b@1G`wc)sR1RFYe)Pf z*G_MAowvZ!i$O-_uRHf20&!z7_Hw1gzgqE|)a-%|3_n?d^CO718JV9~!V^KEf@918 zP`^12-kR-=ifP%-1nESiT+8TZy|%DG4kJ>sC85D)+JtIT!38ai2fFX+W*=NRneVSe zx|gVXla*R(v0Lf2s@#e@t}lDJjp#f9B=<99Tx{X{QyblfQII6Z;JV zbOJ}Mi)3Guiql{8<=st1Es;_%Y)t5#ejPE@0EL^*Pihj$dX7>NZKvUdcZYO;Lk$sA zm--t{=HYPH%d?pcZP7v7ANWAr@=VGyroU&rnbI}maCxk52!XzR)$><34+6#R$>3eU0p=T&0+vDyou&|zsG%oeQdWa zPfGm@qoaOwr&hK#ne;oo)8@(o9<1O*RX*PEqmC?s_Pmb3F|^OA#uPthaNr(w!r+sz zGapv2Em{3rGPnB1T)Glgveqidd|-QkF`Hh)q)lBeq&0YZZ}M6kqP1||>EmO3Dog(& z%!JDuBwF(cf8vb9SbD<;lr{O<_e4{=$c2SXW>Uvt^v+htAGy$wWobY>>4x|x9VJH_ zDSb=qEJ{eLtjPRfCy*;1TTu1JT!P9zXU!n*&_&`a>7wE=Njs1HmgZXFS#a&<<5yib zf81WP8x6B423?r-2KzF7XIAmm2BCJSkqHSP2#-wVAXA3}&<^j51izmYAnlJk58e+99vv0hO! zom`bH83-D-@FBlwS3|0*7`nQ(a5jlrWw)@@H9h3WP5n`~WIZ+>Dm=M2eg%sg%-8J- z)@9n~$zz9os-S8BpG-@VyCSRIQ2yFdpl>~rRm(aP{%sg==-}___{ktF8R-YXjSYo@ zsP%!NKRm2JOP%780{r70+S@Q`=3$`m#pyLAM3XB0TN5_-0BF&`*4|hn{!=;dMu|%- z^`hQWtQ)6W;<#Mdk&9*AWV0loee`RwlSZn@^unY#4upB~(vH4+wxwK^>R!b0#@%dr zI{HiBNdc{IS|4jWm-o~6v9A*0Y~jsea+7ODe?s-N-z(}sV@>KCDt-;c@Dcf?3)`as z8_(6Yz155bE%Q`^S3T$vh(Js&#kT9|LRBeXaNefXNoFY2TS^N0@&5CCBY9FwLB5Xd zGy;&teuskK63lJ!R1e%LCNV&TMH13;kQgx;Nv^*>c%U0{g;tq-e-f5${JSfl$_-4# znesILwLxG-vj3^rcfRMXrlTE7|L0n-FT2fN@Svwj*v|X_9OP%UZ zC*((0@bn+i0T&JfcwpkDE1rxn37xjVv&Oui8ugU5zvBh9c2I;_d*G~vrLAAl|Vm$DX2>3z=zB9M62-qbaN3(N5$HI<8Q{boRo1pEz z&{)`zJBa)WkDJmBqB0w?3<HBN1viocA)?Pul}@KyhhK^%{QH6we~AmAW#lDYC?9u=ZNLCK( zueh4oO-Jx*)yH1F{Wc;p^T(rD1PSDx+?IQe0W{PXG>(~c{caki{A17Yg+Og)?Jexr zeFgiAdp8!?ap`EXvhQ3e(HQ@m*-hn=ZSrH6@u_KMZUlO(Y$?9V?6Iki>avU3g>Mx+K0b3HS1~996DZQlh+zr4J}cC7QoJ=nQhyo(+XTmvCD27&5N^Kv{&lCC?WMl9%S8s zdM?j{^rqhn=BI*o_SselLeeIilVn-`p1}ZWG7antwwnd{E}?(0CrB$>sYHtDY%?@l2*w;h=uj0e_g= zSZtuu?PQmCpKi+6#~!%;!>?OKo9TP3Vd8J~DApFLPEFeRqVEK%lf;n0Ig~wo?w;r` z*sYF-xa0O#2xF`b4QJuiW<=TW_RGSDboK7zs4!vYG6LT)hurE)$q+%63wCbC@mrFg zPTen2y1jY%jxUB&%hM0z5O;o)X*BpWco-U+%Eb=M?gf=Bh!aNI-yZ}P%KbLv$rEP- z>$CU(EsxdY5BZ!nSGgXcPLEHYR);|zJdae3EklK;WA2`q27Npo2CmG(bJ>)HWCKnS z_@BLxKl`MT0quqQgOBPip3{{Lz#_lJ#D8u>A3beIAFk?p0|t*@PVVh>XBX~zYXWOE zdI3UFg;Q4o(CyWWe#nlW5u3?ueU--}t1}*^k1T`^*>&1!u)wRqwzsuh(&=30AiT}h z-pq^hTw_pRJKi^*Pb6b+n4!YCh})S(%ZCenxx(gVw;rKO)wh;A1m|$(jC0PPWJ7s; z#G)|ULp{`Cm*%26`$*ogbjOEwgM)1bY1W_bm~$GnIh%}>IJ~Fg$a{tU(y*y^BKEf_ zg=uo_hM`9pp`UCgh$BBmP0A(r26)v$J(S=66&(Qc?&M&tDvQF6t(u_p@^$vF9 ztNQu^8gMra3>;XKF}=}2yC)wIAPc$U>p+QketAdbHn48 zuWydc=xS5Q>}`TAD%K2Yz zs#u8Am&DCau#uiIP@@3b+IS5$RNdVLAMP9L?!Tz&UQkS^O-%kIU8<)A@OXVm%sQ%r z`>RyHix3RoT*2w-^w5gAv@7%BbtzjnIR&|@HyE*I6tHj(64U;kX)0SS@S}*$mSflP zotoLZ?jkCaT9RD383d3o-3%*06%dr{jhtaw2R2V*fSGn|eE8B=u> z<-11^Q+Ia6nJ_C!7d-r8W+jy=xlyn9f>0cd=0=d3sLk0%OjJI4_51Y8h4KawKDZkY ze$F$sG+lyiEp;HPT=GJIGuBpRCB1!ncBJJQa;!lAGb?^Lgv(ZnNOp{sFHsRx`cA{? z6E)|T8OPfC@iC>GXvV!o(axqmuQqRWRW9uI*6EX>wgOdG_6}=v8Ihab9I(GE#?2UE zo4P&a)(2krulakrbcg;l6XB<`=RrqVBZ4f~Gc%42_}^~jb@w12OH*8Ha7;+Uf-ivs z;PB5%4w*Lzt=?{VvzwYtD2=Kv!%N3a!|`E}x#ID4)+ANJ4o>}9N~~#~w45>u?9zpu z8#1F|(21{7K)gKP6cPRjbaLJiZ8k%~;(%h(dDp@0U^i$XoKzOzi*a=&>lCWGhP~f z%HRXHrV5%hIDRI)etop}9Abq&;nbI52o{fCZ;yS|n#$g@)Ia3mQ*^sTN!3$<14zL% zGKfNN}M39O&2voIwdvNGvUd#HA6D7ENMT`FaM#ue2^vv(YfFScF zivQ}@tiC=&2ZC@4qd=dsBws0$QGm{cFYFuBG*UgLnEypBMvOd$?;ms`hLZeDkgE5y z`b{JCU)wB;L;>|bUWh{PUu|tD{{a3&L;Ww9>KzOP_$o)6%GSTia{s0)`|9-XNq+i! z3AkWvvAnZw_1g{+n+WoM*h&xwoHn04*}|06_+Bfhr|NzwAW@k7EN1qF3fX{~?$0i6 z=(q3r5^aAEOo#WSzn!YS^TQN6-yZ(E-TSk)d(xnbnLqnX!=k4i6c@ zh~5l=hrXX4x9{^bpY)JaS;TuXG9anD{HgN9|F~nB(bZ274Jv}#%KpfUg<%j06Q7Qa zYWwl0k@Q58Y(n<6L*(PSTW@jV0;L5-M5KMaa+J%Ee9kR=&xb{XX7xCt^ghL2i?{0v zuojAhL&bSP8ESQ{G5&E;yGSDLz&X>@t|~ zamCF=4vo+h{N;*J*@lGf3$*`&H&Vk?*K$hvhXw`E!d$N*6Nrz(eS6LmEr)Mb6+0-5 z=Ks;QSu=(WZt)mvt)7eCK9Um*Ri8)&s>{0JLt)Uex; zfpY7xAjFd9mSek0gW@?+HI6n@|7C%keSB}e3uR%M>Obq^GcpVpel3!X3yzHd-0*q} zh(eLHa|g}WTHhjaNvdHGqZlY_Wn`@0PUK4@8rNm{8KqNsw!~cNL+pHL8Ka_f=JS1q z@;|F4ybtsB#iaZGIfMXcP2jf+Cl_wT@^a{}!e=t-SGPzj}E5JwFRV z#}$=_K~?(k+NW0iZvBn}ze)RJ7&Bj%?H*5JFQEMVa}{Fpw798-C#Z#J)5Rp!-7lYy z08e`_q~^bELWe2FlheQ<(ZT=ujBd<`Uf=fyA*(ehG5$6f-m(wCP6qHdfN{}FiKB?I z!=YUJOmn{u-Dh$bE`XjVqwnH2^;3?zh2CLr?JG%d43gm;4@~!$ms%ymsi9e(||0X2B zoJe1^MA%|Mg)_ChxDDB)*&4pqbXz-;{AYi-tTnxv>*?R(l3k!i4*J!Zypd@$e}&wn z+p|2kk3bgRE9xgr^P>2=mzDhbsSH6 zWLlO4F070l<|Qt!`j7g#&YW)SPYp&5%LdfOwu0#DdSzk>GQB_Gin{c8r{QXfn{Eci zib_j4cD@JVw}QNqg&@$j-gl54(=Z5(5&R?n?rpcelrVbqoK{O+e)Pz_&mu?F5B%|v zyzYinKHO*mtyU^a_+{)MA|P zi|QkYnPpiA3cbDLQxRf630nHsaj|e7lYLv7>^)z68V^IfIrd61D%g9$V0{sof=xB` zM#J3b{iY}rP0`<+TaXC2aXU21d^%I#X15i>8ijlKj(maTm;Pp&OD~BFZP!om&rfB9 zaZXZ>-^a)oahZ?A3~v(DLJpJ{4lD>>KaBm3S`ycFm&Q!~{a{V&Kmvh+x8CG{YbaPqqWuWr*p6gvJ;d#MOS+z@ zGR3XZ+_ITk$3|o%db2oLh~e9N>vcl1@gfBz9tEC_KMaV*KQc_+)wu-PL&cws#!gnk z$w9&cSSXRM zH1P3Ziijl^Td!_-2Q~5Ug_aTI`5RnCYzJ?YdgmmuJtA z^s2Po@&0MZBKxi~BsdSs^@k|gDBjW^?@`l&mN|X>F@-7n=_LD6AoI2mMmPE&nt60+Ys%bJM8sj|^u+{{B@)6~ngNrQ{PfA9}kX*MyH&u1`2sbdO z9il)-uh+ZR@+#CkZjTO5$5pLb0O#tgo|_! z=doSVgQyX~jkWd9oVV4AD?4H^V|;gb(R26^{k(1~I+swe0?wc0R%Qj%n$XX>@0!s`r4xw{Xgk+ot2P5gClW@cOA!f#`App1nc_K{QdFzYVdSfkK#j%3E zTSf8d+=1Jn9ul6_WUZ zzlzscrC1{)*K@a8#&7)e`5c1;f@N261lZ^HtNAqnYi}b#%;iz)r>ifxd#IXBm2xF?CenXBUtQAEJ8(pQSE+~-BA&4Rzjq$(~+P>w_BGP8)cS@lnmdaB&610<~wIo;u zW&H6$XTdDj{F5A`1lmMaEy4Upj?WR2*K2 znXaP3=ttD#wxWdq{W*0RRoN6?k4EztH0#ucl~kmBThl39j8R(W^jWxg49C5&EPTDL zbXnwPq^mwa#_Zo!WmRTipadNf0;iFUf`c;J47dh67v-~w5xFy<)52FCYYQVxqOzXI z2JFxviqPR43Re#GUgk!eah*Jns2yR&v$T=?5f1@jtw3{Nnqr<%7n9C7P7hfp@|iJ( z#M?R-(p&fpoxCWQdH69amU$G;E;&uOet-`q7*AE^_^2e5;_@WxwfL#$`W2z+&KZ+_ zUSFaj2r>IU;YyaR*R5SO4+?`Pxr#}rsdwDhqP?3RbkM;wnrN?K(PMoHZ_527R=777 z0uiJHZ;z~x*2b(<*7|rgzOL{Maq~sTYk#_^B^k^P$Xt@-3FRB#fE_Jylt#bES~%~d zN>>jfv#TQWbmsau^584nk)M~HBpQ+N_G6WCn!NTG*P9|g>N1^22kwC!`<|#Pk`Yg_ z-Aym@TabQ!&IOxVTON#|h}a@LO*Nz{q~evz8I$I=4^pdgkDJsrb28HT-0ptlAQVJA zC3&)U5%Vq(8b@dz`dZO>w%E__$S|xzD8T+@l|joJ5|DMfD&q|DEb(W~2{M@Q{=xrF_NQD|hP%r~DnXENo+7A?))s1Kxo zsimidch;hHwD|9FLMp#KahWgGkW|b3V#49Po(AZ`+H!xYf1DoE8EkrfeZo=NfDsD| zB>$@++HhL9@b&bTCyXBpteqGwC@+rjPhQFT6*abAkMLlrnu`711i#GT2+m#^A z03NOFR&VJ+Pmgtvd;FBc$pg+s$Y|Pn!%!>2CS=bXC56@ZeLbj2PL|5p=+vzG!njV*uiR0_Pwg; zOXwV*Yte{cqDQ29Ux_M)bEx6=qq14uQ{W-J6lf6(TEQ0+frn|%FT$^-uWj86%}fRk zi9Gt>ePG#m=19n+h6S=Im~0mDh8!D`_oX0IZyne0EJH26Q_zorJ%_y2+XYhC>6-u91l^QJE$^;Oc` zGA&!!p^t%ch73MM(cX5_WM?jaS!h9ZowB1EbavN#e>@d0fGg4Oa9;Y?sxCI@64Xvo}TKe z>gwuW?dhrMkx^KxKp3$x_VDyPydX}iO|)ZdjRG`sB%m&GQLrZPyU^rl88rw9WZLmG zHYPubuW)|=%#PbvQ_+kwefr2xyKmSP6}2^H;|{fkLce>W027jjVUvB{i@-~%JbHR=BHV#g>mITu3PSKABB#_PY? zEpQ(A)BV1_I(Vie+kqTnbu^YS;g=VX=n^b|4!T-2Wzka9=;I0DY7`X|vC#C*5RR6B z+pyu$LqnV}vCYgC-{*Y3C(Ktt70Be~X_W4F=s*Ldrt&C8>)e?pzmA>4(r%%>P)%X1 zX-_pF_`0A1*=mKk_Ta`fT-4H``-h(RBU91}Yb`v85TMO!h|`J&cYD(wMZ^OUHdhk7 zBE-2(d1q?Po2I~)Xgkd8;a6|BeYivt#yo*3it3s2rc;Gjz|0x6|Q1BTCJ z!y%SZcJ?{;VUuz1rcU}V3*BRKPm4Uvc0LAlghG?|oWIlJND8!clUnt4K6@uTSz)b& z)U9%Pz?(o9$5^UA-r3A1$q@m}oUozp#q5U*2&oyax$nfn`9mfoB=jO*m@!4+>B|x# z0}n>Aw*lwY?a+0HFE@TN{MG`@V>NZ9jPv}?{WfxG69XkQBzJCHraCtV2cxSqEd7lm zK7^c|Dye@52t^Kv`Dz*mw4kV}9!yl^eKG$9&Zd>y85Uz3g z0W=Rgw3+sOTH<&+d!)X@fs)0=8qcmOdPz-|pzb~LoxFv|kzxq=XJ* zl{LhxBWdWbV!1(bkik(=a}(*$Zg8uK2ZFWtpRy^npy~VPXa5T;Mz^h0@G4z-Jr87Y zk%QJd)Y>k@1PSrZ=P59F6)xv^=JX$ zDq3pXWYk4;N6`9^|CwO7tw_O0?&)B(rBvsd;PLWzG$DE%6xwoITO|G-MyEr$eiN-1 z8nPM{s3J@1?R1Oi45Y+@lVpYXy5Wx9!)B-9mxB{ZB|I)Z_q{oIp!-i>SyC`@yNm5r z&esGspqpS|oT5R5Z4cRj57fZow*tq?3O=?*c(yLBZB4#(ZL6?a+0So@kEu&!>U2bt z|H#5DE>cWm94gJuVThVMD6{}wU7WldzWh2deT5()DTPgqw*@zY0d=h%7Obhkv)L?| zYtJd8q*nFsU|!KmWob4&C~pas6^8XEnK_QP>V*4gY~@cmN8D(p3rQ%DtvkYl^6{cQ zuwDU6rYFvP8Cf6-_#p9$djC7r{SVapA4P>P{|6TS?=UGO_aB^mZ9yWV@+##Yd=Aox zzg96TJ&E+51y#P~$Gx?+#gg~RH;Ua=iGjM!ccXX93(9q#BGRMbp>(fkJKx_k>9YR5 z>gkSG2g_lsVGYH~W%BIp)XQ&foPA0+yjRBYZv+rGzYZV|rKqdHCj4ZoiRe~jxbol& zZq$N^P?R=do@a67W0BP9zI$fFQ|icJ8TKIA)))}3ekLBVnWh^vvL@rI|HWuDzpQO= zICc3Fod=B-iKXYtZvGYrejt1B#1#yXk@?Ru@JsVd&Nm~sR07X70|Oy6lpXdf`k9a3 z>{z)fN}YHn;5#C{A3~oyOxDfTotpku&?N(lN#|!@e4>a$eIV+$KOC=wUkad0{r$=m zY+QL5a1oduny*Uf7i&Cxh!UAcF|Mh#Not7ISTwKa$ya%bnW_FJYK0B=z z+FTxG_q_-96`SXgy#ZGOJOraAA~n`26&e%3>Ii@_34_2`#I@815$s~iB0NNMqE0Gz-ucVK3? zs#yD%VKz+&=|T`OAAxsJ=Lvy2e1`$Qf_s59!Ci6{TMKo(@l!j)g>+yuwnh9d9H|ib z+aP8g{5eYeaNIg2^@>RM6hwJ*)$nM=dA9l}lZrWKRa&Iq+k#<@kNL#oHef$Z1y}sK zJyU=z;@VKj;~$~T)6Qs3R&kY$4ra~;usDrt2f)?;q|YFeBQQ*sM~efqoAvrI?tvWA z<8-)Svs)zk91W{(U2WV-yIG|%(%&9}$C0M)vCh9|S5LH)8|u^t3lsoW#bN-h==su- zICjNumMhUdfpUULu6x)n*fD#_jnj)6Jlo*VKtuO#gug~m#(Hn!&ot3NQz5y;s6D`e z4xUd@+;e_vx@i5b>y8!(kPef>{loyu6}0{+^tttZ#u`mrUe^?hO0|!sr|b#N{eQ8I z7f2cX=Ma!Sksd!!Bg{HzyBMbi$1(Dp5VB%S0bDExD|J4hJ^gJgTW_@nAX%7hl1#Mj zeq7Lh*GV2kBH_%dgIQ*R(Wzr0Fo=pSxmZU7%+~o$3qb*-v!(TtWCAVv#$z#avg^Zj zzVJJtEnq`dKFLN*KA}RiZ4tT5)Z@#)Wcb1kuS`LP0$gyaOl~8AV{S-k42ecoj-~wxJK|#7D%g9*5W`#*RT4mBf+q(?ZX`X04fjp}>ZKA) z<{2Ow_o75&LjZW-in(PuFf3Z9zqy&!lyd)>D;J1V)$1BYoxc2ybX%fWV%poX9*NIf z9$isJDJ`xa?4{?re~V+lad+i;D14H!mJY2ysR4Q;^sxjKp?oIG{Dl_!_WR;=2@?<; zJY^WzZP8ymPI(F!Lyt61k5mcUUkW%CMgyt;I#@ZqDHI>`lO-(*GUMJKQ9EP{>@PQL zrt&G3jODsB=BoW$pR18aqVxR=Yd~Hs`pW6n-=;q$A7tId`A_yVS+pF?!OGqM<~R2u zlqPrs8^x@Zd9vUIy-3TYbRZ>F`A(lB-FmT9smKFdnaOP?vpsLWws^I^DDx-dFR&bq zEu}?dA?Dwa1QX3Rqj;3$hG_>#F-Fw(if~S?(cD_VJSmI^f?sg0hLD`XNYOi(e#Nh4 z@nU9BkbHB)!m79k+Q(>?9b8m!gFf(=56`4 zGp`vQ*xj9UO2|eOj|?~{RlwM?lrG31ViJZX?{G^@sm~n~MQ_Ynk=hP)GHI-58Zf3C zbN3dM+mtN_rY`dCS}Y(hx4snv!?MMs$f5!5OB4gmlc}XLeZ?ifX~-~aDdpkSgD@>cj*_@%*mOgDD_w8 z_xz`GVJU%&E0JijowgXWcUW5m?9q?hGQu9gu?zz*0B_L*G9Wa~s}$#y9?7lcJ8GAl z%5S;8+6v?0D;S2ab#O39L}4+;v1a&H#!ERSiV6jyd~eM_+)D;G&fr|Xj@`1wIfT1T z+$W_$O6KkmgS^Mc`<`;#x$1VZAsWVZ4+0xQgqQ37ADL$E@NX#WYGFM zL_*=PuLOVKaN_gUW33v?KaPYdr9AxY{ z(wEuX^>wdptxd37u70)OTOXABAxy$1xZwKFJQbyUqYFD z*cIYJZzcfIpK?l>79F%%RLZyW3#cFgP)n*K3%!&+@HZOi{+&%*t}nbfDu#!9B(Jo+ zl{Iyb?m|l)WP!34j7hc8$E)JLN?_SF4-6lqGnR)?jP!(VsaJkIpa||ASWE zzDtmT*WJ3FhR9Dg455J9e{xj9a=p8d;gKp8PLi~Sz;(nh<&W253och@D;M!>F_fTL z%(5Ba9B}Taye}?9qsR(#w^dSo8|I4_hEs`Nz29hv>rgKrl9|;P*c?}X=Qn)e8jK(5 z`MMd8{_HCRI{W)h@bqt*H*lYFLHv``_RB5Jb13m^jdY(LdZGVw%FiIMbkj{sVWJW92eP4Exh}?x8}asibu19c=^;CQ z7+{}{P7^(Gau>>q3lqvP0nDGED?`Z@NsUQLC360wP$Q=r?n)Ob%cU-u|Kb#_nKS{=ei6kK(duX~PyyM_>BT&O-= zI$70V+8b4~X8k$Epz$3Mj{zc6U#+a!_?gGxMS0i7h_r_tI&4q^BD>G*FLs`XMs@j)%Jk9$PnI;(Ou`dS-(w*YJ->19xnYr7yk<(fX!H}AH!!vd|>|8}@gs8qVQP#Dg)mbL@ZwH~zf)Kx{$1>Odx1Bo~>i@3m>BN#<^g5s7wXgU%8KrH4DaJS>bC2RFyp{8Z=Aeh!CVhp{5xq?u3 zpI!d3_P5KVmtKXAw886AJq|q0)lT2LE%+3Ntrqrfrw8XC8H{>3|6#Ff^zk|AfEnsOSYn&OVeavn!CR?5TRm^wZGC^jej@_7QoPqO;L3dJN2-7Oqv0D z2*QT(EjUHdwkyPdGM9mu8Un>E91rhoszh^f7#To%sokulOdu=V9fb6-k%<bC=RU3_(ha+>-1_cq_*nGE=OZAu3%41s~D2y-H z`iBW6E~^vvVg0v&uyuTS!K!}nhf6qs76it#8qF+Rv@b8{SqURfcq~vT`|3zNe8R$X zlF3*wcpR2zIjOt+?kf)1Fp%}+|iRf*W(mF|E7UGO~x1-u7>6> zy|s!jG5{I$MjY^ijGe4QehfJ(mDsCl&Ps4Hi{qWf2iTwd@$%roqvnCps$w}5n5&@hNRqZTchnmFH>_IFN8 z&_&tWME~6l>HZ;*Pk2&*9uKA(_D7bXQ7OZ?`bGG=kJ&wdMzOs@7?Lm^$-Irg;#`{S zVoMfyvCpBEJeCOd(N7a zsr@(%N#P2c6x8<68c7Zfx`-M4Kt$X!;9^PUIyhC{S z#zA>O0tiZAyP5e+gyId+eEI-EyCnqPXp|JKgW&rOvOBvj1a08nbi8BJZ%pPfE{nZ@ z{VZeK117-`kGqZu5<|#v(281&Z#JBgJO?E0f-!!6%c=<~#(a58?igAdMtTkDLJ;7> z!GQhaz$FS}MMn(tDQ!H>pq&9-3fA*Ws(}7zBI9KN%!)-E4brsTPxV{##qTxFO;h{V|YsyGTjPzJjIE=*I!*cRu(4gz9P@ekdjU&J+Lc>V9f6FkC6N|Yty zCnh1%&K!r-5b4v6xsojkB##b1lX=$?^@0tzLz#ykD- zAfT@xt2a3Id*g3v@Pd03X15KF)epso#BXmpzd0B`1FUe@N69Dv$XuMfZ3;_KeL^S07C7U+jK-LuOAn(4K z)dccWT_+#DPhZZxa$L?dw&a@ZC2(+}3p1fBQcHSr&pw1f;8&=We$^Y2ZbDly>FWgZ zu6cz5Z*TGUL>9oj{j7JCB$H`Y!`aX9`QT7d@AtDlqQ}|OSgB2Tu+|GjXLq-J?yiQ3 zzcSw9g`0L&1$1-4mW%M5b<%*A>snymC; zE9}BihQ$R8X!u8efUX)&#ek#ZVE}gx{CwWcZ5h$|0{EOKchZpul_~$lh}U0sLbv#m zzQS67dYY3pYoRl_Py_}LscNpj&NPRYh!Xt=efi10iPOAmvv@1gx1}1U5i!p?Jt;(3 z3Y4Hv95*}xQxri6*%c2He=`6lp2XA#X-3W67gN#ks%=#(M8F}fFtFuT&B5X{PU64U z^B#(b;|u**hLfY2Xc&|$akU(0H_jUt~F$jKOd{-unm4yF@OTw$jNtV^K zaA5$4%=i|6^GOBo9o*DaQAa$Slu|(oPxo}4A_i>*U-s3|pNMUA8P}z?3bN0VwZA9n zBSlnIxTK{v=5Z#8w$vZ06Onar7>5oRhAJ4G+3AHXER}t zP|2i%ptoQ|OW}Qw;qUc>Tm-j78GhCn={MUnA;c_V*1EUJ0AbZv%u=rmUQht+zp&FX z)%@^28d{+c2a|2Rd58DG&VuMKF+Lyu7u2`KMM@t?%^_xM@DO`kI{peeR7E+b)yCP#2*5`KS|O&eUS)2gOC@+kc6Wh?h2#!MC@>)=#Mm$MBC+9c+!}tbeY`E&w+Ui zCmH(O-5VcK0Qz;~_0yPvvyGqoq6ozz;2Jy^_=ngyZv#C)2Iz`Lx~#H~s4}c3+Y+4M zibm_A49Z@4TMydvB<90hSoL1O45X|WkYkbu_gaPjefK(c9xwuY1n{5ZKv|AQg|^|u+9Sa3W}<+VvFTZI=)(;ATgC*lsr7`;mGU`VYoA4!$4JJEyqQ?sMu2i5 z7NOw|6YA*d#nrORgEKH^pRw=C3Pt*sQ;T90npXC|7O-!qYN)#Oyl4MZr*cFCp_nZ3 z4Y8D8o+nu_POorU+C~Sjr_`Hsguz_B4KSe0hY@%m=ARnUZM%Sh%^ye32Ptv8)Th!lL9}@_s zVYawr@{c)M6ck`|v1hBAGm4aiVAjQj&Nq9`%3??~qdY@`MJV_K$-}$e49H=S>l<^z z0yk}&HN`%|aL*LNtWYs?RvA*mzuVvwK#!1<>Hr#s+C*;BAFev_S*Cw-(j5PS7$~(u zK@Xdqasv+`-7z#CjwUngL`RW9v>?J+#yX4*LN)jd^UH*-h}08o_Z`t$pf{tvho!^J z1`it@Q-v%oEa)5T>6U#Uts{B<`tfqzr?L+Ofl4ac_Wr*z|NgH0bPnvPAz&=7Wl`a? zYcKSFT+kxW>P0Bmcs7y7+HKR|)ZoFM_Yf5zFd(t<$aN%W7Xfn%w({mGD@5OYxU~x=hQc9!L)5`bCYgiB z(QZIy4MQq18gdh@X|7A!BuSt!FZ8}}BOW2;Q=}^S(OAd}R6t@iM4`jxP`!=S0^JBB z8-H0_8V%Zs3_Q8!60-h7Z#RfjBvXt878>&Ftws7nObZqt8G2V3OOk){PPJ#xZt`<@ zqYb$dg{harao!eqRl}>CcloL?w*MPipx&i?hi+hs(n2K)wq&Gf!9?b7KSb|9Ngk{5 znfllP=|~nGvY3TXqzk6*LVxL=PxRdwIR+TCSin5|IA^q9NbgM8!vAo~1p9Zpe5df; zoX+j7wc+-XQ;?rZ6krD$Rd?h@Z95rit%IdyH*ApK*Hq$Gmf(=wem)Q~7SVD>rVFDh z+G#eSc4gHyl2bFOJ&z#nQH1RoE#j2vJ)o2KB@nNbP2vd|k>`5R&?7?G=O;KKX%?&{6BOeG4z%DFzFppOmEyyApXsqMSYi8cmo5 zR8!FD2^&+wgu+*!=od&buoV+Nm0DlPPWLGJ)D!o!?gZ>yh9FGXd%=|*`r+}~$kAAf zP0HrvczW<5uTUU_1V+pXNCB4>7KRqF-k$rBekm@pY|)RnP`+wfL#p5xwkjQrDm|>| zS5%HU^)3+#L$uT?x)bA+yAl`#4LHePmCdTGV~^o)=_)@H&5$p-DC&QC>Azk3^>l$< z_p^y5LrUj|G6Rx=2Iz0d>+r7=;;fw1G9a<Q|gMvw57F_p9pgYsNCC17i z4O9bV8y(uWHLzQ2DO^^;b0u3R6N@%!P=H1urtL6(Q6fRP!ikRfjFYif-^SvFzmvsu z50M+{wN>#X8^9-UO{yF8It=&L*3DzME1mmXY_4GfA}l^<8kSpD69!vf+jlz=;mDt6;X!Rapz z9u5=MSBk{thChukbAOjG$JoEI_@u&Cj(|S`LxnJ+B+jXSU5nR#Li_8gTb5^-$}u_R zs?p~?lU?-$;ZuSpBQv#`M0;@E3`#_HcA(C@k$h0f<$i%8Gpv`wy=WxLh$X9NZnjrQhu zVU==GO@u?ji(6w~nioT_J&|)fAXYN;=6F7xP3W6`L=oDk_KO~ zc(D*KwNjOwI3*=U=*eo_D6o)xlwf9o*u#Z^FqB6W@?tI>l8b0RM zT6nm==U2hKvrIDAzwYEXAlfYm0#h>$@Bz`k zG{=Ev;CiR2I5J`pxiF}lIFsQ%pxG7;^veWXU#g=F4`{SDt>;k@^f_6O&BBcSipls& z)L;u9-Fq}~L9}N%GThGcbtd}N%ApvXF3ASL2QUeQ8u|K?e@&S)Q_;PQnr`NlpB%zS`OZ ziQqY!Jo3ZGty2SzD)YSc6(wJE*3BE8tRh8$Fp~Z_a}Qv0#F}dJEkVSbeW&cGjt&f5 zY3ij5rm`u-h@CknS z)Ev(6(wBwYF)*6t+h+gilK^Bn7XC+(>pVT?du0(R)BLSIr9PLeFdyN9IYW<;qtCPk z?sV}ixK0WSaT4ZeW#%b|-fTiI8I5NZ-~tvZDwJLIL-t@a)ze7A4?pLv)i~jt56l@% zOu+0AZ2+u*tSB8gl@6Xyh{Jt3)F4h5?ux_Lx?M7e^y_$ActztrPJ$~xhb-c+tcD-z zNY3rjC5!WsJxR;RYV*13x#aS0tcu4nss)AZm8j2Z?2x#DS+yU|I^j4R*G#60)u&vS zWOF;rjrD3Q{VcE)20xH9)EBQ%IKkXxQ%*|u-xx{I=t2b%DWWta`fHVRzMti>t}_nr z#AU0zkK6@HrJMN`M}=US2f5mWCKmET9mRtxEp4nu6XGm+xi`jdYB7AZt^CfF&btE} z5a+o;RjHww-@FSDV-fgF=UV%)AP7Z`@^WsN0cpV1BWtq>y0 zLYNHT`Hofa0caqzhNkyi%%VkpovUvi;am^6bmH(IsDi-c`_v<|2mNWE+=YbSfM6gt z%)tn_{TaPVAIq-7tNUvaiq)>d>ctexfDek8qG9uFJeW;ZXuDY0Zx!p`pjEI`YGBJ% zj`9x%CACL|*Q0@U>fJgeG=A#6n?Rvi_A{q?#``2N$I2=h6S^ar!W&pUM<;4J+l@_I zI~;DjR0Q+6ApE3A8NX=EJGiJ=$kC9X>mkbsQ!=&;exZ?(5sXWuIZ~)zDI-TUBBw*6 zAul1%2$mvL26z{JLoJf7`*Njc&7iM-L|hbLDpySaptZFQQeZgvc)gK3e=k+hnV)|Y zaB*_m>ewBigtZ3NRD8DD4*S$n>ocsJ@N~3vU)5QoqL2X4;57dH$x>ds@s=}wOF&> zGwgMbpzq0fP9Re-Noc9m6$*?Zhbo!mm&yJ?;VuwXxBuhg-|K^TqXf9B_h!~6YrHPN z+&B?wWHFtbFK@Pz7omEDOub7GWqJ=w1(To6FI%7_w_bXshD`8={L#dZJO-I2Oqf0> zK`6)w=J$g;JwMTG_(wR3t?2S1{M#&SQE*FP03)YhlsRE_C24ZyW_ERj}?z+8-D+ptg(mOL16CNJeiBnVUSaE{0BtK%`X3!gZy)&?p&dG ze4^}>aLJ(Kk=vXi{3LtolVs+mdZ>l_fqJMZI^%@H95=__bqm#2fA6qi_{JKkmU!ed zYjU_bE51(p$jD1_9E|gatta->DwvV0#HUEwA_UYmd4TuVqcs|5=BZBwytJ*?uh%Iv2}YwfB-OTW}SEjFPg4s1wOUp0eG4Agp3QC zGBL+kIL2rVVk>YcB;0uO6+;U1AO6mzrQxrMT+=zi77HfW9sZv4n|2@_kG_sz zkAQnWNSnYG)q3pKF;n3v1NF_2SNL7-C=DfPfpVE3>S>L5G~CpF(Oq~cq99n3Om*0PbE!jQw( zt#2T^d7M(rH;Ks#qw=NtKMmTk2Z*Cc&qLSsCt}H?lEc^`%9FDc3eapVgr!~~|3NKI zj&LSek3RyK(D=vRhoAy~&LrGi9P%VOYrqk*vNo4%>Km0hL~ zea6fE^bJQ1!iN<4KjP`ur}um4`{9Kdn|QR*5ik?qLH*W{t3F~{8w zlM)mQiNYBkFBU3m3Q2b&H)qx5HMX(+KX%f88zC+f#WG_KVv+8_LQILXLo(TUMI$9j zc(jxjRf#lYj+GcM?x${ea^A_uAygu7_~oHSAQ?A)cs93JS#29Nk8I(_Zt-vz1~nS< zK7b8)E>Q(!1PJy@7K(DHjxL%(&g!gn^Fi)6`3hdECt)(z469-uV^Q+p-#&Wy`FZ)X z@5%Ci0@jEG>h7Gaskwn2^TKo2l7Cfdw?&qva(&(M^#OjdC6zo$9L~MOU@1hLO}J#tJ&eVF_T> zN?eH;yQaVf2@9i^EpFB9_sgS4gH$TjOC>kRS5}i z1Z1vFH56!LeFtlpWtX>?_pfx5h%jHZXqsT`VQI9fF%-EyTw#8`*}-HreEhxRn6zYN zc+yf*yRer;t)Ou_El{)iGsoWIRQ7bCf>{LGQ$ZMzq$T4C6?OLNY2%*fsIH%$+F;yj zsORQ!Rd3|+y>!*)a$M|E<>{}cbS<0Wxns99_phaCHN$RJrGIbyElis0HI#3nL^NX=pZV97hAka-=Ls~QD7Ohngpd0$Ml8YvjMn0;NW zW+HpEO=)TG$Xe9!>#jczOPj!HPyh1cS}66@6YK{tXq^4sRi46|&A z2n*`h*!D^@;5M(a%%cp0;yY_Wk>f=jx2*}*1{B!mn%k6&<0OvS{wrX=-c!%F0I{}a z%_$TYSb#%wX~~-p=Xt|>CAqrx#TC*@=?uK;E#fr|mIPkZMCN>hTn&_br)NjM6fa|g z0h8%_f6Z$aQ|DLw(I7nzHy&n;mr1oiJt1KoBga&-7sI1?P<+xZEhwJ3?dy?m$HQUA zA~Q3q!5bV*rZ4qJ5N&ABe~17!6y$T<_nY*z8rIbn4aVGj*y&f#N%GvYRxs*U7pIjM zTPL&lybNwo+TCWON>k@Y(=d+JIDTxu2L~Ap06`G--QC*y)s@tg5y2b4y07ssluR#k z0V4Vus6ReDP%?Y)a9k=&>1p;hnaz3P?^W0hKh?X+lkHN~=+Lai3en3EX7TtqS6C2K zFffC|l1m)9+LKR_(VKu_Wk>rt`u z4h;4a$F~RlGB0CY7b%!>mwG&`+^1hTeSgYKVDMtv(VeG|<_kCETF~;v#`-d>Iy*u- zTmU~BI+g_zlnDhK2D6HzaJfIl%fX*$W-qE=cQm?t>t0q2E%yC9u1(M3vHw)LoORs7 zXVhv#S9&tvWCjZumMD$0e{w4pK>`@_UV-Y%Z7Bc>s~RL2rDAn+RRKZW5F(ZR)ZHmU z4f+|5RWf-OCC)LUu&JPpz(=)f03#bh`J}&;E7Yf5w|JB!ckKSfE#+-+sp6*pGDe2ZU;R4t zGW>ZdRBb=LJgUv=^IS<8;(P6w>n52x)3P$xmCzhmXf-6I>-UW*{4_7>BDw9JQWgfW zWrD^O)uQ5Q)mT`(=+o6WY8`ZD7lS(VZ+1|}u$HS%zM5S|87M!~>8~kVWXul!ND<{ja)4fNEi z3`hwyD?x_5h4jQh4-9DGe-tz1fbarv0^j zrIM<_GIiRl1}$s4+&41LKqNp6m()8@b5|zGq{8xPO$p0X_0S~-1m8M}5TI;pG1!~O z-JsiIP~NeyoQ^0UomCW;t5xJ^n{=GEbopFp1K0-sH?d61v0FvuwcldPo0jbN$>^_~ zF+o9ld!5@)(lo2fR@zc>L+G!ZNdei|ko`D#2;oEQlYdky$*YcHklEyQ@Qf#K%UlJ| zX^aUxRM^LGgF6^0DVLhxuu%r)E1JgE|MW!{;6oqJvOQ@b5x%A>Yg)!y`PZ|47OQEV z#*oUCb+C)!-W4;3{XZSv2@J|G>aens#mlMr#x1{jjO?1-} zm-q%mXC`ATvnKg!+(a6v9be7)&!GRKXhzffYWG)~NND*#qY#n(7W^LC2?PKFz!=KI z7ztYVAH{Jha1col$t#UBv%Ue| z<^D@2kPhKdiC{mQtrzu66bUCmY$gK8T0@B)y3a-BPeo~WnqCL_!E`k_8BR@(W*keJ zV*X@f-Wm zd}qM@rO?`Y@C;7jh5tn@YEF9h!)fb>^>r#{|1&)$|AdD%Jny3kmG0-~hdPl?WR-V8 zBuW*Q`4Wf94SxdUT=ESccp^NW@L#S@qD3}4AFj5qRnBLiV^`Jy^tpFwc)3eQHSC;O z)$&;RV*rE$MVI{6W8SDKUCP#a25D`sUxe%iwB7yQx*i-n!6akx7lV%2-v_<>*wv%=O_M(QTkCnk>|&onz`&>MZdm!KHuq|Q_DRch-^IF z|6OWL+I`~0gFDCz-JJ1yO3!Wdk=N7f|74+>&utO5gBTMu5@X~3t|mYBN3?hK0?{cX zV>ehov|903FoC0Sn85SxRV@s2%jD61l@3pDgFB~2fK z=}Oy7IEsXa7bvYvW_4vjkwwNs0RGD(K|JsI%)i8^_-Q0pO!X{Ec0Fe$Hktq@Kcd9^ zjR#mE5_c?-pZrBZ1m*2~CHolYY28%LD(PV&DZi;OxCE*)CJQN+Ynrdj zw_VxaESBQ=o1Dg`zT$bQ7F8{?zLfr$qwMF)C7mb^Zdr=Fw>O1a=Sw7Zx7jL*NajzL zv({j8Vz&_$*mvu5Ev0*SfnS%}r}8qbgV_+o<5R$}b}pK%+?+Mk_C&h=9LXdF1$;lzK7x}#ylPHhYRVgKJP~@DUwoit*?t7wl2X zB}vzxEPrm`P0X!O_<~%p7oSv8ZjNWWjOZUxbf)-`{7lK5m&L5V=VV&f`n!4f&ykao z&Pa&(Cx_vaP|kzB?=cf!@emC2^PC9AfRKy>(WFx>AMc-bE|ax;NKT#jpLz5}5Sqq@ zMr-%XD<)@z=Tpy0V%&Z4BIHamA!Va1h{k1k#V5KwfPyh?dzF|jDgh$kIku~GH*OLt`fnfEBqfPT0ePEG)o{WUnnkdbgbG?b8Tk)?ydY z>b}s-a+C9inB?V2i6}(rD{tqYZ{Ga{dx%bcyfi+uBNY&0)*6HmtDEEW9^2N{C$V>b z=zcZd&tktiZu+UkR0b}HC5oyDrg!9uf4AJf>L37>ZVf6{}tr_ zmiD?sETTFlwOA2gAvt-O^bTcy)z&sT=`Y=}mv#78nSHytzv}?j+|I<0(EwclgWx|NA%uV}5pWd!oSAFzkWP1#w02pp@+Xvt({0bV+}Nn-Id0=2W#Zu;VjSZ+ zHaa??%*7`yG`Z-bp!$u85TeoTc}I_jPO#Pj zOJFaoFX1?GG-qAcm+mwD`evwF;C{a6>jY3Z00o$qKknqdx{&k5NPxUYH9yp1Y-rFf z;fFzaqFq78>Gz5DpNSNr{7p6ehl<#w03~G(h+q|Lq2^u3p^#37jbI=Cf*^CSWQTofl zaZsssRT0eljF0ucYnrLP7s#PZWEx>!O5>wB8cDvyq)!5$8(c0A>O`Sxjh|GSqF0SK z_~--7$2`SJ#v05rw5SCtP1AJ%@na5|$J%Km{f{i8EJn*;mxWkB6Y_b<>nC0U`1{ta zsJqpd9gh!DkGA#!UY$$#M_H_TZDbqjeGNW^9y@2~{eq!`p`}k27eSsWe`j*lUTC{F zH0;h;nVFt{!EDH-t&b&GW|W&~q~Yyuj_IQ84W6%7mu{)N%(R5naVRh`X;t^dW02E= z@JutzGRlp2Yc98|Au&~_$14J;@^Z3>)TQcJfhqC~qUPE0ALCtgCQa7lLZZG$QhP_% zg|KCYLPF27s@V-xl|Bp=ed=Wtbfm)@)7-f0g(Yr)7)Ipul4#w7rdbAmrTHmK71zk1Z=)R*fy7#Nl#}LETQV^=PftbLx!_gG!cE zo77R_l#R1RCOW!t$GP3h6{7DJ6W=>UmUX!Um2_?1R;#y8!Z2yr($?t#_|izm;_gYz zJ?hc9q_B~;H}Ou|q$mhwOmRJM?gm)oudgdV6?8o=Ud))AOA((&*jGnMI0HU@aFTEZ zph)kuFf3A2?GNsyX*Uj_qipy8{>LdSB8Ol_9ouu;qRE_`;k| zkN_sPW~nalX4ijj`NMDuFK)74N}d?@c*w7$Q$vZGeR2?;B zFHxN~DxnSfo^vkC6p8IlK5)hXP>FUqSKCwe2)f;#@WJ}95$VF2eU*klgYlsdM?QV~ zhwf0Ob_KG+@yvn1&?Fy@G1qbI$=vGfeOw>hSRB-wSq+CswNxqZxdx^0&%W#_>%-=s z+-IMBw6-U^zmF}JFgsC~mj^UBBR81dcVyF6tBp3d2R)i9K`MMZ+q|Lj;sk%B(FHI-PVHD&nefSK!<*RtBZFB$wbacci#0NPB6cp--1&ztHZS;{t)B zAeGlfu^Y}Pcq#zNuIAML)9wC>Yx}KEvh=Yo{K-_&)d?DT@I7v^La%bK@^U^JsER87 zXSR&@-nkB-)AMvHVq=}{arUN5;WH7omwU*?SlOZd1-H^aYBF>XYH35Tysko*zw*b2 z-}f(^O%3NuZA^v_`Hc4uQ=h+*eaH;XuODr-jH!g>4B7pP+N9iAg z@-Zr3D-_KEFDPGt(4*}2^k3Dh2afJL$8x=v zaXQAXHcp4zI)9xmEWUJ=0KXXwRx&&UT@28H{WQQq^h8%PR(WU>99YqKsuzk42eAVA{rpF0@8CD*4a5BDe{A8hYS9Iq6E!J>Y`Dg*&S1E7PPT9F$6F$Z zmXmrT;32Lwd;3pl-AVHIEEw+1o)W0{qx-roZqVs`I&##@z61EH{tk*|X#A3y+d$UK zLi%a)%?$0xC;}G07nst35>~Rn8U|-On2X}m^u5$uEK$Bd=`3kJUrpIeia0;kA%ikh zxLCz$Ppi3cNli4zOQM=Xn4djVr)$P(=z|U^8v}VX2;VK8I1QFDK(%B$-xPb%4+siP zKfhCdoJKGRjw#4tKu0i`M~xMNI9m9}p`%CqOr;viVmr6H=)KPwO?@cM&5h#a+sj%W z{1D{HN0S~>I=Nio3i#;{3u6D;79S)f?aIBb({~!jLn6Vn68Ysl3?Zb*cxNfj!C^&N zX7MByu*rK#yk@;Ecfm*TM!o+o?vQlmV^!WCKl+j#=9?-X#+<)NHdRutg#S-pUl|t1 z)9kxgf+x5WG)RIcxCeI#?(Q03ad&qJu0etX7H5&*4#8R6b#eE*`M>9!`@HAebHB{= zR998YR98>^re|7RYMgOwha0X%NWpx{MBd+B+m081U%zBW4mOE9wPq|u0^>ePt-BB+Z~M{iP(4kc`3om=td z^P&^w!&*hatF8swhhi=6N_GTUPki6BtnP~);QJn1gOTgZ5~Uz` zJOHS1JhBenC)Mz9jR_)rW5v40+;?FW+ev1JRfxki!n(Gy7U0QkZCLp8Gwb&_M+?cI zyWe0u`Zr|FlF_fOqPsFrd;Z)-c!CX>R=At| zeqy#+j`NYPFD6dmaE#jAe5w2ctEP~%!3cb#(SSl8-oC`R2Iz|d|sgf zQDHX?1>8!nm)Uig4neiqWpCJB+$xmi6<+BRHz#+JCDRQ@J~XG5C=Zp4$)X|`+Lf^_ z`B)qf&hPSD!Lbw14M|-t35mHZV$)*JkWRK*I>peGg%w26lg=i^bi5paD6o)297@`M z9=Zz!^#j;8iW(m$334S@lM%!NU9D$Ef0G+`Cwl3A$MRHZEi-`q)d{n{MgrS3x1_4U zMj1n+fcO)QPdA>A%NXE$$=3l3=6PsxJx~YqrY3mHC2bJl7ck5Vi~Wm^zW6_G&JTev z&2W7V%uVyo0#Vh;?dv7lJW43p1foDvjZt)vb>DRPmE!?7lG3GONOqq0xQn58b6+Xi zM|-Jje#T$$N@zF_j`5CjDCLlk-{R1KiOV0+ByZxIR|DYo(zFRWUX6 z;xajVkW1n7I&0x2iDpD`B#hB^ia+JSsdlq|Fnld16X##C!^}^cflmBtc~jaW*nW))@fPJL$_^I{{|aYiMs zZ_}2!<|A5(Y2xcoFo|Hk8F4mG zC#L_^cT9VmCLAQFHFwVYhd&&g)KSj4HAYLHYNv!VOjQq?xXeEJl3#|j6D1=IGN=bD zh;hJrO1>xmR*1b#{RJKz9dg_`5E?!aYNYYr4xR6I&o=OtsVioZyLPzuqX;@<5j!7j>gZ?{&PN;V7iMOqK|PD`hAO+VrfwOd@YTjB7Firo^e_z~ z=SnfxeC{Drb~W-D!;3tJ?-iJE_E&$cNA^3bwTuh)3D*zF zB>+wS`QJ&y|xXld+lx`<+vy;Ns4$@D|h^wF!?OpK+;-QQKh zwg*R;^ZBTlt(X!G0@efUM|DBr8R1j+S*uNi&0hSIZ;61+wG=&$4U&p3iX(ZqhQbIp zagCzv4(4$rbFvfTJx`uwI$fGoQ31xa>IZVYTj=2Qa3UFv1D5pV!ffODOFiTXD~a!x z?0u)}L=Tq}5X7}!_gUSNNkh8wuqOzvi4K&_BM|Y6P@k{Us3}%~Y~DY!?{2kCPbWnV zGvXE)z;$#LSn)(Lo2w$XNY}`3H!Cx%_hsPKtaQ@m*|Ez(gV?M#6}kCFtvzg!I-M{& zKZa!j6?1MGuL3&bI3y-*x@QbGD7y@W3qaKmUIIgrW2XWL#-!-K(#Ev7~6lR(#WL3*I&JE-E>YP5C3oTy5BwNdYk^xpbes|@Y>+@{2(dM*ZkBIQ6bz^ zLLBl_VptjYM8qu2IRZZ23`&|WcxgvjY>Z@^3Ehv&ZdpZ_hvI<9;j(BY zw{3n;qS#5VoW1KlezXsQq*qPs5u(jmri?1rZjmRgW|_Os;((vX0n=t|{8@X-G$6}| z&3M0~l&I%~2Nijzv5+t;lmHx1yDg`JQ3eF=18q3K$E#);~JGHq~8O@)F`YbSVFl# zi1KNYqu_^Bwm*uKw)7m+B-P)qwc%h=d!nE5-)^~Sp4~f}`DA&;OpdY&#}+YKz?Fjt z&2^>4%PkY{MuzLXM4gHr&4`x_`o~ zBSet03*dTV=-Jy3opv+f@dsriHTYzpIiPJmF`~Cvvd2AZqZkikiWC?A{5oS9{Q=Bc z{fH{Oj{E3=aoaZ`;(KrQnv{PZ_qS-WZSlZ!HIz71=H;aocBf1WtXryLSisztUcUaf z?^Q2&E6DJpRn62*`$a?HKO18T)!Gnd#Dc!W-z8;xn`;;}j<{fm_Pj}uw=T9QG;2&k z0yYsvCfyHwp-LtT94UcfasuXO&#Z2G3Sg);o9N8cdOhO6x=Bu=q z_IIM?v-K=kwGqv@4D$Y-{#p_x%_0-31+@+7`Aj3Vb}rH# z+M_{UT0oR9`Eh9IW?^y;DZ7B<+&2K`*2-L$$ITaOPQ2Y3>>i-c2MXTUA98h9277Hc>N14_3ZOYD z+21aX#*G$M#R8mQ>ME=F+*+2aj^+o8#%mUVb8~i$cKvB`4cWj+x>y>E6#F@z7Zm29 zV=YMKLT=dI{Z;P8ODB*XeDkZL?&X^rua?{CsgR~G+kJ^jn~pyBr)&~~$^9+n#r9*j zLRTBN#ofw|4mzJ}vvLMbU+b8A8DOS1t1xpSY|+!8n||;De$MWmy@g0$}#9e2_LxOutA5l3d)W2UHOB`7##WT z2?NCE`Mwfqm>w)Ga&}Jy2s=O4^t>xwj9@FCnA=$YZ5%*%ecZqL%VtB@g6DbTfFHQglB~v++G?|b&)0?~T zShQRvAP!{;>EQe5u5|)TVg!oq!KSld=zw;qHpyeRt6zZ?5${mdJ|qfiIn#*)qX0?< zC5E#g8}z)^6M3XIVG%S7j&F8-Ae+>Ac5I#f=~rRatl)Kcj4un}bv z+7|y$vg2hsRsaXsf=FbRf_)v81`AmY$OlH2IdA|Xw^gq_$HbM>7bc$t9hI-s;$&A?ZA>xGgf*p ziI)g}b6KGYV)qhOloIeG1PJkS1t?Rj$-HWbG(E^smn4 z-$Df&aF*x&*?zu^8Bn|2yJjNDmdIJv7{fNpvIg{qI+_tp?EWP+YXu@a^Lh_V^0f?_isTiqtMSq{ce-|ksAY##e-9MWt zx10HVMu(yvFgu&WYk=dPs}RwtYs(3a17|L~3F+%@@)D~Tm>2Hu+;3PvUi^ob zud1>Y@HzTQO<#Z*z*j|DVP&Om>pUYo62P$Z5r!x<(UNqjvJ$ILhim!S-}Wn(6QqVJ z;?LnXj3(M6sAS>k-xX#B8V)jND}RkJe}$*67(RZL>cM{1V-K++n26hCd$GFpxHa;z zmFFc2)h5U-%s*SZC=WU6TllJ2%nP~zp&^o+zV=lUSPD={bgNzCC3$OIsgynkS2RE2 z%Sm0b`A5Jkcd-xpKGW31H3@IBA|Q7-wm65oM0||#Ese4{iYzcI$h7|xL#OCzBiEY) zJ`cQ6W^~o>la*im<3i%2leLx$vlL7%K3}@GSNpJl?e|SfR$|aB$yBu-@4k&>sM^Z^ zdOE*aJ{|6NJ~R`%eB89Cb$d9joEeSE>CjoC_rcr2?|Sj?@%YN@8a)ufa-r`*&PrTBJno+; zr9a@Luz?Hwj=JKl)&P|ERgS_@YWyRKdW3@Nh&073!^}BwhV(6nhBiN?o~?Kq)1unE zZf2mvzR$eTz74a=;$gokOs4f!@qFrahkJb=0|Ud-wz&sAyQhch#5FOv3-v_kx;|re z8>x$J_b>vVTMt`mQlURRq{2cp16@nNmvy=$kf(WkzSHb7TggTHzFoi$eUmZ?=yZeQUY?!F5U97$$2k#9=bIjC7hM>0AU+bd|q}Z5YgljDB z%X1A?k--_;^&Jd-=`0Wi#QND+Uhh-N$_jH~l+Fd<;QGWAZ2bXMeW|G^nj`NEx2(dOS6{>F2hpqkPizJeKv@S=$JO-#Ipn33O{&Pmq_la?`#)KIq+nD`pJ$eR(Pr zKUs7g_cOpv&Gy?UtOXfdjxw7x>uCKP$K}_U%{Wm}G=gNmtQ7}_a+jP6e0<56jxyfU z*|<#K{F0rn_|WY-p3A%7xa*d)33_zO!*D}3PI&(GY+q#T;}N(zp&r?DflGr*xUT?V z`btC#!9pC2HI)QWBPWvmO}wSyOC%Hd-;VK6DIg7BAO$1=mO!lW2Guq`=$ST-u96SN zQlzQ?6*1TAs6I-;{;G~_Ok`GKLfxVDYe{^j=H=pgH)@|Fd)Fz^(E@?hZF>5zI25!w zVR*M}g8$k-j9c*@n(i~N*sKI>X%i>@TB7p{R*F3ilyLh&?s1>WFsxJDMvfB* zaQ|Q#k({qsC4Hd`q?(GCzc3aEoy1qg7u_WsEUu_d&S!U15a2OvMjRdoW?7EF#3X@= zS)K(Zv?@%~nwXX;HdleKcotV=z2X8GzZ;2D36NptQFmC*<+pf4643Vt>Yf4Y(C2ZkigyCK)7GY*ZNUfl(0nAH@49;)_h*ZxXIm^I45&VhgJ z_umbf;vaMVkK=PzJ#Db*4GrY%-ZnG=icC;j#j@*jct``z9cE_u_UQeWc%$w?#;VUk z*=Jf$FnH_)u?zlDW5L`l1q`;)dyRg->yR3M@eKImnz(H8g~a{odiaJ&0z|EQ*pm5d zNr;Sy@SX*sv2ka8_I{9GPN5>M*OdEU)&J$iw1g!H+Qd?L!XZZo!LoN*eKA-}9l zdehj*LXfgz_OcJkw3_sAcjQpCH(PJ%!h!hY8qx64X(O$<{vI66S}?f#RUI1uNk)5h z&volaM2P%!v&mI>an(sz$NWO%M{E!ixT)B^(lx|9PlCb90w&;C^0OnAoZN4nTe(I= zwedh@cgfjx72b6<>OgKhWQGlLUrIi{?pbM|Lcm1`jc{1QVNnfU1OkN=KS0Q$i1Ux- zd|6UsPseu&vL_=JLU)eiH<4+(Hgg1RSBeDh`U0Y!wey6UNWEJuG4SVI&1KEz`;)YR zyiL}E^{%At<^34akR&!d*k){p_KreVEDm0~X+aykSBLrz+?IOi+G%ZE zTpAjiensVRTy0Uxj!tF-=vk{Hc1;(nJjZ5pJP3fg4F)1_Wfk6kc_I3;+rDBj%;EiL z9VdZph)p1rl$~z$YE}vuOK8jl@>Z!J<#fHJ^|u zo%=faWrMi|uiD{;@YV{MpW98GV4=M!1B3R>Uy4&J4ue&svT zk~C9&7;T1{;?v30I)wD0fppMBftWRV+l9+wTFJme87i-Y9PTebqYqR+5-TH;ThWyt zBQwZ`lKj6yXV{=H0oS0d}^(416a{aWPH7TBLAMAMxye2aIv*tW z<2l!5yAm|+Q8bQqZ7oQvlT|EE*IV8C71lkYqc`(V-ksmJ z!mQ1uCdfNIeb$v)GyYNF?s7lrcJF2pu~#{{$11da^dPyOoey&Btx(3n(~G%`!g(XB zs4x#q#3SE*8Xv@zRV4j1)FNAeo1itBn!U&ds-@zS@r_mX=Frd46nHO9q`smW4nHC} zgzYC1%wt<5paLLn=H%oiJKz~(-hi82t0HK$d; z7IE$NO~pzRzZp?PX*y-(iQ3Xw6RsVGhIEk&rzj04Cc{*g!et4F;ZW1VqMM_TyQX*Q zrRi|+#tV+HFRp*5@y7dxv$}VR)YR>pFctg2XG*HLt*oYEv$wlB>S9#Qx?)>E#kdwz zE8spH=Shz*kfl0sly8@kh%2Xzhm`F6qcei-FeXI6&#$im8ru>2?#Okc=)UXL$+V}5 zYq_Gk2f^((2dEpt4WOB)Lv4D~0zIV{2T<@0V1f_;pdcs(-#-@s07L``0Q^H02F8;C z{^kMvVc7$if2sd=fdKfwEP!}2)7Sqn0)AhBHu>Y1hd~#P@!2MVn1GdF!erBTrZ?Uv z=YuLrj}W3@=*r>j<`vxOtJVNZSHHljOVXq#>rbai0>HG4F_5PJutWXl-SW_piqDFu0u4ew}d!SifkZ!ul5gOTfAb&=HqHiI?TC{8_L?^U`kTEuR?5b8HZ@*BmKtjE8WOPWv@9>{ zaMAi(=l1Sw-n#GFPu=;Z>$&!O-PJ6HEsWuZ)ADaswG=K75yHmG=U?|~db^9hB7YWJ z4BITxq9T80Ak1TeGB>g4N!YoAxW}}#J(lKMaaUp6N+}JiJ_SPVSSS0fJ4!HI)H-H8 zeT*vN>QbKygvcKV@Mdg9fbq+s=n_O@Z_WtbJik|$o>J@h-DqFxaGo)LfH8MD_7f=U zdXc++ zQNya)YWtQ!+}+Pk{s;8&S6UNFRp$JNYR|;EcAXF0Jh6pxz4oir$9Neo?wvyFe8uVq zv4KAqW)Ji#?SI;E1$CajvvTw)1iBhInbDnQqg4L+wKrxQxH~Kg97|ZH0b`ZUmSZ!B z-61-i=o7ZnfC22){74q6U zVTOZ3q!-LO5~6Ce_#Z z*&oU;U~!hhuM)I<*o#bWnU;7dEXi|qN+umUcWRsgGLpI+`X0ZRZaLTl`$jM2f0xF@$Rwf<(MA~4;iy>4bMX?J?$SWS)5lY@lPyBpBH5i61yc30Q!eN#td{yUjP%cI z&ifR^3s}oP4R0gJWY2!Hbv_4gK5O@B`>docubn99XxuM*j@!Ob=WDfYu_f<`ceCgExo6f>TW{}`-5`(P5~}o;U^r1#8h8!Q3(#Nua|tbI{Qq1apT?ssdCf_ zLu<r^^^@$71Wnkq*Iw8sWsjfLPP@Y!8=$)nXZY?#xdQk`ea@29SRQ}8q11} zI&*1tf)=sy9d-bSOo^Jl=Z(TgxD4PXVovEmJD}MUiW@Q{eHev;)aEnV+_+Q`6mPLa z-UhwGcZ9p3wTvMUR*4>*yM)J(=*A$}{q<5T?&i$7s>MY|-iS6n-UFvSqO)+f?GrZU z{wg6%tT7oREdl~V5E3wadgwT17vC=qS*CgjxrFUjthZV1sGfu4nZ|c-K?gx``!wodRWTFUn4ef{E(Ix5P7@x%hBFY)*c3Qf}FX&c56u>1SQN zcnWtLziD8BUo9Tq*!4bB;<7qQvyYe+U7KjerJLbm-Dt6A=>Wit#^NO8>pmY+~( zOBrvFnVU$vwVx~qvh{E)XS9}zytLL;oxr-%loS)9IGzgun9H@>>}KX_Aqy9{(*{gW zpln!#tEL55ejegY1zm^`d8OZ&HeT@rslP5&i~RuEHt@4sU{pI3e}F6061#X+=Hsw-747=bY!?BZ z65x+6g;)$oEevJZ8CVc@cXK7ePLNTRK&ZxY+(c_Slb$y+!T0=FVcZBuO?+QE(B!@w z=h}!kui0hoi~{HI@+bKz8gyY&wVMWsuBEq)XlUv*z!#~p;Gly#{A!o$e=Pme^zmUl ztBnJ5Mj7~AyJ(>OBDd7=qt1vri}TcO<PK8Lt3giyU?vO7A?_rV}) zXL~;B)u+_mn35O}b@1Dv*ten(EKy`LNo2}oGRj~jVDSH&K+h+NoDW5&gsJ$K6g~J( z6kF7nA$j?A^Rc;E%G>i{&E|w;G7dh`?|(J+{~PoFkcJWdTbbu^ng+t|vkZJ7OWTDg zkEQGP;{s-q_+`V3zb~iY*|W*`@}UEPO_+N(p^In()pIwPu(v0sY`QnowXZUYZ6ZJ0 zu`7@E_!MP8aBk+j*{eMj>&Rg8eXeg}tuitS_wP;f?mL>zwNp7Yr(A6e_YVezb2a3q zJYeGoyy}iPgw#-X7KBvjr#E;z6c=>SAYhEr;_lg#-vq?3W8CY(3&$sAXkYsTg*+NrQpwpA3bU_YhZP5--2=`A6}<@<%GQqulLzY!V>Wf=xLxhbMNm-gUR?ggJ^ z_!~U{6`j7vvwnYF?7q^}-20tB`veZ}gwl8NR@;vn$($ed*Nq-a&KzG%SG4aRhZN>Q zzgBEoxRiXCxn>(0;{s)p_XJ%5mxa-}zRRNL2$S*ToXM+<89TdIB-sMKxZ1#r@Y?3Z2nAPX{(({p| z_h8%N2Kgt%z}NFi-oaem0JG|#S6o*F;dP>a0;5aUa}zJ^WD0E_8Ov76#dTt*t@k!9 zs8W}+4c$cO`;d6j>t!FWM*M{iTNnFyX>*)cwqhG+91)i*%J$-I>Qc6?!)$FsV`MFi zD}qAl#ZxP;_tadqUZj>^EE<2sL>&(-cMT~N_-2I0=n~J{DPY;?Ap7g zh{6h#cw+9lfY|#Nfb)VGT&)51yYmB4B&#n}EW}#Mzhg_Yc7Uk;G29#5R#o29ODK_r z6_(P|Vuc@2LCzi%dJ+1K-;5pZ$@?!=`hE(Y3>~j`bO+#(EH|8Fb$0J4ab&MdZ9N}f z3(5GR>Ga9emCrCyXd#@Ft$ox%4xIVxY#>Z2vJYhn@SQTRym)BhxZX0Bcsseu6#Y8- zJ^*K@EOzMmI-onX@rOJ7bg#txa_H3saXdd_dryEAc@q{<_p-`MYmhC%JOO3!O^h2> z(+yH@hL@sV$KLnN_L=Usi|`yE!IbLc#k4bdUpIK=ap-uIg`Or7;xgxGX3?)mfp+Ab z_}A&Zh9D`hXjgWB4BY4&g%V3fJoSiozxkPjZ57Zm0HAlPb_;nsnF6o{Ot?_#MD&S~ zU=QzlP&B`;1I34L-#AkdViCbSrVJ1pYeEXzI+eE!oyB3PCE;0ka&Nt}O`o-N<_DfM zM%9shjf#HQF|{9_=W-B8pvO0D4KgK{;0Th#si_iKR0g)wn@m;1wGJGg7U(eIskGDc z6<0wO#Bf!}x2p;Iv90GwV0%FHC^xD_go-b#X>Vsjm2gUaRB4t)(Tf7|Of5{x9VtNh zH}^f}R*R-OM5F8{VaKOgpIf<@dx#T z$!7oK{2}sjQ}OY?K;@hOTS7r8=s8%pAW%&i5j{cJ^CCSE6*6FlL5wMHK?wp7%>gG*D|zHv*Hyvy?R$I?={^ zUin>6kYR;9DZGQ& zQu-4moh;n#Sm49Q)9Xu9xVi-15>d^VQE112Vt+urmHPB|1R!=kV|g=h)+w7Zz`Hbq zOwNL^4Nn$SLN(>)`M}3SyXUyJ+9Nwv$!LF>PzIuF54GgnaDYcdtcRbswSeFHHB5RKeVvor)jtmGmvRD z-fXFhUBrho**kQFNZjkTC~zkbNbf_guXoJv8lPm`d!}>}^zz>BA12Q-HT(L724DS& z3xURtTXKE>5giKWQy|oL&#RNH?hW$IF>hjag>q#tVn zLhV`v6(5s$5C1UY~cf{hUGx>ZyUsci7 zS8cP(O91OB8ye%+bg{Dwd$MWy8|V2$0k`t^=uPlw5V(J$J{SQONF)4>s{ybu`yY<~ zN%$LW3&VnMz<)>q;+IcC-~rr>@vzCdDuoEJW;sv^521u9N`SE%j{N_W{Vnd_mHn6Y zfN{ASqIdGs>#bjaYe`Om_IGqeWVxG5uqzI6SCg+CY5{i}SFQ*Bt@TgSY@eN@_h1-7 zZog|zgTtflvB9a;HVSnS#jtLA7kT2M8z%r>weR_@)Y4a=`Mtgv(xozqZmF(1jjO@fphKYAq~ryBr(N1{d)tke%sSz&+2V*fPv#C2_kU= zL_g1Z)j!!HGIM^}Vx#0=_SNrI{`~1x$1TXWHZB_>WGJHd5tp3Iz!l%W_gE&7jL$y#98nV!siq%N4Au;j=C&f;S+VV=F|cUeAVIh>BjB$HKjM|Z(42^o7(s4mXL#EZ8d&c@O<#t>ti3E zAIc#~|2a6->ykLO)htls?4a@e^N)Rb4082@p81AO_3T>(fx$$x1I^9p!AN#9Td2b) zj&E(e=ZnApvU3_(V+#hRB(Z-j(@YQTl+Jg;dElL3lz*qHvSI)XU^`1P zGy9>O-ypxT&I>M1ex3B+xuC~rk!H#J8@OVO5KPMM#UsGaOAmn&OG$ev_z@qyNcv`@_??;q}mm$ z=oeW3N&_=`JR$oDxU0-nvG#l+#gpH0LoNrEN zG~n(KzdBkv7hz`XkZCj5m=D*f!f%5vv_YY|sWYWF40BYJ;AFgn)$>p1wc9_rJMAk( zU2~kNyu?4*gr|x;+S4I`5MH97WA;1ki<(6%o?F4D2DwwcFlGsuIa?=t_%lz7nxqqbCb^Y(OgFFg?ciBg`^DBuvNU zjHpt08;-V&IKz#{!-X0!#Pgsu%D5#j<th@8mGdV5#-_-RZcbikwPM_ zp>H{u1oVNW3l-d%zO`FljO_W$K3|9i)tV0nR)22RQBRp+<`M8CAjehI|9ZxGgnP>q$*%dM+K=<0cwB?a=GxdHW(7%53HL z`;a3oI#bCuXz(NcMP(5N=z|yag3D&bg@^6Ld-u79E7FCd_q>*(Cf3T%YTM>U*B%=d zH%z`;>B=0#kSm>0?c~a+CplF8lJ{KOzBCR7?ztt_;CkQuZ7dBZZ!B}IZ+2`Luk@&SLl6dvT&nv*CxBm(r17Tdi?Q5{*-%X6QfoTV&~DA+y< z#{_*nMxcV>%UI%+Ok()^_g=77y)E-m`vzGrwt|{%F^v7&<9v?K)^yZokl}T|=M5hq z+Fk$D*sZee&5jEjCHOXQ(yp~2UA}Psnu%IW7B*5ufCjK-W-nGJ(VjSJU93>{s*@fx zMV3$O#WazI!m2u-tyOhwj^q~!U%DPq{9cIuXwNMR3^van+1~gt16)~glIhTY-^})6 zJ|U_l)Y&(XkTCbZr)pw$H_o`WB0zn2VTXwvs(tft^>kmQbQI1QO436Hkbk3xRkuUc zv-~S%*^APD^K7~CGu#UK3omo};3seJL!*8NJ*go+xUhVau{SDga7-uL&#uk)Oh6qE zKEBB#MflIV&ddVdVFc@9ZrIb=N5Z~#7Y>LXleVUV|D8K)>;>mQ6*Owbnk}Tyn-xEz zXOH&vrwntW3H3R}O(XWQ0wT(EIQP>4!$tB3?D3Uf$ImYx&Q#}pn4Tq+!3=F}y%XzeF$ctBR2Vmzk*H(n=QlrkBq z2i-ow*A$1~%%hm*Cx<<fT_V@Z%9)%Yz*D+Zwz&rjME|Va_1kV)f-UU*2d?nWNci}lz{J1M zi-eB2cYQ~eE5Bvv`Ob+#+?>d>N_tVuBn3&Vmd1Xpe2P>~&kR)pZpC4x^M^o5u}$9; zPSx6yrS~39iy~ooSkonhYv12_ym|veL~3SSSLMe!Trr(d8WCxKI+Ps89$g=c^DV?G z0ekf4j)<99cz0e5d6?YKs6UxD?*6#Yo?fIMa4hjWn(HHH>L8f*-)_Cv*1$lCHvWZ* zfp=LzY$r+2XZ!(M?`+y(EACFHhEUM02l=$;Njun)_sfekeYkMtYnyjWXSh+tXo~23 zAxcoJa7eb5frr)oCTDpd&QR?92k=LI3+IuSvtH`AZ>n)+e-j+1l!{6)$-&br_X>?( zt$e&a?fv{mGmq9D&=J$DDk})nmYvDdB{@K z;B3t>~kW6SeZ0N&>k=;Y#;ZFx_#TPt#FhfRtBQ4bNKq%Vsyhc<;nDUdr_s zr)GCn_$X^xwk}5mT2CfKs-+(cAbrkgGTD;V3VeG5R)Py{`h#S$d}w$*NOse8i}Iz! zlm`D-&j=DQY34dV6eL85>0QQ$b!onsAk|~Ig!D*6(~=~h>ipT#SA3A<%_0^T9Za~l z#TlfP`VzDS_Zi6&qm0r2)j8vvy^l<#Th_nTJl5jdV2jQ3KZF5kw;gq`hhjD3Uggnl z44(C}MTzE`9cJNQ^?H^7m!e@XS*q7@e14%aS$3rN;mwWH^_a)(SlYWrYFL(E@C^)1 z8?cZ(|Hk;Aw)+nh_dij#|A~|Rci_y2HSz6vz7nuw&^2TOk8-P<9^cm*><2e`>Fr;z TK*0kG0|3(E3Sw2FhJpVFq}&)< literal 0 HcmV?d00001 diff --git a/doc/administration/geo/setup/two_single_node_external_services.md b/doc/administration/geo/setup/two_single_node_external_services.md index 9a6339c18f7..9a14bfd1b11 100644 --- a/doc/administration/geo/setup/two_single_node_external_services.md +++ b/doc/administration/geo/setup/two_single_node_external_services.md @@ -322,7 +322,7 @@ secondary site is a read-only copy. 1. Select **Geo > Sites**. 1. Select **Add site**. - ![Form to add a new secondary Geo site](../replication/img/adding_a_secondary_v15_8.png) + ![Form to add a new secondary Geo site](img/adding_a_secondary_v15_8.png) 1. In **Name**, enter the value for `gitlab_rails['geo_node_name']` in `/etc/gitlab/gitlab.rb`. The values must match exactly. @@ -397,7 +397,7 @@ The initial replication might take some time. You can monitor the synchronization process on each Geo site from the primary site **Geo Sites** dashboard in your browser. -![Geo admin dashboard showing the synchronization status of a secondary site.](../replication/img/geo_dashboard_v14_0.png) +![Geo admin dashboard showing the synchronization status of a secondary site.](img/geo_dashboard_v14_0.png) ## Configure the tracking database diff --git a/doc/administration/geo/setup/two_single_node_sites.md b/doc/administration/geo/setup/two_single_node_sites.md index 0df2904ded6..6b11f01bc5e 100644 --- a/doc/administration/geo/setup/two_single_node_sites.md +++ b/doc/administration/geo/setup/two_single_node_sites.md @@ -592,7 +592,7 @@ You must manually replicate the secret file across all of your secondary sites, 1. Select **Geo > Sites**. 1. Select **Add site**. - ![Form to add a new site with three input fields: Name, External URL, and Internal URL (optional).](../replication/img/adding_a_secondary_v15_8.png) + ![Form to add a new site with three input fields: Name, External URL, and Internal URL (optional).](img/adding_a_secondary_v15_8.png) 1. In **Name**, enter the value for `gitlab_rails['geo_node_name']` in `/etc/gitlab/gitlab.rb`. The values must match exactly. @@ -667,7 +667,7 @@ The initial replication might take some time. You can monitor the synchronization process on each Geo site from the primary site **Geo Sites** dashboard in your browser. -![The Geo Sites dashboard displaying the synchronization status.](../replication/img/geo_dashboard_v14_0.png) +![The Geo Sites dashboard displaying the synchronization status.](img/geo_dashboard_v14_0.png) ## Related topics 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 5002f626ab8..6b669deaf68 100644 --- a/doc/administration/gitlab_duo_self_hosted/configure_duo_features.md +++ b/doc/administration/gitlab_duo_self_hosted/configure_duo_features.md @@ -163,7 +163,7 @@ Configure the GitLab Duo feature and sub-feature to send queries to the configur For example, for the code generation sub-feature under GitLab Duo Code Suggestions, you can select **Claude-3 on Bedrock deployment (Claude 3)**. - ![GitLab Duo Self-Hosted Feature Configuration](../img/gitlab_duo_self_hosted_feature_configuration_v17_11.png) + ![GitLab Duo Self-Hosted Feature Configuration](img/gitlab_duo_self_hosted_feature_configuration_v17_11.png) #### GitLab Duo Chat sub-feature fall back configuration @@ -190,4 +190,4 @@ To disable a GitLab Duo feature or sub-feature: For example, to specifically disable the `Write Test` and `Refactor Code` features, select **Disabled**: - ![Disabling GitLab Duo Feature](../img/gitlab_duo_self_hosted_disable_feature_v17_11.png) + ![Disabling GitLab Duo Feature](img/gitlab_duo_self_hosted_disable_feature_v17_11.png) diff --git a/doc/administration/img/gitlab_duo_self_hosted_disable_feature_v17_11.png b/doc/administration/gitlab_duo_self_hosted/img/gitlab_duo_self_hosted_disable_feature_v17_11.png similarity index 100% rename from doc/administration/img/gitlab_duo_self_hosted_disable_feature_v17_11.png rename to doc/administration/gitlab_duo_self_hosted/img/gitlab_duo_self_hosted_disable_feature_v17_11.png diff --git a/doc/administration/img/gitlab_duo_self_hosted_feature_configuration_v17_11.png b/doc/administration/gitlab_duo_self_hosted/img/gitlab_duo_self_hosted_feature_configuration_v17_11.png similarity index 100% rename from doc/administration/img/gitlab_duo_self_hosted_feature_configuration_v17_11.png rename to doc/administration/gitlab_duo_self_hosted/img/gitlab_duo_self_hosted_feature_configuration_v17_11.png diff --git a/doc/administration/img/kroki_c4_diagram_v13_7.png b/doc/administration/integration/img/kroki_c4_diagram_v13_7.png similarity index 100% rename from doc/administration/img/kroki_c4_diagram_v13_7.png rename to doc/administration/integration/img/kroki_c4_diagram_v13_7.png diff --git a/doc/administration/img/kroki_graphviz_diagram_v13_7.png b/doc/administration/integration/img/kroki_graphviz_diagram_v13_7.png similarity index 100% rename from doc/administration/img/kroki_graphviz_diagram_v13_7.png rename to doc/administration/integration/img/kroki_graphviz_diagram_v13_7.png diff --git a/doc/administration/img/kroki_nomnoml_diagram_v13_7.png b/doc/administration/integration/img/kroki_nomnoml_diagram_v13_7.png similarity index 100% rename from doc/administration/img/kroki_nomnoml_diagram_v13_7.png rename to doc/administration/integration/img/kroki_nomnoml_diagram_v13_7.png diff --git a/doc/administration/img/kroki_plantuml_diagram_v13_7.png b/doc/administration/integration/img/kroki_plantuml_diagram_v13_7.png similarity index 100% rename from doc/administration/img/kroki_plantuml_diagram_v13_7.png rename to doc/administration/integration/img/kroki_plantuml_diagram_v13_7.png diff --git a/doc/administration/integration/kroki.md b/doc/administration/integration/kroki.md index ad636565dba..3a4333eae46 100644 --- a/doc/administration/integration/kroki.md +++ b/doc/administration/integration/kroki.md @@ -122,7 +122,7 @@ The above blocks are converted to an HTML image tag with source pointing to the Kroki instance. If the Kroki server is correctly configured, this should render a nice diagram instead of the block: -![A PlantUML diagram rendered from example code.](../img/kroki_plantuml_diagram_v13_7.png) +![A PlantUML diagram rendered from example code.](img/kroki_plantuml_diagram_v13_7.png) Kroki supports more than a dozen diagram libraries. Here's a few examples for AsciiDoc: @@ -153,7 +153,7 @@ digraph finite_state_machine { .... ``` -![A GraphViz diagram generated from example code.](../img/kroki_graphviz_diagram_v13_7.png) +![A GraphViz diagram generated from example code.](img/kroki_graphviz_diagram_v13_7.png) **C4 (based on PlantUML)** @@ -179,7 +179,7 @@ Rel(banking_system, mainframe, "Uses") .... ``` -![A C4 PlantUML diagram generated from example code.](../img/kroki_c4_diagram_v13_7.png) +![A C4 PlantUML diagram generated from example code.](img/kroki_c4_diagram_v13_7.png) @@ -206,4 +206,4 @@ Rel(banking_system, mainframe, "Uses") .... ``` -![A Nomnoml diagram generated from example code.](../img/kroki_nomnoml_diagram_v13_7.png) +![A Nomnoml diagram generated from example code.](img/kroki_nomnoml_diagram_v13_7.png) diff --git a/doc/administration/settings/account_and_limit_settings.md b/doc/administration/settings/account_and_limit_settings.md index 6553f5265fe..32454bf8ff9 100644 --- a/doc/administration/settings/account_and_limit_settings.md +++ b/doc/administration/settings/account_and_limit_settings.md @@ -171,7 +171,7 @@ You can change how long users can remain signed in without activity. {{< /alert >}} -If [Remember me](#turn-remember-me-on-or-off) is enabled, users' sessions can remain active for an indefinite period of time. +If [Remember me](#configure-the-remember-me-option) is enabled, users' sessions can remain active for an indefinite period of time. For details, see [cookies used for sign-in](../../user/profile/_index.md#cookies-used-for-sign-in). @@ -195,7 +195,7 @@ By default, sessions expire a set amount of time after the session becomes inact When the session duration is met, the session ends and the user is signed out even if: - The user is still actively using the session. -- The user selected [remember me](#turn-remember-me-on-or-off) during sign in. +- The user selected [remember me](#configure-the-remember-me-option) during sign in. 1. On the left sidebar, at the bottom, select **Admin Area**. 1. Select **Settings > General**. @@ -204,7 +204,7 @@ When the session duration is met, the session ends and the user is signed out ev After a session ends, a window prompts the user to sign in again. -### Turn Remember me on or off +### Configure the Remember me option {{< history >}} diff --git a/doc/administration/settings/visibility_and_access_controls.md b/doc/administration/settings/visibility_and_access_controls.md index b2a06fe23da..b30f59e233c 100644 --- a/doc/administration/settings/visibility_and_access_controls.md +++ b/doc/administration/settings/visibility_and_access_controls.md @@ -440,7 +440,7 @@ Prerequisites: this list by authorization type. 1. Select **Save changes**. -## Disable user invitations +## Prevent invitations to groups and projects {{< history >}} @@ -448,14 +448,16 @@ Prerequisites: {{< /history >}} -You can disable the ability for non-administrators to invite users to groups or projects. After +You can remove the ability for non-administrators to invite users to all groups or projects on the instance. After you configure this setting, only administrators can invite users to groups or projects on the instance. +You can also prevent user invitations for a specific group. For more information, see [prevent user invitations to a group](../../user/group/manage.md#prevent-invitations-to-a-group). + Prerequisites: - You must be an administrator. -To disable user invitations: +To prevent invitations to an instance: 1. On the left sidebar, at the bottom, select **Admin**. 1. Select **Settings > General**. diff --git a/doc/api/invitations.md b/doc/api/invitations.md index 33c4607d1e1..39b066476a4 100644 --- a/doc/api/invitations.md +++ b/doc/api/invitations.md @@ -39,8 +39,8 @@ Prerequisites: - You must have the Owner or Maintainer role for the project. - [Group membership lock]( ../user/group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group) must be disabled. - For GitLab Self-Managed instances: - - If [user sign-ups are disabled](../administration/settings/sign_up_restrictions.md#disable-new-sign-ups), an administrator must add the user. - - If [user invitations are disabled](../administration/settings/visibility_and_access_controls.md#disable-user-invitations), an administrator must add the user. + - If [new sign-ups are disabled](../administration/settings/sign_up_restrictions.md#disable-new-sign-ups), an administrator must add the user. + - If [user invitations are not allowed](../administration/settings/visibility_and_access_controls.md#prevent-invitations-to-groups-and-projects), an administrator must add the user. - If [administrator approval for role promotions is enabled](../administration/settings/sign_up_restrictions.md#turn-on-administrator-approval-for-role-promotions), an administrator must approve the invitation. ```plaintext diff --git a/doc/api/settings.md b/doc/api/settings.md index 898da1117e2..9a4671b8eb8 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -688,7 +688,7 @@ to configure other related settings. These requirements are | `recaptcha_site_key` | string | required by: `recaptcha_enabled` | Site key for reCAPTCHA. | | `receptive_cluster_agents_enabled` | boolean | no | Enable receptive mode for GitLab Agents for Kubernetes. | | `receive_max_input_size` | integer | no | Maximum push size (MB). | -| `remember_me_enabled` | boolean | no | Enable [**Remember me** setting](../administration/settings/account_and_limit_settings.md#turn-remember-me-on-or-off). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/369133) in GitLab 16.0. | +| `remember_me_enabled` | boolean | no | Enable [**Remember me** setting](../administration/settings/account_and_limit_settings.md#configure-the-remember-me-option). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/369133) in GitLab 16.0. | | `repository_checks_enabled` | boolean | no | GitLab periodically runs `git fsck` in all project and wiki repositories to look for silent disk corruption issues. | | `repository_size_limit` | integer | no | Size limit per repository (MB). Premium and Ultimate only. | | `repository_storages_weighted` | hash of strings to integers | no | Hash of names of taken from `gitlab.yml` to [weights](../administration/repository_storage_paths.md#configure-where-new-repositories-are-stored). New projects are created in one of these stores, chosen by a weighted random selection. | diff --git a/doc/ci/cloud_deployment/_index.md b/doc/ci/cloud_deployment/_index.md index eaf6eac0e04..f7647ff9554 100644 --- a/doc/ci/cloud_deployment/_index.md +++ b/doc/ci/cloud_deployment/_index.md @@ -171,7 +171,7 @@ When you configure related JSON objects and use the template, the pipeline: 1. **Deploys to EC2**: The content is deployed on an [AWS EC2](https://aws.amazon.com/ec2/) instance, as shown in this diagram: -![Shows the CF-Provision-and-Deploy-EC2 pipeline, including the steps of provisioning infrastructure, pushing artifacts to S3, and deploying to EC2.](../img/cf_ec2_diagram_v13_5.png) +![Shows the CF-Provision-and-Deploy-EC2 pipeline, including the steps of provisioning infrastructure, pushing artifacts to S3, and deploying to EC2.](img/cf_ec2_diagram_v13_5.png) ### Configure the template and JSON diff --git a/doc/ci/img/cf_ec2_diagram_v13_5.png b/doc/ci/cloud_deployment/img/cf_ec2_diagram_v13_5.png similarity index 100% rename from doc/ci/img/cf_ec2_diagram_v13_5.png rename to doc/ci/cloud_deployment/img/cf_ec2_diagram_v13_5.png diff --git a/doc/ci/environments/_index.md b/doc/ci/environments/_index.md index 67fb7256847..b2ff3606110 100644 --- a/doc/ci/environments/_index.md +++ b/doc/ci/environments/_index.md @@ -62,11 +62,11 @@ The [environment URL](../yaml/_index.md#environmenturl) is displayed in a few places in GitLab: - In a merge request as a link: - ![Environment URL in merge request](../img/environments_mr_review_app_v11_10.png) + ![Environment URL in merge request](img/environments_mr_review_app_v11_10.png) - In the Environments view as a button: ![Open live environment from environments view](img/environments_open_live_environment_v14_8.png) - In the Deployments view as a button: - ![Environment URL in deployments](../img/deployments_view_v11_10.png) + ![Environment URL in deployments](img/deployments_view_v11_10.png) You can see this information in a merge request if: @@ -75,7 +75,7 @@ You can see this information in a merge request if: For example: -![Environment URLs in merge request](../img/environments_link_url_mr_v10_1.png) +![Environment URLs in merge request](img/environments_link_url_mr_v10_1.png) #### Go from source files to public pages diff --git a/doc/ci/environments/configure_kubernetes_deployments.md b/doc/ci/environments/configure_kubernetes_deployments.md index 70c765f7d60..3d753e8d9cb 100644 --- a/doc/ci/environments/configure_kubernetes_deployments.md +++ b/doc/ci/environments/configure_kubernetes_deployments.md @@ -54,7 +54,7 @@ When you use the GitLab Kubernetes integration to deploy to a Kubernetes cluster you can view cluster and namespace information. On the deployment job page, it's displayed above the job trace: -![Deployment cluster information with cluster and namespace.](../img/environments_deployment_cluster_v12_8.png) +![Deployment cluster information with cluster and namespace.](img/environments_deployment_cluster_v12_8.png) ## Configure incremental rollouts diff --git a/doc/ci/img/deployments_view_v11_10.png b/doc/ci/environments/img/deployments_view_v11_10.png similarity index 100% rename from doc/ci/img/deployments_view_v11_10.png rename to doc/ci/environments/img/deployments_view_v11_10.png diff --git a/doc/ci/img/environments_deployment_cluster_v12_8.png b/doc/ci/environments/img/environments_deployment_cluster_v12_8.png similarity index 100% rename from doc/ci/img/environments_deployment_cluster_v12_8.png rename to doc/ci/environments/img/environments_deployment_cluster_v12_8.png diff --git a/doc/ci/img/environments_link_url_mr_v10_1.png b/doc/ci/environments/img/environments_link_url_mr_v10_1.png similarity index 100% rename from doc/ci/img/environments_link_url_mr_v10_1.png rename to doc/ci/environments/img/environments_link_url_mr_v10_1.png diff --git a/doc/ci/img/environments_mr_review_app_v11_10.png b/doc/ci/environments/img/environments_mr_review_app_v11_10.png similarity index 100% rename from doc/ci/img/environments_mr_review_app_v11_10.png rename to doc/ci/environments/img/environments_mr_review_app_v11_10.png diff --git a/doc/ci/jobs/fine_grained_permissions.md b/doc/ci/jobs/fine_grained_permissions.md index 68f06a7b811..4f5bf474c93 100644 --- a/doc/ci/jobs/fine_grained_permissions.md +++ b/doc/ci/jobs/fine_grained_permissions.md @@ -17,24 +17,25 @@ title: Fine-grained permissions for CI/CD job tokens {{< details >}} -Tier: Free, Premium, Ultimate -Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated -Status: Experiment +- Tier: Free, Premium, Ultimate +- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated +- Status: Beta {{< /details >}} {{< history >}} -- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/15234) in GitLab 17.10. This feature is an [experiment](../../policy/development_stages_support.md#experiment). +- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/15234) as an [experiment](../../policy/development_stages_support.md#experiment) in GitLab 17.10. +- [Changed](https://gitlab.com/groups/gitlab-org/-/epics/16199) from experiment to beta in GitLab 18.0. {{< /history >}} You can use fine-grained permissions to explicitly allow access to a limited set of API endpoints. These permissions are applied to the CI/CD job tokens in a specified project. -This feature is an [experiment](../../policy/development_stages_support.md#experiment) and subject to change without notice. This feature is not ready for production use. If you want to use this feature, you should test outside of production first. +This feature is in [beta](../../policy/development_stages_support.md#beta). -## Enable use of fine-grained permissions +## Enable fine-grained permissions Prerequisites: diff --git a/doc/ci/runners/hosted_runners/_index.md b/doc/ci/runners/hosted_runners/_index.md index 22f84532a75..75dde9c9f9f 100644 --- a/doc/ci/runners/hosted_runners/_index.md +++ b/doc/ci/runners/hosted_runners/_index.md @@ -65,7 +65,7 @@ Hosted runners for GitLab.com are configured as such: The following graphic shows the architecture diagram of hosted runners for GitLab.com -![Hosted runners for GitLab.com architecture](../img/gitlab-hosted_runners_architecture_v17_0.png) +![Hosted runners for GitLab.com architecture](img/gitlab-hosted_runners_architecture_v17_0.png) For more information on how runners are authenticating and executing the job payload, see [Runner Execution Flow](https://docs.gitlab.com/runner#runner-execution-flow). @@ -74,7 +74,7 @@ For more information on how runners are authenticating and executing the job pay In addition to isolating runners on the network, each ephemeral runner VM only serves a single job and is deleted straight after the job execution. In the following example, three jobs are executed in a project's pipeline. Each of these jobs runs in a dedicated ephemeral VM. -![Job isolation](../img/build_isolation_v17_9.png) +![Job isolation](img/build_isolation_v17_9.png) The build job ran on `runner-ns46nmmj-project-43717858`, test job on `f131a6a2runner-new2m-od-project-43717858` and deploy job on `runner-tmand5m-project-43717858`. diff --git a/doc/ci/runners/img/build_isolation_v17_9.png b/doc/ci/runners/hosted_runners/img/build_isolation_v17_9.png similarity index 100% rename from doc/ci/runners/img/build_isolation_v17_9.png rename to doc/ci/runners/hosted_runners/img/build_isolation_v17_9.png diff --git a/doc/ci/runners/img/gitlab-hosted_runners_architecture_v17_0.png b/doc/ci/runners/hosted_runners/img/gitlab-hosted_runners_architecture_v17_0.png similarity index 100% rename from doc/ci/runners/img/gitlab-hosted_runners_architecture_v17_0.png rename to doc/ci/runners/hosted_runners/img/gitlab-hosted_runners_architecture_v17_0.png diff --git a/doc/ci/secrets/_index.md b/doc/ci/secrets/_index.md index c3b8224600c..8772d97bcb5 100644 --- a/doc/ci/secrets/_index.md +++ b/doc/ci/secrets/_index.md @@ -40,7 +40,7 @@ can [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job). The flow for using GitLab with HashiCorp Vault is summarized by this diagram: -![Flow between GitLab and HashiCorp](../img/gitlab_vault_workflow_v13_4.png "How GitLab authenticates with HashiCorp Vault") +![Flow between GitLab and HashiCorp](img/gitlab_vault_workflow_v13_4.png "How GitLab authenticates with HashiCorp Vault") 1. Configure your vault and secrets. 1. Generate your JWT and provide it to your CI job. diff --git a/doc/ci/img/gitlab_vault_workflow_v13_4.png b/doc/ci/secrets/img/gitlab_vault_workflow_v13_4.png similarity index 100% rename from doc/ci/img/gitlab_vault_workflow_v13_4.png rename to doc/ci/secrets/img/gitlab_vault_workflow_v13_4.png diff --git a/doc/ci/testing/code_coverage/_index.md b/doc/ci/testing/code_coverage/_index.md index 503182e9fc2..4eee6c79954 100644 --- a/doc/ci/testing/code_coverage/_index.md +++ b/doc/ci/testing/code_coverage/_index.md @@ -229,7 +229,7 @@ After a pipeline runs successfully, you can view code coverage results in: - Merge request widget: See the coverage percentage and changes compared to the target branch. - ![Merge request widget showing code coverage percentage](../img/pipelines_test_coverage_mr_widget_v17_3.png) + ![Merge request widget showing code coverage percentage](img/pipelines_test_coverage_mr_widget_v17_3.png) - Merge request diff: Review which lines are covered by tests. Available with Cobertura and JaCoCo reports. - Pipeline jobs: Monitor coverage results for individual jobs. diff --git a/doc/ci/testing/img/pipelines_test_coverage_mr_widget_v17_3.png b/doc/ci/testing/code_coverage/img/pipelines_test_coverage_mr_widget_v17_3.png similarity index 100% rename from doc/ci/testing/img/pipelines_test_coverage_mr_widget_v17_3.png rename to doc/ci/testing/code_coverage/img/pipelines_test_coverage_mr_widget_v17_3.png diff --git a/doc/legal/licensing_policy.md b/doc/legal/licensing_policy.md index 0172287bb75..39a87a52663 100644 --- a/doc/legal/licensing_policy.md +++ b/doc/legal/licensing_policy.md @@ -77,7 +77,7 @@ Customers may have multiple instances of Free tier, subject to some exceptions. For the Free tier of GitLab.com, [there is a five-user maximum on a top-level namespace with private visibility](../user/free_user_limit.md) per customer or entity. This five-user maximum is in the aggregate of any Free tier instances. So, for example, if a customer has one Free tier instance with five users, -that customer is prohibited from activating an additional Free tier instance of any user level since the five-user maximum has been met. +that customer is prohibited from activating an additional Free tier instance of any user level because the five-user maximum has been met. For the Free tier of self-managed, there is no five-user maximum. @@ -147,7 +147,7 @@ The following scenarios reflect questions a customer may ask related to multiple - Q: I want to buy a license for 50 total users, but want to split these users into two instances. Can I do this? - A: Yes, provided it is for two GitLab Self-Managed instances, you can apply one Cloud Licensing activation code (or license key) to multiple GitLab Self-Managed instances, - provided that the users on the instances are the same, or are a subset of the total users. In this case, since there are 50 total or unique users, you may split + provided that the users on the instances are the same, or are a subset of the total users. In this case, because there are 50 total or unique users, you may split those users into two subset instances. #### Example 2 diff --git a/doc/solutions/components/duo_workflow_codestyle.md b/doc/solutions/components/duo_workflow_codestyle.md index 1810babc4fa..f3347a357b1 100644 --- a/doc/solutions/components/duo_workflow_codestyle.md +++ b/doc/solutions/components/duo_workflow_codestyle.md @@ -1,317 +1,13 @@ --- -stage: Solutions Architecture -group: Solutions Architecture -info: This page is owned by the Solutions Architecture team. -title: Duo Workflow Use Case for Applying Coding Style +redirect_to: 'duo_workflow/duo_workflow_codestyle.md' +remove_date: '2025-08-15' --- -{{< details >}} + -- Tier: Ultimate with GitLab Duo Workflow -- Offering: GitLab.com -- Status: Experiment +This document was moved to [another location](duo_workflow/duo_workflow_codestyle.md). -{{< /details >}} - -## Getting Started - -### Download the Solution Component - -1. Obtain the invitation code from your account team. -1. Download the solution component from [the solution component webstore](https://cloud.gitlab-accelerator-marketplace.com) by using your invitation code. - -## Duo Workflow Use Case: Improve Java Application with Style Guide - -The document describes GitLab Duo Workflow Solution with prompt and context library. The purpose of the solution is to improve appliction coding based on defined style. - -This solution provides a GitLab issue as the prompt and the style guide as the context, designed to automate Java style guidelines to codebases using GitLab Duo Workflow. The prompt and context library enables Duo Workflow to: - -1. Access centralized style guide content stored in GitLab repository, -1. Understand domain-specific coding standards, and -1. Apply consistent formatting to Java code while preserving functionality. - -For detailed information about GitLab Duo Workflow, review [the document here](../../user/duo_workflow/_index.md). - -### Key Benefits - -- **Enforces consistent style** across all Java codebases -- **Automates style application** without manual effort -- **Maintains code functionality** while improving readability -- **Integrates with GitLab Workflow** for seamless implementation -- **Reduces code review time** spent addressing style issues -- **Serves as a learning tool** for developers to understand style guidelines - -### Sample Result - -When properly configured, the prompt will transform your code to match enterprise standards, similar to the transformation shown in this diff: - -![Duo Workflow Output](img/duoworkflow-style_output_v17_10.png) - -![Style Guide Applied](img/duoworkflow_style_code_transform_v17_10.png) - -## Configure the Solutio Prompt and Context Library - -### Basic Setup - -To run the agentic workflow to review and apply style to your application, you need to set up this use case prompt and context library. - -1. **Set up the prompt and contet library** by cloning `Enterprise Code Quality Standards` project -1. **Create a GitLab issue** `Review and Apply Style` with the prompt content from the library file `.gitlab/workflows/java-style-workflow.md` -1. **In the issue** `Review and Apply Style` configure the workflow variables as detailed in the [Configuration section](#configuration-guide) -1. **In your VS code** with the project `Enterprise Code Quality Standards`, start the Duo Workflow with a simple [workflow prompt](#example-duo-workflow-prompt) -1. **Work with the Duo Workflow** by reviewing the proposed plan and automated tasks, if needed add further input to the workflow -1. **Review and commit** the styled code changes to your repository - -### Example Duo Workflow Prompt - -```yaml -Follow the instructions in issue for the file . Make sure to access any issues or GitLab projects mentioned in the issue to retrieve all necessary information. -``` - -This simple prompt is powerful because it instructs Duo Workflow to: - -1. Read the detailed requirements in a specific issue ID -1. Access the referenced style guide repository -1. Apply the guidelines to the specified file -1. Follow all instructions in the issue - -## Configuration Guide - -The prompt is defined in the `.gitlab/workflows/java-style-workflow.md` file in the solution package. This file serves as your template for creating GitLab issues that instruct the workflow agent to build out the plan to automate the style guide review on your application and apply the changes. - -In the first section of `.gitlab/workflows/java-style-workflow.md`, it defines variables you need to configure for the prompt. - -### Variable Definition - -The variables are defined directly in the `.gitlab/workflows/java-style-workflow.md` file. This file serves as your template for creating GitLab issues that instruct the AI assistant. You'll modify the variables in this file before creating a new issue with its contents. - -#### 1. Style Guide Repository as the Context - -The prompt must be configured to point to your organization's style guide repository. In the `java-style-prompt.md` file, replace the following variables: - -- `{{GITLAB_INSTANCE}}`: Your GitLab instance URL (e.g., `https://gitlab.example.com`) -- `{{STYLE_GUIDE_PROJECT_ID}}`: The GitLab project ID containing your Java style guide -- `{{STYLE_GUIDE_PROJECT_NAME}}`: The display name for your style guide project -- `{{STYLE_GUIDE_BRANCH}}`: The branch containing the most up-to-date style guide (default: main) -- `{{STYLE_GUIDE_PATH}}`: The path to the style guide document within the repository - -Example: - -```yaml -GITLAB_INSTANCE=https://gitlab.example.com -STYLE_GUIDE_PROJECT_ID=gl-demo-ultimate-zhenderson/sandbox/enterprise-java-standards -STYLE_GUIDE_PROJECT_NAME=Enterprise Java Standards -STYLE_GUIDE_BRANCH=main -STYLE_GUIDE_PATH=coding-style/java/guidelines/java-coding-standards.md -``` - -#### 2. Target Repository to Apply Style Improvement - -In the same `java-style-prompt.md` file, configure which files to apply the style guide to: - -- `{{TARGET_PROJECT_ID}}`: Your Java project's GitLab ID -- `{{TARGET_FILES}}`: Specific files or patterns to target (e.g., "src/main/java/**/*.java") - -Example: - -```yaml -TARGET_PROJECT_ID=royal-reserve-bank -TARGET_FILES=asset-management-api/src/main/java/com/royal/reserve/bank/asset/management/api/service/AssetManagementService.java -``` - -### Important Notes About AI-Generated Code - -**⚠️ Important Disclaimer**: - -GitLab Workflow uses Agentic AI which is non-deterministic, meaning: - -- Results may vary between runs even with identical inputs -- The AI assistant's understanding and application of style guidelines may differ slightly each time -- The examples provided in this documentation are illustrative and your actual results may differ - -**Best Practices for Working with AI-Generated Code Changes**: - -1. **Always review generated code**: Never merge AI-generated changes without thorough human review -1. **Follow proper merge request processes**: Use your standard code review procedures -1. **Run all tests**: Ensure all unit and integration tests pass before merging -1. **Verify style compliance**: Confirm the changes align with your style guide expectations -1. **Incremental application**: Consider applying style changes to smaller sets of files initially - -Remember that this tool is meant to assist developers, not replace human judgment in the code review process. - -## Step-by-Step Implementation - -1. **Create a Style Guide Issue** - - - Create a new issue in your project (e.g., Issue #3) - - Include detailed information about the style guidelines to apply - - Reference the external style guide repository if applicable - - Specify requirements like: - - ```yaml - Task: Code Style Update - Description: Apply the enterprise standard Java style guidelines to the codebase. - Reference Style Guide: Enterprise Java Style Guidelines (https://gitlab.com/gl-demo-ultimate-zhenderson/sandbox/enterprise-java-standards/-/blob/main/coding-style/java/guidelines/java-coding-standards.md) - Constraints: - - Adhere to Enterprise Standard Java Style Guide - - Maintain Functionality - - Implement automated style checks - ``` - -1. **Configure the Prompt** - - - Copy the template from `java-style-prompt.md` - - Fill in all configuration variables - - Add any project-specific exceptions or requirements - -1. **Execute via GitLab Workflow** - - - Submit the configured prompt to Duo Workflow - - Duo Workflow will run through a multi-step process as seen in the sample workflow execution: - - - Plan the task with specific tools (`run_read_only_git_command`, `read_file`, `find_files`, `edit_file`) - - Access the referenced issue - - Retrieve the enterprise Java style guide - - Analyze the current code structure - - Apply the style guidelines to the specified file(s) - - Verify the changes maintain functionality - - Provide a detailed report of changes made - -1. **Review and Implement** - - - Review the suggested changes - - Implement the changes in your codebase - - Run tests to ensure functionality is preserved - - Monitor the task progression through the GitLab Workflow interface - -## Sample Workflow Execution - -When properly configured, the GitLab Workflow will execute a detailed plan for applying the style guidelines. Here's a sample of what the workflow execution looks like: - -### Sample Workflow Plan - -The AI assistant first creates an execution plan with specific tools: - -1. Access the Java style guidelines by retrieving content from Enterprise Java Standards project using `run_read_only_git_command` on `https://gitlab.com/gl-demo-ultimate-zhenderson/sandbox/enterprise-java-standards.git` for file `coding-style/java/guidelines/java-coding-standards.md`, supported by: `run_read_only_git_command` -1. Read and analyze the current content of `AssetManagementService.java` using the `read_file` tool to understand its structure and identify areas that need style updates, supported by `read_file` -1. Search for any similar files in the project using find_files to ensure consistent style application across related service classes, supported by `find_files` -1. Edit AssetManagementService.java using the edit_file tool to apply the style guidelines, focusing on: - - - Package and import organization - - Class and method documentation - - Code indentation and spacing - - Annotation placement - - Method parameter formatting, supported by `edit_file` - -1. Verify all Lombok annotations (@Service, @RequiredArgsConstructor, @Slf4j) are properly formatted according to the style guide using grep_files to check annotation patterns across the codebase, supported by `grep_files` - -### Sample Analysis Output - -The assistant then analyzes the files and requirements: - -I've analyzed issue #3 and the `AssetManagementService.java` file. Here's what I found: - -1. Issue #3 is about applying enterprise standard Java style guidelines to the Royal Banking codebase. -1. The `AssetManagementService.java` file is located in the `asset-management-api` module. -1. The style guidelines are stored in a separate GitLab project at `gl-demo-ultimate-zhenderson/sandbox/enterprise-java-standards`. -1. The current `AssetManagementService.java` file contains: - - 1. Package and import statements - 1. Service class with Spring annotations - 1. One public method for checking asset availability - 1. Lombok annotations - 1. Javadoc comments - -The issue requires: - -1. Formatting according to enterprise standard Java style guidelines -1. Implementation of automated style checks -1. Maintaining existing functionality -1. Ensuring all tests pass after style updates - -To proceed with the implementation, you'll need to: - -1. Access the Java style guidelines from the Enterprise Java Standards project -1. Apply the style guidelines to AssetManagementService.java -1. Verify that the changes don't affect functionality -1. Ensure all tests pass after the updates - -### Sample Implementation Result - -After execution, the assistant provides the transformed code with explanations of the changes made, including: - -1. A summary of style guidelines applied -1. The transformed code with proper formatting -1. Explanations for key style changes -1. Recommendations for automated style enforcement - -The changes typically include: - -- Standardized import ordering -- Consistent spacing around operators -- Proper Javadoc formatting -- Standardized method parameter alignment -- Consistent brace placement - -All while ensuring the code maintains its original functionality. - -## Sample Tutorial Use Case: Royal Reserve Bank Java Project - -This repository includes a banking tutorial example to demonstrate how the style guide application works in a real-world scenario. The Royal Reserve Bank project follows a microservices architecture with multiple Java services: - -- Account API -- Asset Management API -- Transaction API -- Notification API -- API Gateway -- Config Server -- Discovery Server - -The sample examples applies enterprise style guidelines to the `AssetManagementService.java` class, demonstrating proper formatting for: - -1. Import organization -1. Javadoc standards -1. Method parameter alignment -1. Variable naming conventions -1. Exception handling patterns - -## Customizing for Your Organization - -To adapt this prompt for your organization's needs: - -1. **Style Guide Replacement** - - - Point to your organization's style guide repository - - Reference your specific style guide document - -1. **Target File Selection** - - - Choose specific files or patterns to apply the style guide to - - Prioritize high-visibility code files for initial implementation - -1. **Additional Validation** - - - Add custom validation requirements - - Specify any exceptions to the standard style rules - -1. **Integration with CI/CD** - - - Configure the prompt to run as part of your CI/CD pipeline - - Set up automated style checks to ensure ongoing compliance - -## Troubleshooting - -Common issues and their solutions: - -- **Access Denied**: Ensure the AI agent has proper permissions to access both repositories -- **Missing Style Guide**: Verify the style guide path and branch are correct -- **Functionality Changes**: Run all tests after applying style changes to verify functionality - -## Contributing - -Feel free to enhance this prompt by: - -- Adding more style rule explanations -- Creating examples for different Java project types -- Improving the validation workflow -- Adding integration with additional static analysis tools + + + + diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md index a7ff6e8f85a..558f1e04c42 100644 --- a/doc/topics/autodevops/stages.md +++ b/doc/topics/autodevops/stages.md @@ -243,7 +243,7 @@ For more information, see ## Auto Review Apps -This is an optional step, since many projects don't have a Kubernetes cluster +This is an optional step because many projects don't have a Kubernetes cluster available. If the [requirements](requirements.md) are not met, the job is silently skipped. diff --git a/doc/user/application_security/api_security_testing/checks/json_hijacking_check.md b/doc/user/application_security/api_security_testing/checks/json_hijacking_check.md index 46a6512e33f..25630434453 100644 --- a/doc/user/application_security/api_security_testing/checks/json_hijacking_check.md +++ b/doc/user/application_security/api_security_testing/checks/json_hijacking_check.md @@ -11,7 +11,7 @@ Checks for JSON data potentially vulnerable to hijacking. This check looks for a ## Remediation -JSON hijacking allows an attacker to send a GET request via a malicious web site or similar attack vector and utilize a user's stored credentials to retrieve sensitive or protected data to which that user has access. Since a JSON array on its own is valid JavaScript, a malicious GET request to a resource that returns only a JavaScript array can allow the attacker to use a malicious script to read the data in the array from the request. GET requests should never return a JSON array, even if the resource requires authentication to access. Consider using POST instead of a GET for this request or wrapping the array in a JSON object. +JSON hijacking allows an attacker to send a GET request via a malicious web site or similar attack vector and utilize a user's stored credentials to retrieve sensitive or protected data to which that user has access. A JSON array on its own is valid JavaScript, so a malicious GET request to a resource that returns only a JavaScript array can allow the attacker to use a malicious script to read the data in the array from the request. GET requests should never return a JSON array, even if the resource requires authentication to access. Consider using POST instead of a GET for this request or wrapping the array in a JSON object. ## Links diff --git a/doc/user/application_security/dast/browser/checks/319.1.md b/doc/user/application_security/dast/browser/checks/319.1.md index 646f1bbb7fd..519f0d1ee31 100644 --- a/doc/user/application_security/dast/browser/checks/319.1.md +++ b/doc/user/application_security/dast/browser/checks/319.1.md @@ -11,7 +11,7 @@ The target application was found to request resources over insecure transport pr elements which load resources using the `http://` scheme instead of `https://`. It should be noted that most modern browsers block these requests automatically so there is limited risk. -Some parts of the application may not behave correctly since these files are not being properly loaded. +Some parts of the application may not behave correctly because these files are not being properly loaded. ## Remediation diff --git a/doc/user/gitlab_duo/_index.md b/doc/user/gitlab_duo/_index.md index ea16171160c..dcb49230799 100644 --- a/doc/user/gitlab_duo/_index.md +++ b/doc/user/gitlab_duo/_index.md @@ -131,11 +131,17 @@ They require a Premium or Ultimate subscription and one of the available add-ons | [Vulnerability Resolution](../application_security/vulnerabilities/_index.md#vulnerability-resolution) | {{< icon name="dash-circle" >}} No | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes | | [AI Impact Dashboard](../analytics/ai_impact_analytics.md) | {{< icon name="dash-circle" >}} No | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes | -In addition: +### Features available in GitLab Duo Self-Hosted -- All GitLab Duo Core and Pro features include generally available support for - [GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/_index.md). -- All GitLab Duo Enterprise-only features include beta support for GitLab Duo Self-Hosted. +Your organization can use [GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/_index.md) +to self-host the AI gateway and language models if you: + +- Have the GitLab Duo Enterprise add-on. +- Are a GitLab Self-Managed customer. + +To check which GitLab Duo features are available for use with GitLab Duo Self-Hosted, +and the status of those features, see the +[supported GitLab Duo features for GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/_index.md#supported-gitlab-duo-features). ### Beta and experimental features diff --git a/doc/user/group/_index.md b/doc/user/group/_index.md index 8047d37136e..9225d375742 100644 --- a/doc/user/group/_index.md +++ b/doc/user/group/_index.md @@ -413,8 +413,8 @@ Prerequisites: - You must have the Owner role for the group. - For GitLab Self-Managed instances: - - If [New sign-ups are disabled](../../administration/settings/sign_up_restrictions.md#disable-new-sign-ups), an administrator must add the user. - - If [User invitations are disabled](../../administration/settings/visibility_and_access_controls.md#disable-user-invitations), an administrator must add the user. + - If [new sign-ups are disabled](../../administration/settings/sign_up_restrictions.md#disable-new-sign-ups), an administrator must add the user. + - If [user invitations are not allowed](../../administration/settings/visibility_and_access_controls.md#prevent-invitations-to-groups-and-projects), an administrator must add the user. - If [administrator approval is enabled](../../administration/settings/sign_up_restrictions.md#turn-on-administrator-approval-for-role-promotions), an administrator must approve the invitation. 1. On the left sidebar, select **Search or go to** and find your group. diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index 5e87ed2e24d..5843d9cfc17 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -250,7 +250,7 @@ To disable group mentions: 1. Select **Group mentions are disabled**. 1. Select **Save changes**. -## Disable user invitations to a group +## Prevent invitations to a group {{< history >}} @@ -258,15 +258,17 @@ To disable group mentions: {{< /history >}} -You can disable the ability for users to invite new members to sub-groups or projects in a top-level +You can remove the ability for users to invite new members to subgroups or projects in a top-level group. This also stops group Owners from sending invites. You must disable this setting before you can invite users again. +On GitLab Self-Managed and GitLab Dedicated instances, you can prevent user invitations for the entire instance. For more information, see [prevent invitations to a groups and projects](../../administration/settings/visibility_and_access_controls.md#prevent-invitations-to-groups-and-projects). + Prerequisites: - You must have the Owner role for the group. -To disable user invitations: +To prevent invitations to a group: 1. On the left sidebar, select **Search or go to** and find your group. 1. Select **Settings > General**. diff --git a/doc/user/project/deploy_boards.md b/doc/user/project/deploy_boards.md index 7151e12ce14..cc863949c0f 100644 --- a/doc/user/project/deploy_boards.md +++ b/doc/user/project/deploy_boards.md @@ -61,8 +61,7 @@ the given environment. Hovering above each square you can see the state of a deploy rolling out. The percentage is the percent of the pods that are updated to the latest release. -Since deploy boards are tightly coupled with Kubernetes, there is some required -knowledge. In particular, you should be familiar with: +Deploy boards are tightly coupled with Kubernetes, so you should be familiar with: - [Kubernetes pods](https://kubernetes.io/docs/concepts/workloads/pods/) - [Kubernetes labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) @@ -71,22 +70,21 @@ knowledge. In particular, you should be familiar with: ## Use cases -Since the deploy board is a visual representation of the Kubernetes pods for a -specific environment, there are a lot of use cases. To name a few: +The deploy board is a visual representation of the Kubernetes pods for a +specific environment, so there are a lot of use cases. To name a few: -- You want to promote what's running in staging, to production. You go to the - environments list, verify that what's running in staging is what you think is - running, then select the [manual job](../../ci/jobs/job_control.md#create-a-job-that-must-be-run-manually) to deploy to production. -- You trigger a deploy, and you have many containers to upgrade so you know +- You want to promote what's running in staging to production. So you go to the + environments list, verify that what's running in staging is what you think it is, then select the [manual job](../../ci/jobs/job_control.md#create-a-job-that-must-be-run-manually) to deploy to production. +- You triggered a deploy, and you have many containers to upgrade, so you know this takes a while (you've also throttled your deploy to only take down X containers at a time). But you need to tell someone when it's deployed, so you - go to the environments list, look at the production environment to see what + go to the environments list and look at the production environment to see what the progress is in real-time as each pod is rolled. - You get a report that something is weird in production, so you look at the - production environment to see what is running, and if a deploy is ongoing or - stuck or failed. + production environment to see what is running, and if a deploy is ongoing, + stuck, or failed. - You've got an MR that looks good, but you want to run it on staging because - staging is set up in some way closer to production. You go to the environment + staging is set up in some way closer to production. So you go to the environment list, find the [Review App](../../ci/review_apps/_index.md) you're interested in, and select the manual action to deploy it to staging. diff --git a/doc/user/project/import/cvs.md b/doc/user/project/import/cvs.md index c61ff3c8ac2..0de5513e0a5 100644 --- a/doc/user/project/import/cvs.md +++ b/doc/user/project/import/cvs.md @@ -19,11 +19,10 @@ control system similar to [SVN](https://subversion.apache.org/). The following list illustrates the main differences between CVS and Git: -- **Git is distributed.** On the other hand, CVS is centralized using a client-server - architecture. This translates to Git having a more flexible workflow since +- **Git is distributed.** On the other hand, CVS is centralized and uses a client-server + architecture. This translates to Git having a more flexible workflow because your working area is a copy of the entire repository. This decreases the - overhead when switching branches or merging for example, since you don't have - to communicate with a remote server. + overhead when switching branches or merging, for example, because you don't need to communicate with a remote server. - **Atomic operations.** In Git all operations are [atomic](https://en.wikipedia.org/wiki/Atomic_commit), either they succeed as whole, or they fail without any changes. In CVS, commits (and other operations) diff --git a/doc/user/project/import/tfvc.md b/doc/user/project/import/tfvc.md index 4f573ea7712..c63ede689f2 100644 --- a/doc/user/project/import/tfvc.md +++ b/doc/user/project/import/tfvc.md @@ -24,7 +24,7 @@ In this document, we focus on the TFVC to Git migration. The main differences between TFVC and Git are: - **Git is distributed:** While TFVC is centralized using a client-server architecture, - Git is distributed. This translates to Git having a more flexible workflow since + Git is distributed. This translates to Git having a more flexible workflow because you work with a copy of the entire repository. This allows you to quickly switch branches or merge, for example, without needing to communicate with a remote server. - **Storage:** Changes in a centralized version control system are per file (changeset), diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md index 5d2dcb0bd9c..3f69013ec98 100644 --- a/doc/user/project/issues/crosslinking_issues.md +++ b/doc/user/project/issues/crosslinking_issues.md @@ -41,7 +41,7 @@ add `#xxx` to the commit message, where `xxx` is the issue number. git commit -m "this is my commit message. Ref #xxx" ``` -Since commit messages cannot usually begin with a `#` character, you may use +Commit messages cannot usually begin with a `#` character, so you may use the alternative `GL-xxx` notation as well: ```shell diff --git a/doc/user/project/members/_index.md b/doc/user/project/members/_index.md index 690fb5b37c8..82af096b4fd 100644 --- a/doc/user/project/members/_index.md +++ b/doc/user/project/members/_index.md @@ -95,8 +95,8 @@ Prerequisites: - You must have the Owner or Maintainer role. - [Group membership lock](../../group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group) must be disabled. - For GitLab Self-Managed instances: - - If [user sign-ups are disabled](../../../administration/settings/sign_up_restrictions.md#disable-new-sign-ups), an administrator must add the user. - - If [user invitations are disabled](../../../administration/settings/visibility_and_access_controls.md#disable-user-invitations), an administrator must add the user. + - If [new sign-ups are disabled](../../../administration/settings/sign_up_restrictions.md#disable-new-sign-ups), an administrator must add the user. + - If [user invitations are not allowed](../../../administration/settings/visibility_and_access_controls.md#prevent-invitations-to-groups-and-projects), an administrator must add the user. - If [administrator approval is enabled](../../../administration/settings/sign_up_restrictions.md#turn-on-administrator-approval-for-role-promotions), an administrator must approve the invitation. To add a user to a project: diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md index af6b3a70b14..05efe7bd76f 100644 --- a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md +++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md @@ -44,7 +44,7 @@ Now we have a different picture. [According to Josh Aas](https://letsencrypt.org -> _We've since come to realize that HTTPS is important for almost all websites. It's important for any website that allows people to sign in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn't want its content altered](https://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We've also learned that any site not secured by HTTPS [can be used to attack other sites](https://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._ +> _We've come to realize that HTTPS is important for almost all websites. It's important for any website that allows people to sign in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn't want its content altered](https://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We've also learned that any site not secured by HTTPS [can be used to attack other sites](https://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._ Therefore, the reason why certificates are so important is that they encrypt the connection between the **client** (you, your visitors) diff --git a/lib/gitlab/ci/parsers/sbom/component.rb b/lib/gitlab/ci/parsers/sbom/component.rb index 4e1a8312f4b..8d0d52c946c 100644 --- a/lib/gitlab/ci/parsers/sbom/component.rb +++ b/lib/gitlab/ci/parsers/sbom/component.rb @@ -57,7 +57,7 @@ module Gitlab def licenses data.fetch('licenses', []).filter_map do |license_data| - license = License.new(license_data).parse + license = License::Common.parse(license_data, container_scanning_component?) next unless license license diff --git a/lib/gitlab/ci/parsers/sbom/license.rb b/lib/gitlab/ci/parsers/sbom/license.rb deleted file mode 100644 index 46f292f87ca..00000000000 --- a/lib/gitlab/ci/parsers/sbom/license.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - module Parsers - module Sbom - class License - def initialize(data) - @data = data - end - - def parse - license = data['license'] - return unless license - - # A license must have either id or name - return unless license['id'].present? || license['name'].present? - - ::Gitlab::Ci::Reports::Sbom::License.new( - spdx_identifier: license['id'], - name: license['name'], - url: license['url'] - ) - end - - private - - attr_reader :data - end - end - end - end -end diff --git a/lib/gitlab/ci/parsers/sbom/license/common.rb b/lib/gitlab/ci/parsers/sbom/license/common.rb new file mode 100644 index 00000000000..0fc4a6c22f7 --- /dev/null +++ b/lib/gitlab/ci/parsers/sbom/license/common.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Parsers + module Sbom + module License + class Common + def self.parse(data, is_container_scanning) + license = is_container_scanning ? ContainerScanning.new(data) : new(data) + license.parse + end + + def initialize(data) + @data = data + end + + def parse + license = data['license'] + return unless license + + # A license must have either id or name + return unless license['id'].present? || license['name'].present? + + parsed_license(license) + end + + private + + attr_reader :data + + def parsed_license(license) + ::Gitlab::Ci::Reports::Sbom::License.new( + spdx_identifier: license['id'], + name: license['name'], + url: license['url'] + ) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/parsers/sbom/license/container_scanning.rb b/lib/gitlab/ci/parsers/sbom/license/container_scanning.rb new file mode 100644 index 00000000000..17d5d5d6083 --- /dev/null +++ b/lib/gitlab/ci/parsers/sbom/license/container_scanning.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Parsers + module Sbom + module License + class ContainerScanning < Common + private + + def parsed_license(license) + ::Gitlab::Ci::Reports::Sbom::License.new( + spdx_identifier: license['name'], + name: nil, + url: nil + ) + end + end + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb index f0e0713552b..3341267dd4e 100644 --- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb +++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb @@ -228,7 +228,7 @@ module Gitlab current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) end parse_params do |raw_time_date| - Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute + Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date, current_user.timezone).execute end command :spend, :spent, :spend_time do |time_spent, time_spent_date, category| if time_spent diff --git a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb index d8f0a2a2064..a10ac3fd8a0 100644 --- a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb +++ b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb @@ -14,8 +14,9 @@ module Gitlab DATE_REGEX = %r{(\d{2,4}[/\-.]\d{1,2}[/\-.]\d{1,2})} CATEGORY_REGEX = %r{\[timecategory:(.*)\]} - def initialize(spend_command_arg) + def initialize(spend_command_arg, timezone) @spend_arg = spend_command_arg + @timezone = timezone || Time.zone.name end def execute @@ -38,7 +39,9 @@ module Gitlab return DateTime.current unless date_present? string_date = @spend_arg.match(DATE_REGEX)[0] - Date.parse(string_date).midday + date = Date.parse(string_date) + date = date.in_time_zone(@timezone) + date.midday end def date_present? diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake index 8ac5e2fdc95..a1c1add3d23 100644 --- a/lib/tasks/gitlab/tw/codeowners.rake +++ b/lib/tasks/gitlab/tw/codeowners.rake @@ -34,7 +34,7 @@ namespace :tw do CodeOwnerRule.new('Code Review', '@aqualls'), CodeOwnerRule.new('Compliance', '@eread'), CodeOwnerRule.new('Composition Analysis', '@rdickenson'), - CodeOwnerRule.new('Container Registry', '@lyspin'), + CodeOwnerRule.new('Container Registry', '@z_painter'), CodeOwnerRule.new('Contributor Experience', '@eread'), CodeOwnerRule.new('Custom Models', '@jglassman1'), # CodeOwnerRule.new('Database Frameworks', ''), @@ -85,7 +85,7 @@ namespace :tw do CodeOwnerRule.new('Source Code', '@brendan777'), CodeOwnerRule.new('Static Analysis', '@rdickenson'), # CodeOwnerRule.new('Subscription Management', ''), - CodeOwnerRule.new('Switchboard', '@emily.sahlani'), + CodeOwnerRule.new('Switchboard', '@lyspin'), CodeOwnerRule.new('Testing', '@eread'), CodeOwnerRule.new('Tutorials', '@gl-docsteam'), # CodeOwnerRule.new('US Public Sector Services', ''), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 989c35de773..a32d7948bb0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -47128,6 +47128,9 @@ msgstr "" msgid "Profiles|Private contributions" msgstr "" +msgid "Profiles|Private profile" +msgstr "" + msgid "Profiles|Profile was successfully updated" msgstr "" @@ -47224,6 +47227,9 @@ msgstr "" msgid "Profiles|Time settings" msgstr "" +msgid "Profiles|Timezone" +msgstr "" + msgid "Profiles|Title" msgstr "" @@ -47329,6 +47335,9 @@ msgstr "" msgid "Profiles|username" msgstr "" +msgid "Profiles|what information is hidden?" +msgstr "" + msgid "Profiles|your account" msgstr "" @@ -61991,7 +62000,7 @@ msgstr "" msgid "Third Party Advisory Link" msgstr "" -msgid "This %{context} has been scheduled for deletion on %{strongStart}%{date}%{strongEnd}. To cancel the scheduled deletion, you can restore this %{context}, including all its resources." +msgid "This %{context} has been scheduled for deletion on %{date}. To cancel the scheduled deletion, you can restore this %{context}, including all its resources." msgstr "" msgid "This %{issuableType} is confidential and should only be visible to team members with at least the Planner role." @@ -62033,7 +62042,7 @@ msgstr "" msgid "This action will permanently delete this project, including all its resources." msgstr "" -msgid "This action will place this group, including its subgroups and projects, in a pending deletion state for %{deletion_delayed_period} days, and delete it permanently on %{date}." +msgid "This action will place this group, including its subgroups and projects, in a pending deletion state for %{deletion_adjourned_period} days, and delete it permanently on %{date}." msgstr "" msgid "This action will place this project, including all its resources, in a pending deletion state for %{deletion_adjourned_period} days, and delete it permanently on %{date}." @@ -62261,7 +62270,7 @@ msgstr "" msgid "This group is not permitted to create compliance violations" msgstr "" -msgid "This group is scheduled for deletion on %{date}. This action will permanently delete this group, including its subgroups and projects, %{strong_open}immediately%{strong_close}. This action cannot be undone." +msgid "This group is scheduled for deletion on %{date}. This action will permanently delete this group, including its subgroups and projects, %{strongOpen}immediately%{strongClose}. This action cannot be undone." msgstr "" msgid "This group is scheduled to be deleted on %{date}. You are about to delete this group, including its subgroups and projects, immediately. This action cannot be undone." diff --git a/qa/Gemfile b/qa/Gemfile index 4946f4df1e2..54ee8c182ee 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'gitlab-qa', '~> 15', '>= 15.4.0', require: 'gitlab/qa' +gem 'gitlab-qa', '~> 15', '>= 15.5.0', require: 'gitlab/qa' gem 'gitlab_quality-test_tooling', '~> 2.10.0', require: false gem 'gitlab-utils', path: '../gems/gitlab-utils' gem 'activesupport', '~> 7.1.5.1' # This should stay in sync with the root's Gemfile diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 61b24ecee72..b94de42ff39 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -129,7 +129,7 @@ GEM gitlab (4.19.0) httparty (~> 0.20) terminal-table (>= 1.5.1) - gitlab-qa (15.4.0) + gitlab-qa (15.5.0) activesupport (>= 6.1, < 7.2) ffi (~> 1.17) gitlab (~> 4.19) @@ -380,7 +380,7 @@ DEPENDENCIES fog-core (= 2.1.0) fog-google (~> 1.24, >= 1.24.1) gitlab-orchestrator! - gitlab-qa (~> 15, >= 15.4.0) + gitlab-qa (~> 15, >= 15.5.0) gitlab-utils! gitlab_quality-test_tooling (~> 2.10.0) googleauth (~> 1.9.0) diff --git a/rubocop/cop/.rubocop.yml b/rubocop/cop/.rubocop.yml index 47061356f05..e9ed9c2f5f0 100644 --- a/rubocop/cop/.rubocop.yml +++ b/rubocop/cop/.rubocop.yml @@ -1,3 +1,8 @@ --- inherit_from: - '../../.rubocop/internal_affairs.yml' + +InternalAffairs/CopDescriptionWithExample: + Enabled: true + Include: + - '**/*.rb' diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 53ec018b122..0d418208df7 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -588,11 +588,10 @@ RSpec.describe GroupsController, :with_current_organization, factory_default: :k it 'returns json with message' do subject - # FIXME: Replace `group.marked_for_deletion_on` with `group` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 expect(json_response['message']) .to eq( "'#{group.name}' has been scheduled for deletion and will be deleted on " \ - "#{permanent_deletion_date_formatted(group.marked_for_deletion_on)}.") + "#{permanent_deletion_date_formatted(group)}.") end end end diff --git a/spec/frontend/profile/edit/components/profile_edit_app_spec.js b/spec/frontend/profile/edit/components/profile_edit_app_spec.js index 05380c67f1c..2fcbe68df41 100644 --- a/spec/frontend/profile/edit/components/profile_edit_app_spec.js +++ b/spec/frontend/profile/edit/components/profile_edit_app_spec.js @@ -9,6 +9,7 @@ import ProfileEditApp from '~/profile/edit/components/profile_edit_app.vue'; import UserAvatar from '~/profile/edit/components/user_avatar.vue'; import SetStatusForm from '~/set_status_modal/set_status_form.vue'; import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue'; +import UserMainSetting from '~/profile/edit/components/user_main_settings.vue'; import { VARIANT_DANGER, VARIANT_INFO, createAlert } from '~/alert'; import { AVAILABILITY_STATUS } from '~/set_status_modal/constants'; import { timeRanges } from '~/vue_shared/constants'; @@ -20,6 +21,22 @@ jest.mock('~/lib/utils/file_utility', () => ({ })); const [oneMinute, oneHour] = timeRanges; + +const userMainSettings = { + id: 'user123', + name: 'Test User', + pronouns: 'they/them', + pronunciation: 'test-user', + websiteUrl: 'https://example.com', + location: 'Remote', + jobTitle: 'Developer', + organization: 'GitLab', + bio: 'Test bio', + privateProfile: false, + includePrivateContributions: false, + achievementsEnabled: true, +}; + const defaultProvide = { currentEmoji: 'basketball', currentMessage: 'Foo bar', @@ -28,6 +45,7 @@ const defaultProvide = { currentClearStatusAfter: oneMinute.shortcut, timezones: mockTimezones, userTimezone: '', + userMainSettings, }; describe('Profile Edit App', () => { @@ -63,6 +81,7 @@ describe('Profile Edit App', () => { const findAvatar = () => wrapper.findComponent(UserAvatar); const findSetStatusForm = () => wrapper.findComponent(SetStatusForm); const findTimezoneDropdown = () => wrapper.findComponent(TimezoneDropdown); + const findMainSetting = () => wrapper.findComponent(UserMainSetting); const submitForm = () => findForm().vm.$emit('submit', new Event('submit')); const setAvatar = () => findAvatar().vm.$emit('blob-change', mockAvatarFile); const setStatus = () => { @@ -107,6 +126,10 @@ describe('Profile Edit App', () => { }); }); + it('renders `UserMainSetting` component and passes correct props', () => { + expect(findMainSetting().props('userSettings')).toEqual(userMainSettings); + }); + describe('when form submit request is successful', () => { it('shows success alert', async () => { mockAxios.onPut(stubbedProfilePath).reply(HTTP_STATUS_OK, { @@ -205,6 +228,45 @@ describe('Profile Edit App', () => { }); }); + describe('when user changes main settings', () => { + it.each` + field | value | formField + ${'name'} | ${'Updated name'} | ${'user[name]'} + ${'pronouns'} | ${'Updated pronouns'} | ${'user[pronouns]'} + ${'pronunciation'} | ${'Updated pronunciation'} | ${'user[pronunciation]'} + ${'websiteUrl'} | ${'https://example.org'} | ${'user[website_url]'} + ${'location'} | ${'New Location'} | ${'user[location]'} + ${'bio'} | ${'Updated bio text'} | ${'user[bio]'} + ${'jobTitle'} | ${'Senior Developer'} | ${'user[job_title]'} + ${'organization'} | ${'New Organization'} | ${'user[organization]'} + `('submits form with updated $field field', async ({ field, value, formField }) => { + const updatedMainSettings = { ...userMainSettings, [field]: value }; + findMainSetting().vm.$emit('change', updatedMainSettings); + + submitForm(); + await waitForPromises(); + + const axiosRequestData = mockAxios.history.put[0].data; + expect(axiosRequestData.get(formField)).toEqual(value); + }); + + it.each` + field | value | formField + ${'privateProfile'} | ${true} | ${'user[private_profile]'} + ${'includePrivateContributions'} | ${true} | ${'user[include_private_contributions]'} + ${'achievementsEnabled'} | ${false} | ${'user[achievements_enabled]'} + `('submits form with $field toggled', async ({ field, value, formField }) => { + const updatedMainSettings = { ...userMainSettings, [field]: value }; + findMainSetting().vm.$emit('change', updatedMainSettings); + + submitForm(); + await waitForPromises(); + + const axiosRequestData = mockAxios.history.put[0].data; + expect(axiosRequestData.get(formField)).toEqual(String(value)); + }); + }); + it('submits API request with avatar file', async () => { mockAxios.onPut(stubbedProfilePath).reply(HTTP_STATUS_OK); diff --git a/spec/frontend/profile/edit/components/user_main_settings_spec.js b/spec/frontend/profile/edit/components/user_main_settings_spec.js new file mode 100644 index 00000000000..313cdfb7f88 --- /dev/null +++ b/spec/frontend/profile/edit/components/user_main_settings_spec.js @@ -0,0 +1,173 @@ +import { GlFormCheckbox, GlFormInput, GlFormTextarea, GlFormGroup, GlLink } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { stubComponent } from 'helpers/stub_component'; +import UserMainSettings from '~/profile/edit/components/user_main_settings.vue'; + +jest.mock('~/helpers/help_page_helper'); + +const mockPrivatePageLink = '/user/profile/_index.md#make-your-user-profile-page-private'; + +const i18n = { + name: 'Name', + nameRequired: 'Using emoji in names seems fun, but please try to set a status message instead', + nameDescription: 'Enter your name', + userId: 'User ID', + pronouns: 'Pronouns', + pronunciation: 'Name pronunciation', + websiteUrl: 'Website URL', + location: 'Location', + jobTitle: 'Job title', + organization: 'Organization', + bio: 'Bio', + privateProfile: 'Private profile', + privateProfileLabel: 'Make profile private', + privateProfileLink: 'what information is hidden?', + privateContributions: 'Private contributions', + privateContributionsLabel: 'Include private contributions', + achievements: 'Achievements', + achievementsLabel: 'Show achievements', + fullNameDescription: 'Enter your name, so people you know can recognize you.', + fullNameSafeDescription: 'No "<" or ">" characters, please.', +}; + +describe('MainSetting', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(UserMainSettings, { + propsData: { + userSettings: { + id: '123', + name: '', + ...props, + }, + }, + provide: { + i18n, + }, + stubs: { + GlFormGroup: stubComponent(GlFormGroup, { + props: ['state', 'invalidFeedback', 'description'], + }), + GlLink: stubComponent(GlLink, { + template: ``, + setup() { + return { mockPrivatePageLink }; + }, + }), + }, + }); + }; + + describe('user interactions', () => { + beforeEach(() => { + createComponent(); + }); + + it.each` + testId | name + ${'full-name-group'} | ${'Full Name'} + ${'user-id-group'} | ${'User ID'} + ${'pronouns-group'} | ${'Pronouns'} + ${'pronunciation-group'} | ${'Pronunciation'} + ${'website-url-group'} | ${'Website URL'} + ${'location-group'} | ${'Location'} + ${'job-title-group'} | ${'Job Title'} + ${'organization-group'} | ${'Organization'} + ${'bio-group'} | ${'Bio'} + ${'private-profile-group'} | ${'Private Profile'} + ${'private-contributions-group'} | ${'Private Contributions'} + ${'achievements-group'} | ${'Achievements'} + `('displays the $name field', ({ testId }) => { + expect(wrapper.findByTestId(testId).exists()).toBe(true); + }); + + it('displays help link for private profile with correct text', () => { + const link = wrapper.findByTestId('private-profile-link'); + expect(link.text()).toBe(i18n.privateProfileLink); + expect(link.attributes('href')).toBe(mockPrivatePageLink); + }); + }); + + describe('form validation', () => { + it('shows error message when name is empty', () => { + createComponent({ name: '' }); + + const nameGroup = wrapper.findByTestId('full-name-group'); + expect(nameGroup.props('state')).toBe(false); + expect(nameGroup.props('invalidFeedback')).toBe(i18n.nameRequired); + }); + + it('shows no error when name is provided', () => { + createComponent({ name: 'John Doe' }); + + const nameGroup = wrapper.findByTestId('full-name-group'); + const description = `${i18n.fullNameDescription} ${i18n.fullNameSafeDescription}`; + expect(nameGroup.props('state')).toBe(true); + expect(nameGroup.props('description')).toBe(description); + }); + }); + + describe('form interaction', () => { + beforeEach(() => { + createComponent(); + }); + + it.each` + testId | inputValue | formProperty | componentType + ${'full-name-group'} | ${'John Doe'} | ${'name'} | ${GlFormInput} + ${'pronouns-group'} | ${'they/them'} | ${'pronouns'} | ${GlFormInput} + ${'pronunciation-group'} | ${'jawn-doe'} | ${'pronunciation'} | ${GlFormInput} + ${'website-url-group'} | ${'https://example.com'} | ${'websiteUrl'} | ${GlFormInput} + ${'location-group'} | ${'San Francisco, CA'} | ${'location'} | ${GlFormInput} + ${'job-title-group'} | ${'Software Engineer'} | ${'jobTitle'} | ${GlFormInput} + ${'organization-group'} | ${'GitLab'} | ${'organization'} | ${GlFormInput} + ${'bio-group'} | ${'This is my bio'} | ${'bio'} | ${GlFormTextarea} + `( + 'updates $formProperty field when user types', + async ({ testId, inputValue, formProperty, componentType }) => { + const input = wrapper.findByTestId(testId).findComponent(componentType); + await input.vm.$emit('input', inputValue); + expect(wrapper.vm.form[formProperty]).toBe(inputValue); + + await wrapper.vm.$emit('change', wrapper.vm.form); + expect(wrapper.emitted()).toHaveProperty('change'); + expect(wrapper.emitted('change')[0][0]).toEqual( + expect.objectContaining({ + [formProperty]: inputValue, + }), + ); + }, + ); + + it.each` + testId | formProperty + ${'private-profile-group'} | ${'privateProfile'} + ${'private-contributions-group'} | ${'includePrivateContributions'} + ${'achievements-group'} | ${'achievementsEnabled'} + `('toggles $formProperty checkbox', async ({ testId, formProperty }) => { + const checkbox = wrapper.findByTestId(testId).findComponent(GlFormCheckbox); + await checkbox.vm.$emit('input', true); + expect(wrapper.vm.form[formProperty]).toBe(true); + + await wrapper.vm.$emit('change', wrapper.vm.form); + expect(wrapper.emitted()).toHaveProperty('change'); + expect(wrapper.emitted('change')[0][0]).toEqual( + expect.objectContaining({ + [formProperty]: true, + }), + ); + }); + }); + + describe('readonly fields', () => { + beforeEach(() => { + createComponent(); + }); + + it('prevents user from editing user ID', () => { + const userIdInput = wrapper.findByTestId('user-id-group').findComponent(GlFormInput); + expect(userIdInput.props('readonly')).toBe(true); + }); + }); +}); diff --git a/spec/frontend/projects/new_v2/components/shared_project_creation_fields_spec.js b/spec/frontend/projects/new_v2/components/shared_project_creation_fields_spec.js index 3ea8a1b4bd7..de339f8cc60 100644 --- a/spec/frontend/projects/new_v2/components/shared_project_creation_fields_spec.js +++ b/spec/frontend/projects/new_v2/components/shared_project_creation_fields_spec.js @@ -1,6 +1,7 @@ import { nextTick } from 'vue'; import { GlFormInput, GlFormSelect } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector.vue'; import SharedProjectCreationFields from '~/projects/new_v2/components/shared_project_creation_fields.vue'; import NewProjectDestinationSelect from '~/projects/new_v2/components/project_destination_select.vue'; import { DEPLOYMENT_TARGET_SELECTIONS } from '~/projects/new_v2/form_constants'; @@ -16,12 +17,15 @@ describe('Project creation form fields component', () => { }, }; - const createComponent = (props = {}) => { + const createComponent = ({ props = {}, provide = {} } = {}) => { wrapper = shallowMountExtended(SharedProjectCreationFields, { propsData: { ...defaultProps, ...props, }, + provide: { + ...provide, + }, stubs: { GlFormInput, GlFormSelect, @@ -29,28 +33,34 @@ describe('Project creation form fields component', () => { }); }; - beforeEach(() => { - createComponent(); - }); - const findProjectNameInput = () => wrapper.findByTestId('project-name-input'); const findProjectSlugInput = () => wrapper.findByTestId('project-slug-input'); const findNamespaceSelect = () => wrapper.findComponent(NewProjectDestinationSelect); const findDeploymentTargetSelect = () => wrapper.findByTestId('deployment-target-select'); const findKubernetesHelpLink = () => wrapper.findByTestId('kubernetes-help-link'); + const findVisibilitySelector = () => wrapper.findComponent(SingleChoiceSelector); + const findPrivateVisibilityLevelOption = () => wrapper.findByTestId('private-visibility-level'); + const findInternalVisibilityLevelOption = () => wrapper.findByTestId('internal-visibility-level'); + const findPublicVisibilityLevelOption = () => wrapper.findByTestId('public-visibility-level'); describe('target select', () => { it('renders the optional deployment target select', () => { + createComponent(); + expect(findDeploymentTargetSelect().exists()).toBe(true); expect(findKubernetesHelpLink().exists()).toBe(false); }); it('has all the options', () => { + createComponent(); + expect(findDeploymentTargetSelect().props('options')).toEqual(DEPLOYMENT_TARGET_SELECTIONS); }); }); it('updates project slug according to a project name', async () => { + createComponent(); + // NOTE: vue3 test needs the .setValue(value) and the vm.$emit('input'), // while the vue2 needs either .setValue(value) or vm.$emit('input', value) const value = 'My Awesome Project 123'; @@ -62,6 +72,8 @@ describe('Project creation form fields component', () => { }); it('emits namespace change', () => { + createComponent(); + findNamespaceSelect().vm.$emit('onSelectNamespace', { id: '2', fullPath: 'group/subgroup', @@ -78,6 +90,8 @@ describe('Project creation form fields component', () => { describe('validation', () => { it('shows an error message when project name is cleared', async () => { + createComponent(); + findProjectNameInput().setValue(''); findProjectNameInput().trigger('blur'); await nextTick(); @@ -87,6 +101,8 @@ describe('Project creation form fields component', () => { }); it('shows an error message when slug is cleared', async () => { + createComponent(); + findProjectSlugInput().setValue(''); findProjectSlugInput().trigger('blur'); await nextTick(); @@ -95,4 +111,52 @@ describe('Project creation form fields component', () => { expect(formGroup.vm.$attrs['invalid-feedback']).toBe('Please enter project slug.'); }); }); + + describe('visibility selector', () => { + it('renders all levels when there are no restictions and parent is public', async () => { + createComponent(); + await nextTick(); + + expect(findPrivateVisibilityLevelOption().props('disabled')).toBe(false); + expect(findInternalVisibilityLevelOption().props('disabled')).toBe(false); + expect(findPublicVisibilityLevelOption().props('disabled')).toBe(false); + }); + + it('renders internal visibility level as disabled when it was rescticted by admin', async () => { + createComponent({ + provide: { restrictedVisibilityLevels: [10] }, + }); + await nextTick(); + + expect(findPrivateVisibilityLevelOption().props('disabled')).toBe(false); + expect(findInternalVisibilityLevelOption().props('disabled')).toBe(true); + expect(findPublicVisibilityLevelOption().props('disabled')).toBe(false); + }); + + it('renders public and internal visibility levels as disabled when parent is private', async () => { + createComponent({ + props: { + namespace: { + id: '1', + fullPath: 'root', + isPersonal: false, + visibility: 'private', + }, + }, + }); + await nextTick(); + + expect(findPrivateVisibilityLevelOption().props('disabled')).toBe(false); + expect(findInternalVisibilityLevelOption().props('disabled')).toBe(true); + expect(findPublicVisibilityLevelOption().props('disabled')).toBe(true); + }); + + it('renders internal visibility level as default when admin set it up', () => { + createComponent({ + provide: { defaultProjectVisibility: 10 }, + }); + + expect(findVisibilitySelector().props('checked')).toBe('internal'); + }); + }); }); diff --git a/spec/frontend/todos/components/todos_pagination_spec.js b/spec/frontend/todos/components/todos_pagination_spec.js index aa4cf21eba9..57c54481faf 100644 --- a/spec/frontend/todos/components/todos_pagination_spec.js +++ b/spec/frontend/todos/components/todos_pagination_spec.js @@ -1,13 +1,18 @@ import { shallowMount } from '@vue/test-utils'; import { GlKeysetPagination } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import TodosPagination, { CURSOR_CHANGED_EVENT } from '~/todos/components/todos_pagination.vue'; import PageSizeSelector from '~/vue_shared/components/page_size_selector.vue'; import { DEFAULT_PAGE_SIZE } from '~/todos/constants'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; describe('TodosPagination', () => { let wrapper; + useLocalStorageSpy(); + const createComponent = (props = {}) => { wrapper = shallowMount(TodosPagination, { propsData: { @@ -17,6 +22,9 @@ describe('TodosPagination', () => { endCursor: '', ...props, }, + stubs: { + LocalStorageSync, + }, }); }; @@ -149,4 +157,24 @@ describe('TodosPagination', () => { }, ]); }); + + it('syncs the page size to local storage', async () => { + const pageSizeSelector = findPageSizeSelector(); + const newSize = 50; + + pageSizeSelector.vm.$emit('input', newSize); + await nextTick(); + + expect(localStorage.setItem).toHaveBeenCalledWith('todos-page-size', newSize.toString()); + }); + + it('loads page size from local storage', async () => { + const savedSize = 5; + localStorage.setItem('todos-page-size', savedSize.toString()); + + createComponent(); + await nextTick(); + + expect(findPageSizeSelector().props('value')).toBe(savedSize); + }); }); diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 16c0f4a5835..83964b731e8 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -784,6 +784,39 @@ RSpec.describe GroupsHelper, feature_category: :groups_and_projects do end end + describe '#delete_delayed_group_message' do + let(:group) { build(:group) } + + subject(:message) { helper.delete_delayed_group_message(group) } + + specify do + deletion_adjourned_period = ::Gitlab::CurrentSettings.deletion_adjourned_period + + expect(message).to eq "This action will place this group, " \ + "including its subgroups and projects, in a pending deletion state for #{deletion_adjourned_period} days, " \ + "and delete it permanently on #{helper.permanent_deletion_date_formatted}." + end + end + + describe '#delete_immediately_group_scheduled_for_deletion_message' do + let(:group) { build(:group) } + let(:marked_for_deletion) { Date.parse('2024-01-01') } + + subject(:message) { helper.delete_immediately_group_scheduled_for_deletion_message(group) } + + before do + allow(group).to receive(:marked_for_deletion_on).and_return(marked_for_deletion) + end + + it 'returns the delete permanently override message' do + deletion_date = helper.permanent_deletion_date_formatted(group) + + expect(message).to eq "This group is scheduled for deletion on #{deletion_date}. " \ + "This action will permanently delete this group, including its subgroups and projects, " \ + "immediately. This action cannot be undone." + end + end + describe '#remove_group_message' do let_it_be(:group) { create(:group) } let(:delayed_deletion_message) { "The contents of this group, its subgroups and projects will be permanently deleted after" } diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb index 22902b8d1c5..8bdcbad8539 100644 --- a/spec/helpers/namespaces_helper_spec.rb +++ b/spec/helpers/namespaces_helper_spec.rb @@ -279,8 +279,7 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do context 'when container responds to :self_deletion_scheduled_deletion_created_on' do context 'when container.self_deletion_scheduled_deletion_created_on returns nil' do - # FIXME: Replace `double` with `instance_double(Namespace` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 - let(:container) { double(self_deletion_scheduled_deletion_created_on: nil) } # rubocop:disable RSpec/VerifiedDoubles -- We'll solve this with the above task + let(:container) { instance_double(Namespace, self_deletion_scheduled_deletion_created_on: nil) } it 'returns nil' do expect(permanent_deletion_date_formatted(container)).to be_nil @@ -288,8 +287,7 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do end context 'when container.self_deletion_scheduled_deletion_created_on returns a date' do - # FIXME: Replace `double` with `instance_double(Namespace` after https://gitlab.com/gitlab-org/gitlab/-/work_items/527085 - let(:container) { double(self_deletion_scheduled_deletion_created_on: Date.yesterday) } # rubocop:disable RSpec/VerifiedDoubles -- We'll solve this with the above task + let(:container) { instance_double(Namespace, self_deletion_scheduled_deletion_created_on: Date.yesterday) } it 'returns the date formatted' do expect(permanent_deletion_date_formatted(container)).to eq(4.days.from_now.strftime('%F')) @@ -301,11 +299,17 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do expect(permanent_deletion_date_formatted(Date.current)).to eq(5.days.from_now.strftime('%F')) end end + + context 'when no argument is passed' do + it 'returns the date formatted' do + expect(permanent_deletion_date_formatted).to eq(5.days.from_now.strftime('%F')) + end + end end context 'when a format is given' do it 'returns the date formatted with the given format' do - expect(permanent_deletion_date_formatted(Date.current, format: Date::DATE_FORMATS[:medium])).to eq(5.days.from_now.strftime(Date::DATE_FORMATS[:medium])) + expect(permanent_deletion_date_formatted(format: Date::DATE_FORMATS[:medium])).to eq(5.days.from_now.strftime(Date::DATE_FORMATS[:medium])) end end end diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb index af45c3b56d7..99a263fea72 100644 --- a/spec/helpers/profiles_helper_spec.rb +++ b/spec/helpers/profiles_helper_spec.rb @@ -118,12 +118,22 @@ RSpec.describe ProfilesHelper, feature_category: :user_profile do let(:time) { 3.hours.ago } let(:timezone) { 'Europe/London' } let(:user) do - build_stubbed(:user, status: UserStatus.new( - message: 'Some message', - emoji: 'basketball', - availability: 'busy', - clear_status_at: time - ), timezone: timezone) + build_stubbed( + :user, + status: UserStatus.new( + message: 'Some message', + emoji: 'basketball', + availability: 'busy', + clear_status_at: time + ), + timezone: timezone, + pronouns: 'they/them', + pronunciation: 'test-user', + job_title: 'Developer', + organization: 'GitLab', + location: 'Remote', + website_url: 'https://example.com', + bio: 'Test bio') end before do @@ -150,6 +160,23 @@ RSpec.describe ProfilesHelper, feature_category: :user_profile do expect(data[:timezones]).to eq(helper.timezone_data_with_unique_identifiers.to_json) expect(data[:user_timezone]).to eq(timezone) end + + it 'includes all user profile fields' do + data = helper.user_profile_data(user) + + expect(data[:id]).to eq(user.id) + expect(data[:name]).to eq(user.name) + expect(data[:pronouns]).to eq('they/them') + expect(data[:pronunciation]).to eq('test-user') + expect(data[:website_url]).to eq('https://example.com') + expect(data[:location]).to eq('Remote') + expect(data[:job_title]).to eq('Developer') + expect(data[:organization]).to eq('GitLab') + expect(data[:bio]).to eq('Test bio') + expect(data[:include_private_contributions]).to eq(user.include_private_contributions?.to_s) + expect(data[:achievements_enabled]).to eq(user.achievements_enabled.to_s) + expect(data[:private_profile]).to eq(user.private_profile?.to_s) + end end def stub_auth0_omniauth_provider diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 980276b9762..dc0a6bc4981 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -2089,91 +2089,33 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do end end - describe '#scheduled_for_deletion?' do - context 'when project is NOT scheduled for deletion' do - it { expect(helper.scheduled_for_deletion?(project)).to be false } - end + describe '#delete_delayed_project_message' do + subject(:message) { helper.delete_delayed_project_message(project) } - context 'when project is scheduled for deletion' do - let_it_be(:archived_project) { create(:project, :archived, marked_for_deletion_at: 10.minutes.ago) } + specify do + deletion_adjourned_period = ::Gitlab::CurrentSettings.deletion_adjourned_period - it { expect(helper.scheduled_for_deletion?(archived_project)).to be true } + expect(message).to eq "This action will place this project, " \ + "including all its resources, in a pending deletion state for #{deletion_adjourned_period} days, " \ + "and delete it permanently on #{helper.permanent_deletion_date_formatted}." end end - describe '#delete_delayed_message' do - subject(:message) { helper.delete_delayed_message(project) } + describe '#delete_immediately_project_scheduled_for_deletion_message' do + let(:marked_for_deletion) { Date.parse('2024-01-01') } + + subject(:message) { helper.delete_immediately_project_scheduled_for_deletion_message(project) } before do - allow(project).to receive(:delayed_deletion_ready?) - .and_return(feature_available) - end - - context 'when project has delayed deletion feature' do - let(:feature_available) { true } - - specify do - deletion_adjourned_period = ::Gitlab::CurrentSettings.deletion_adjourned_period - deletion_date = helper.permanent_deletion_date_formatted(Date.current) - - expect(message).to eq "This action will place this project, " \ - "including all its resources, in a pending deletion state for #{deletion_adjourned_period} days, " \ - "and delete it permanently on #{deletion_date}." - end - end - - context 'when project does not have delayed deletion feature' do - let(:feature_available) { false } - - specify do - expect(message).to eq "This action will permanently delete this project, including all its resources." - end - end - end - - describe '#delete_immediately_message' do - subject(:message) { helper.delete_immediately_message(project) } - - before do - allow(project).to receive(:adjourned_deletion?).and_return(allowed) - allow(project).to receive(:adjourned_deletion_configured?).and_return(allowed) allow(project).to receive(:marked_for_deletion_on).and_return(marked_for_deletion) end - describe 'when adjourned deletion is not available' do - let(:allowed) { false } - let(:marked_for_deletion) { nil } + it 'returns the delete permanently override message' do + deletion_date = helper.permanent_deletion_date_formatted(project) - it 'returns permanent deletion message' do - expect(message).to eq "This action will permanently delete this project, including all its resources." - end - end - - describe 'when adjourned deletion is available and project is already marked for deletion' do - let(:allowed) { true } - let(:marked_for_deletion) { Date.parse('2024-01-01') } - - it 'returns the delete permanently override message' do - deletion_date = helper.permanent_deletion_date_formatted(project.marked_for_deletion_on) - - expect(message).to eq "This project is scheduled for deletion on #{deletion_date}. " \ - "This action will permanently delete this project, including all its resources, " \ - "immediately. This action cannot be undone." - end - end - - describe 'when adjourned deletion is available and project is not marked for deletion' do - let(:allowed) { true } - let(:marked_for_deletion) { nil } - - it 'returns the delete delete delayed message' do - deletion_adjourned_period = ::Gitlab::CurrentSettings.deletion_adjourned_period - deletion_date = helper.permanent_deletion_date_formatted(Date.current) - - expect(message).to eq "This action will place this project, " \ - "including all its resources, in a pending deletion state for #{deletion_adjourned_period} days, " \ - "and delete it permanently on #{deletion_date}." - end + expect(message).to eq "This project is scheduled for deletion on #{deletion_date}. " \ + "This action will permanently delete this project, including all its resources, " \ + "immediately. This action cannot be undone." end end diff --git a/spec/lib/gitlab/ci/parsers/sbom/license_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/license/common_spec.rb similarity index 61% rename from spec/lib/gitlab/ci/parsers/sbom/license_spec.rb rename to spec/lib/gitlab/ci/parsers/sbom/license/common_spec.rb index d8b30bcded2..9df122ed7c4 100644 --- a/spec/lib/gitlab/ci/parsers/sbom/license_spec.rb +++ b/spec/lib/gitlab/ci/parsers/sbom/license/common_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Parsers::Sbom::License, feature_category: :dependency_management do +RSpec.describe Gitlab::Ci::Parsers::Sbom::License::Common, feature_category: :dependency_management do describe "#parse" do subject(:license) { described_class.new(data).parse } @@ -39,33 +39,7 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::License, feature_category: :dependency expect(license.name).to eq("Example, Inc. Commercial License") end end - - context "when the license has neither id nor name" do - let(:data) do - { - "license" => { - "url" => "https://example.com/license.txt" - } - } - end - - it "returns nil" do - is_expected.to be_nil - end - end - - context "when the license is defined using an expression" do - let(:data) do - { - "expression" => { - "name" => "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0" - } - } - end - - it "ignores the license" do - is_expected.to be_nil - end - end end + + it_behaves_like 'with sbom licenses' end diff --git a/spec/lib/gitlab/ci/parsers/sbom/license/container_scanning_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/license/container_scanning_spec.rb new file mode 100644 index 00000000000..20f7ba0e2b0 --- /dev/null +++ b/spec/lib/gitlab/ci/parsers/sbom/license/container_scanning_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Parsers::Sbom::License::ContainerScanning, feature_category: :dependency_management do + describe "#parse" do + subject(:license) { described_class.new(data).parse } + + context "when the license is defined by name" do + let(:data) do + { + "license" => { + "name" => "Example, Inc. Commercial License" + } + } + end + + it "sets the expected values" do + is_expected.to be_kind_of(::Gitlab::Ci::Reports::Sbom::License) + + expect(license.spdx_identifier).to eq("Example, Inc. Commercial License") + end + end + end + + it_behaves_like 'with sbom licenses' +end diff --git a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb index 718c79d5f50..7c4517d8e1d 100644 --- a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb +++ b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb @@ -7,14 +7,27 @@ RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator, feature_category shared_examples 'arg line with invalid parameters' do it 'return nil' do - expect(subject.new(invalid_arg).execute).to eq(nil) + expect(subject.new(invalid_arg, nil).execute).to eq(nil) end end shared_examples 'arg line with valid parameters' do it 'return time and date array' do freeze_time do - expect(subject.new(valid_arg).execute).to eq(expected_response) + expect(subject.new(valid_arg, nil).execute).to eq(expected_response) + end + end + + context 'when timezone set' do + let(:timezone) { 'Hawaii' } + + it 'return time and date array' do + freeze_time do + date = defined?(raw_date) ? Date.parse(raw_date).in_time_zone(timezone).midday : DateTime.current + expected_response[1] = date + + expect(subject.new(valid_arg, timezone).execute).to eq(expected_response) + end end end end @@ -43,7 +56,7 @@ RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator, feature_category let(:invalid_arg) { 'dfjkghdskjfghdjskfgdfg' } it 'return nil as time value' do - time_date_response = subject.new(invalid_arg).execute + time_date_response = subject.new(invalid_arg, nil).execute expect(time_date_response).to be_an_instance_of(Array) expect(time_date_response.first).to eq(nil) diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 69adc0fa9c0..37f8ab18d5a 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -122,22 +122,26 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do context 'when not specifying a date' do let(:note_text) { "/spend 1h" } - it 'does not include the date' do - _, update_params = service.execute(note) - service.apply_updates(update_params, note) + it 'includes the date, time and timezone' do + allow_any_instance_of(User).to receive(:timezone).and_return('Hawaii') # rubocop:disable RSpec/AnyInstanceOf -- It's not the next instance - expect(Note.last.note).to eq('added 1h of time spent') + travel_to(Time.utc(2025, 5, 1)) do + _, update_params = service.execute(note) + service.apply_updates(update_params, note) + + expect(Note.last.note).to eq('added 1h of time spent at 2025-04-30 14:00:00 -1000') + end end end context 'when specifying a date' do let(:note_text) { "/spend 1h 2020-01-01" } - it 'does include the date' do + it 'includes the date, time and timezone' do _, update_params = service.execute(note) service.apply_updates(update_params, note) - expect(Note.last.note).to eq('added 1h of time spent at 2020-01-01') + expect(Note.last.note).to eq('added 1h of time spent at 2020-01-01 12:00:00 UTC') end end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index afbe228f635..85edac6b9b0 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -433,6 +433,12 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d end shared_examples 'spend command with valid date' do + let(:timezone) { 'Europe/Athens' } + + before do + allow(developer).to receive(:timezone).and_return(timezone) + end + it 'populates spend time: 1800 with date in date type format' do _, updates, _ = service.execute(content, issuable) @@ -440,7 +446,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :text_editors d category: nil, duration: 1800, user_id: developer.id, - spent_at: Date.parse(date).midday + spent_at: Date.parse(date).in_time_zone(timezone).midday }) end end diff --git a/spec/services/system_notes/time_tracking_service_spec.rb b/spec/services/system_notes/time_tracking_service_spec.rb index 7daa82a945c..b815530d82a 100644 --- a/spec/services/system_notes/time_tracking_service_spec.rb +++ b/spec/services/system_notes/time_tracking_service_spec.rb @@ -283,7 +283,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: 1800, spent_at: '2022-03-30T00:00:00.000Z') } it 'sets the note text' do - expect(subject.note).to eq "added 30m of time spent at 2022-03-30" + expect(subject.note).to eq "added 30m of time spent at 2022-03-30 00:00:00 UTC" end end @@ -297,7 +297,15 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: -time_spent.to_i, spent_at: spent_at) } it 'sets the note text' do - expect(subject.note).to eq "subtracted 30m of time spent at 2022-03-30" + expect(subject.note).to eq "subtracted 30m of time spent at 2022-03-30 00:00:00 UTC" + end + + context 'when the user timezone is set' do + let(:author) { create(:user, timezone: 'Hawaii') } + + it 'sets the note text using the user timezone' do + expect(subject.note).to eq "subtracted 30m of time spent at 2022-03-29 14:00:00 -1000" + end end end end @@ -308,10 +316,18 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann context 'when the timelog has a positive time spent value' do let_it_be(:noteable, reload: true) { create(:issue, project: project) } - let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: 1800, spent_at: '2022-03-30T00:00:00.000Z') } + let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: 1800, spent_at: '2022-03-30T12:00:00.000Z') } it 'sets the note text' do - expect(subject.note).to eq "deleted 30m of spent time from 2022-03-30" + expect(subject.note).to eq "deleted 30m of spent time from 2022-03-30 12:00:00 UTC" + end + + context 'when the user timezone is set' do + let(:author) { create(:user, timezone: 'Hawaii') } + + it 'sets the note text using the user timezone' do + expect(subject.note).to eq "deleted 30m of spent time from 2022-03-30 02:00:00 -1000" + end end end @@ -325,7 +341,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann let(:timelog) { create(:timelog, user: author, issue: noteable, time_spent: -time_spent.to_i, spent_at: spent_at) } it 'sets the note text' do - expect(subject.note).to eq "deleted -30m of spent time from 2022-03-30" + expect(subject.note).to eq "deleted -30m of spent time from 2022-03-30 00:00:00 UTC" end end end @@ -345,10 +361,26 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann end context 'when time was added' do + include ActiveSupport::Testing::TimeHelpers + + around do |example| + travel_to(Time.utc(2019, 12, 30, 14, 5, 12)) { example.run } + end + it 'sets the note text' do spend_time!(277200) - expect(subject.note).to eq "added 1w 4d 5h of time spent" + expect(subject.note).to eq "added 1w 4d 5h of time spent at 2019-12-30 14:05:12 UTC" + end + + context 'when author timezone is set' do + let(:author) { create(:user, timezone: 'Hawaii') } + + it 'sets the note text' do + spend_time!(277200) + + expect(subject.note).to eq "added 1w 4d 5h of time spent at 2019-12-30 04:05:12 -1000" + end end context 'when time was subtracted' do @@ -356,7 +388,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann spend_time!(360000) spend_time!(-277200) - expect(subject.note).to eq "subtracted 1w 4d 5h of time spent" + expect(subject.note).to eq "subtracted 1w 4d 5h of time spent at 2019-12-30 14:05:12 UTC" end end @@ -376,7 +408,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann it 'sets the note text' do spend_time!(277200) - expect(subject.note).to eq "added 77h of time spent" + expect(subject.note).to eq "added 77h of time spent at 2019-12-30 14:05:12 UTC" end end diff --git a/spec/support/shared_examples/lib/gitlab/ci/parsers/sbom/license/sbom_licenses_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/parsers/sbom/license/sbom_licenses_shared_examples.rb new file mode 100644 index 00000000000..9732b77367e --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/ci/parsers/sbom/license/sbom_licenses_shared_examples.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'with sbom licenses' do + describe '.parse' do + subject(:license) { described_class.parse({}, is_container_scanning) } + + context 'when it is not a container scanning' do + let(:is_container_scanning) { false } + + it "creates an instance of #{described_class}" do + expect(described_class).to receive(:new).and_call_original + + license + end + end + + context 'when it is container scanning' do + let(:is_container_scanning) { true } + + it 'creates an instance of Gitlab::Ci::Parsers::Sbom::License::ContainerScanning' do + expect(Gitlab::Ci::Parsers::Sbom::License::ContainerScanning).to receive(:new).and_call_original + + license + end + end + end + + describe "#parse" do + subject(:license) { described_class.new(data).parse } + + context "when the license has neither id nor name" do + let(:data) do + { + "license" => { + "url" => "https://example.com/license.txt" + } + } + end + + it "returns nil" do + is_expected.to be_nil + end + end + + context "when the license is defined using an expression" do + let(:data) do + { + "expression" => { + "name" => "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0" + } + } + end + + it "ignores the license" do + is_expected.to be_nil + end + end + end +end diff --git a/tooling/ci/job_tokens/docs/templates/fine_grained_permissions.md.erb b/tooling/ci/job_tokens/docs/templates/fine_grained_permissions.md.erb index db2b3398be4..781da63676c 100644 --- a/tooling/ci/job_tokens/docs/templates/fine_grained_permissions.md.erb +++ b/tooling/ci/job_tokens/docs/templates/fine_grained_permissions.md.erb @@ -17,24 +17,25 @@ title: Fine-grained permissions for CI/CD job tokens {{< details >}} -Tier: Free, Premium, Ultimate -Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated -Status: Experiment +- Tier: Free, Premium, Ultimate +- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated +- Status: Beta {{< /details >}} {{< history >}} -- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/15234) in GitLab 17.10. This feature is an [experiment](../../policy/development_stages_support.md#experiment). +- [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/15234) as an [experiment](../../policy/development_stages_support.md#experiment) in GitLab 17.10. +- [Changed](https://gitlab.com/groups/gitlab-org/-/epics/16199) from experiment to beta in GitLab 18.0. {{< /history >}} You can use fine-grained permissions to explicitly allow access to a limited set of API endpoints. These permissions are applied to the CI/CD job tokens in a specified project. -This feature is an [experiment](../../policy/development_stages_support.md#experiment) and subject to change without notice. This feature is not ready for production use. If you want to use this feature, you should test outside of production first. +This feature is in [beta](../../policy/development_stages_support.md#beta). -## Enable use of fine-grained permissions +## Enable fine-grained permissions Prerequisites: