From d6a7d36ff2c468d8fd11b227d40630a2ba7f2aa2 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 22 Nov 2024 12:33:18 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/rails.gitlab-ci.yml | 3 +- .gitlab/ci/rails/shared.gitlab-ci.yml | 4 +- .rubocop.yml | 10 +- .rubocop_todo/gitlab/bounded_contexts.yml | 2 +- .rubocop_todo/layout/class_structure.yml | 1 - .rubocop_todo/layout/line_length.yml | 22 +- .../lint/redundant_cop_disable_directive.yml | 53 +- .rubocop_todo/rspec/context_wording.yml | 1 - .../rspec/example_without_description.yml | 1 - GITLAB_KAS_VERSION | 2 +- .../javascripts/access_level/constants.js | 14 + .../branches/components/sort_dropdown.vue | 4 + .../pages/dashboard/todos/index/index.js | 7 +- .../pages/dashboard/todos/vue/index.js | 3 - .../wikis/components/clone_wiki_modal.vue | 76 +- .../customizable_dashboard.stories.js | 2 +- .../panels_base.stories.js | 2 +- .../page_bundles/environments.scss | 49 -- .../page_bundles/merge_requests.scss | 12 +- .../admin/application_settings_controller.rb | 1 - app/controllers/dashboard/todos_controller.rb | 21 +- .../groups/milestones_controller.rb | 2 +- app/controllers/root_controller.rb | 3 +- app/finders/issues/confidentiality_filter.rb | 2 +- app/graphql/resolvers/projects_resolver.rb | 4 + app/graphql/types/member_access_level_enum.rb | 1 + .../work_items/widgets/development_type.rb | 2 +- app/helpers/application_settings_helper.rb | 1 + app/helpers/projects_helper.rb | 1 + app/helpers/sidebars_helper.rb | 6 +- app/models/ci/bridge.rb | 25 +- .../concerns/integrations/base/integration.rb | 2 +- .../token_authenticatable_strategies/base.rb | 48 +- .../routable_token_generator.rb | 112 +++ app/models/group.rb | 4 + app/models/issue.rb | 4 +- app/models/member.rb | 1 + app/models/project.rb | 2 +- app/models/project_team.rb | 12 + app/models/system/broadcast_message.rb | 1 + app/models/user.rb | 2 +- app/models/work_items/type.rb | 2 +- app/policies/board_policy.rb | 6 +- app/policies/group_policy.rb | 19 +- app/policies/issuable_policy.rb | 7 +- app/policies/issue_policy.rb | 8 +- app/policies/merge_request_policy.rb | 2 +- .../group_project_namespace_shared_policy.rb | 4 + app/policies/project_policy.rb | 44 +- .../set_runner_associated_projects_service.rb | 2 +- .../groups/open_issues_count_service.rb | 10 +- .../projects/open_issues_count_service.rb | 10 +- .../destroy/confidential_issue_service.rb | 2 +- .../todos/destroy/entity_leave_service.rb | 36 +- .../work_items/data_sync/clone_service.rb | 2 +- .../work_items/data_sync/move_service.rb | 2 +- app/views/projects/branches/index.html.haml | 6 +- app/views/shared/wikis/git_access.html.haml | 23 - .../ci_optimize_memory_for_variables.yml} | 14 +- data/whats_new/202411210001_17_06.yml | 47 ++ db/docs/views/postgres_table_sizes.yml | 10 + ...021082113_create_partitioned_ci_runners.rb | 2 - ...6_create_partitioned_ci_runner_managers.rb | 2 - ...119101703_add_postgres_table_sizes_view.rb | 29 + ...163518_drop_user_canonical_emails_table.rb | 2 +- db/schema_migrations/20241119101703 | 1 + db/structure.sql | 12 + doc/administration/auth/index.md | 4 +- .../reporting/git_abuse_rate_limit.md | 2 +- .../reporting/ip_addr_restrictions.md | 2 +- doc/administration/review_abuse_reports.md | 2 +- doc/administration/review_spam_logs.md | 2 +- .../settings/account_and_limit_settings.md | 7 +- doc/api/access_requests.md | 1 + doc/api/broadcast_messages.md | 2 + doc/api/graphql/reference/index.md | 2 + doc/api/group_access_tokens.md | 2 +- doc/api/invitations.md | 1 + doc/api/member_roles.md | 2 +- doc/api/members.md | 1 + doc/api/milestones.md | 6 +- doc/api/project_access_tokens.md | 2 +- doc/ci/test_cases/index.md | 21 +- .../documentation/styleguide/index.md | 1 + .../fe_guide/accessibility/best_practices.md | 211 +---- doc/development/identity_verification.md | 2 +- .../internal_analytics/service_ping/index.md | 20 +- .../exploratory_testing.md | 2 +- .../graphql_api.md | 2 +- .../spam_protection_and_captcha/index.md | 2 +- .../model_and_services.md | 2 +- .../spam_protection_and_captcha/rest_api.md | 2 +- .../spam_protection_and_captcha/web_ui.md | 2 +- doc/integration/akismet.md | 2 +- doc/integration/arkose.md | 2 +- doc/integration/recaptcha.md | 2 +- doc/security/email_verification.md | 2 +- doc/security/identity_verification.md | 2 +- doc/update/package/convert_to_ee.md | 94 +-- .../policies/pipeline_execution_policies.md | 15 + .../policies/scan_execution_policies.md | 1 + doc/user/crm/index.md | 27 +- doc/user/gitlab_duo_chat/index.md | 4 +- doc/user/group/epics/epic_boards.md | 19 +- doc/user/group/epics/manage_epics.md | 48 +- doc/user/group/iterations/index.md | 17 +- doc/user/group/moderate_users.md | 2 +- .../group/reporting/git_abuse_rate_limit.md | 2 +- doc/user/group/saml_sso/index.md | 2 +- doc/user/okrs.md | 51 +- doc/user/permissions.md | 682 ++++++++-------- doc/user/profile/service_accounts.md | 6 +- doc/user/project/issue_board.md | 35 +- .../project/issues/confidential_issues.md | 8 +- doc/user/project/issues/csv_export.md | 4 +- doc/user/project/issues/design_management.md | 12 +- doc/user/project/issues/due_dates.md | 4 +- doc/user/project/issues/issue_weight.md | 8 +- doc/user/project/issues/managing_issues.md | 55 +- doc/user/project/labels.md | 41 +- doc/user/project/members/index.md | 2 +- doc/user/project/milestones/index.md | 16 +- doc/user/project/requirements/index.md | 27 +- doc/user/project/time_tracking.md | 15 +- doc/user/report_abuse.md | 2 +- doc/user/tasks.md | 35 +- lib/api/submodules.rb | 2 +- lib/gitlab/access.rb | 4 + .../backfill_security_policies.rb | 2 +- ...ot_user_details_bot_namespace_migration.rb | 2 - lib/gitlab/ci/build/context/base.rb | 23 +- lib/gitlab/ci/config/external/context.rb | 23 +- .../ci/pipeline/chain/populate_metadata.rb | 2 +- lib/gitlab/ci/pipeline/seed/build.rb | 2 +- lib/gitlab/ci/project_config.rb | 5 +- .../pipeline_execution_policy_forced.rb | 23 - lib/gitlab/ci/variables/collection.rb | 14 +- lib/gitlab/ci/variables/collection/item.rb | 12 +- lib/gitlab/database/postgres_table_size.rb | 19 + lib/gitlab/popen/runner.rb | 2 +- lib/sidebars/your_work/menus/todos_menu.rb | 6 +- locale/gitlab.pot | 55 +- qa/qa/resource/user_runners.rb | 2 +- qa/qa/service/docker_run/gitlab_runner.rb | 4 +- qa/qa/vendor/one_password/cli.rb | 2 +- .../pajamas/banner_component_preview.rb | 2 +- .../dashboard/todos_controller_spec.rb | 53 +- spec/controllers/root_controller_spec.rb | 17 +- .../gitlab/database/postgres_table_size.rb | 13 + spec/factories/groups.rb | 2 + spec/factories/member.rb | 1 + spec/factories/projects.rb | 2 + spec/factories/user_highest_roles.rb | 1 + spec/factories/users.rb | 2 + .../dashboard/todos/accessibility_spec.rb | 4 + .../dashboard/todos/target_state_spec.rb | 1 + .../dashboard/todos/todos_filtering_spec.rb | 2 + .../dashboard/todos/todos_sorting_spec.rb | 1 + spec/features/dashboard/todos/todos_spec.rb | 20 +- .../groups/participants_autocomplete_spec.rb | 2 +- .../merge_request/user_edits_mr_spec.rb | 2 +- spec/features/projects/branches_spec.rb | 31 + .../branches/components/sort_dropdown_spec.js | 22 +- spec/frontend/fixtures/todos.rb | 1 + .../table/drawer/role_details_drawer_spec.js | 4 +- spec/frontend/members/mock_data.js | 1 + .../wikis/components/clone_wiki_modal_spec.js | 10 +- .../resolvers/merge_requests_resolver_spec.rb | 5 +- .../types/current_user_todos_type_spec.rb | 4 +- .../types/member_access_level_enum_spec.rb | 2 +- .../application_settings_helper_spec.rb | 4 + spec/helpers/groups_helper_spec.rb | 3 + spec/helpers/sidebars_helper_spec.rb | 2 +- .../database/postgres_table_sizes_spec.rb | 66 ++ .../secret/pbkdf2_sha512_spec.rb | 4 +- .../token/pbkdf2_sha512_spec.rb | 2 +- .../merge_requests/message_generator_spec.rb | 2 +- .../lib/gitlab/search/abuse_detection_spec.rb | 2 +- ...between_ci_builds_and_ci_pipelines_spec.rb | 2 +- .../concerns/encrypted_user_password_spec.rb | 5 +- .../base_spec.rb | 111 +-- .../routable_token_generator_spec.rb | 153 ++++ spec/models/group_spec.rb | 1 + spec/models/members/project_member_spec.rb | 1 + spec/models/project_spec.rb | 2 +- spec/models/project_team_spec.rb | 30 +- spec/models/user_highest_role_spec.rb | 1 + spec/models/user_spec.rb | 24 +- spec/policies/board_policy_spec.rb | 49 +- spec/policies/group_policy_spec.rb | 138 +++- spec/policies/issuable_policy_spec.rb | 20 + spec/policies/issue_policy_spec.rb | 152 ++-- spec/policies/merge_request_policy_spec.rb | 168 +++- spec/policies/project_policy_spec.rb | 753 +++++++++--------- spec/policies/work_item_policy_spec.rb | 2 + spec/presenters/member_presenter_spec.rb | 1 + .../api/alert_management_alerts_spec.rb | 8 +- spec/requests/api/ci/jobs_spec.rb | 2 +- spec/requests/api/members_spec.rb | 2 +- .../projects/releases_controller_spec.rb | 2 +- spec/scripts/setup/tests-metadata_spec.rb | 2 +- .../ci/create_pipeline_service/logger_spec.rb | 3 +- .../groups/open_issues_count_service_spec.rb | 2 +- .../conan/create_package_service_spec.rb | 3 - .../pypi/create_package_service_spec.rb | 7 +- .../create_acme_order_service_spec.rb | 2 +- .../open_issues_count_service_spec.rb | 2 +- .../destroy/entity_leave_service_spec.rb | 34 +- .../vulnerabilities_findings_helper.rb | 2 +- spec/support/helpers/wait_for_requests.rb | 5 +- .../token_authenticatable_matchers.rb | 215 +++++ spec/support/rspec_order_todo.yml | 1 - .../policies/group_policy_shared_context.rb | 11 + .../policies/project_policy_shared_context.rb | 15 + .../milestone_editing_shared_examples.rb | 2 +- ...redacted_search_results_shared_examples.rb | 2 +- .../protected_ref_access_shared_examples.rb | 2 +- .../token_authenticatable_shared_examples.rb | 31 + .../group_level_work_items_shared_examples.rb | 57 ++ ...roject_level_work_items_shared_examples.rb | 32 + .../project_policy_shared_examples.rb | 111 +++ .../policies/wiki_policies_shared_examples.rb | 9 + tooling/danger/sidekiq_queues.rb | 2 +- tooling/danger/stable_branch.rb | 2 +- 224 files changed, 3202 insertions(+), 1851 deletions(-) delete mode 100644 app/assets/javascripts/pages/dashboard/todos/vue/index.js create mode 100644 app/models/concerns/token_authenticatable_strategies/routable_token_generator.rb rename config/feature_flags/{gitlab_com_derisk/routable_token.yml => beta/ci_optimize_memory_for_variables.yml} (62%) create mode 100644 data/whats_new/202411210001_17_06.yml create mode 100644 db/docs/views/postgres_table_sizes.yml create mode 100644 db/migrate/20241119101703_add_postgres_table_sizes_view.rb create mode 100644 db/schema_migrations/20241119101703 delete mode 100644 lib/gitlab/ci/project_config/pipeline_execution_policy_forced.rb create mode 100644 lib/gitlab/database/postgres_table_size.rb create mode 100644 spec/factories/gitlab/database/postgres_table_size.rb create mode 100644 spec/lib/gitlab/database/postgres_table_sizes_spec.rb create mode 100644 spec/models/concerns/token_authenticatable_strategies/routable_token_generator_spec.rb create mode 100644 spec/support/matchers/token_authenticatable_matchers.rb diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 752f380bbab..879ed459f82 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -764,6 +764,7 @@ rspec-ee:predictive:trigger single-db-ci-connection: .rspec-ee-base-gitlab-duo: extends: - .rspec-ee-base-pg14 + when: manual variables: REAL_AI_REQUEST: "true" AI_GATEWAY_URL: http://ai-gateway:5052 @@ -1297,7 +1298,7 @@ rspec-ee system pg16: stage: test script: - !reference [.base-script, script] - - rspec_section rspec_fail_fast "${MATCHING_TESTS_PATH}" "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~zoekt --tag ~click_house" + - rspec_section rspec_fail_fast "${MATCHING_TESTS_PATH}" "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~zoekt --tag ~click_house --tag ~real_ai_request" rspec fail-fast: extends: diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml index 8553d6ccf60..703e9ca0a9c 100644 --- a/.gitlab/ci/rails/shared.gitlab-ci.yml +++ b/.gitlab/ci/rails/shared.gitlab-ci.yml @@ -107,7 +107,7 @@ include: # spec/lib, yet background migration tests are also sitting there, # and they should run on their own jobs so we don't need to run them # in unit tests again. - - rspec_section rspec_parallelized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~level:background_migration --tag ~click_house" + - rspec_section rspec_parallelized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~level:background_migration --tag ~click_house --tag ~real_ai_request" after_script: - source scripts/utils.sh - log_disk_usage # https://gitlab.com/gitlab-org/gitlab/-/issues/478880 @@ -204,7 +204,7 @@ include: .rspec-base-migration: script: - !reference [.base-script, script] - - rspec_section rspec_parallelized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~zoekt --tag ~click_house" + - rspec_section rspec_parallelized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~zoekt --tag ~click_house --tag ~real_ai_request" after_script: - !reference [.rspec-base, after_script] diff --git a/.rubocop.yml b/.rubocop.yml index d814e41156b..9171c586b9c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -67,6 +67,13 @@ Rails/Date: # See https://gitlab.com/gitlab-org/gitlab/-/issues/502580 - danger/database/Dangerfile +Rails/Pluck: + Exclude: + # See https://gitlab.com/gitlab-org/gitlab/-/issues/502580 + - 'tooling/danger/**/*' + # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94047#note_1179689274 + AutoCorrect: false + RSpec: Language: Includes: @@ -363,9 +370,6 @@ Rails/MailerName: # See for the context on why it's excluded https://gitlab.com/gitlab-org/gitlab/-/issues/239356#note_956419227 - 'app/mailers/notify.rb' -Rails/Pluck: - # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94047#note_1179689274 - AutoCorrect: false Rails/RakeEnvironment: # Context on why it's disabled: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93419#note_1048223982 diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml index 7f8f8fa7eb3..f4c6efc40ac 100644 --- a/.rubocop_todo/gitlab/bounded_contexts.yml +++ b/.rubocop_todo/gitlab/bounded_contexts.yml @@ -945,6 +945,7 @@ Gitlab/BoundedContexts: - 'app/models/concerns/token_authenticatable_strategies/encrypted.rb' - 'app/models/concerns/token_authenticatable_strategies/encryption_helper.rb' - 'app/models/concerns/token_authenticatable_strategies/insecure.rb' + - 'app/models/concerns/token_authenticatable_strategies/routable_token_generator.rb' - 'app/models/concerns/transactions.rb' - 'app/models/concerns/transitionable.rb' - 'app/models/concerns/triggerable_hooks.rb' @@ -3405,7 +3406,6 @@ Gitlab/BoundedContexts: - 'ee/app/services/personal_access_tokens/groups/update_lifetime_service.rb' - 'ee/app/services/personal_access_tokens/instance/update_lifetime_service.rb' - 'ee/app/services/personal_access_tokens/revoke_invalid_tokens.rb' - - 'ee/app/services/personal_access_tokens/rotation_verifier_service.rb' - 'ee/app/services/phone_verification/telesign_client/base_service.rb' - 'ee/app/services/phone_verification/telesign_client/risk_score_service.rb' - 'ee/app/services/phone_verification/telesign_client/send_verification_code_service.rb' diff --git a/.rubocop_todo/layout/class_structure.yml b/.rubocop_todo/layout/class_structure.yml index 615306df72d..7ddd5b6e305 100644 --- a/.rubocop_todo/layout/class_structure.yml +++ b/.rubocop_todo/layout/class_structure.yml @@ -243,7 +243,6 @@ Layout/ClassStructure: - 'ee/app/services/package_metadata/ingestion/compressed_package/package_ingestion_task.rb' - 'ee/app/services/path_locks/lock_service.rb' - 'ee/app/services/path_locks/unlock_service.rb' - - 'ee/app/services/personal_access_tokens/rotation_verifier_service.rb' - 'ee/app/services/search/zoekt/callback_service.rb' - 'ee/app/services/search/zoekt/task_serializer_service.rb' - 'ee/app/services/security/ingestion/tasks/update_vulnerability_uuids.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 20535304c01..d147c4cda5f 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -17,6 +17,7 @@ Layout/LineLength: - 'app/controllers/concerns/issuable_collections.rb' - 'app/controllers/concerns/membership_actions.rb' - 'app/controllers/concerns/notes_actions.rb' + - 'app/controllers/groups/milestones_controller.rb' - 'app/controllers/groups_controller.rb' - 'app/controllers/import/base_controller.rb' - 'app/controllers/import/bitbucket_controller.rb' @@ -791,7 +792,6 @@ Layout/LineLength: - 'ee/app/services/jira/requests/issues/list_service.rb' - 'ee/app/services/merge_requests/create_from_vulnerability_data_service.rb' - 'ee/app/services/merge_trains/refresh_merge_request_service.rb' - - 'ee/app/services/personal_access_tokens/rotation_verifier_service.rb' - 'ee/app/services/projects/mark_for_deletion_service.rb' - 'ee/app/services/projects/update_mirror_service.rb' - 'ee/app/services/security/ingestion/finding_map.rb' @@ -1075,6 +1075,7 @@ Layout/LineLength: - 'ee/spec/features/groups/saml_providers_spec.rb' - 'ee/spec/features/groups/scim_token_spec.rb' - 'ee/spec/features/groups/security/compliance_dashboards_spec.rb' + - 'ee/spec/features/groups/settings/domain_verification_spec.rb' - 'ee/spec/features/integrations/jira/jira_issues_list_spec.rb' - 'ee/spec/features/issues/filtered_search/filter_issues_weight_spec.rb' - 'ee/spec/features/labels_hierarchy_spec.rb' @@ -1096,6 +1097,7 @@ Layout/LineLength: - 'ee/spec/features/projects/iterations/user_views_iteration_spec.rb' - 'ee/spec/features/projects/members/member_is_removed_from_project_spec.rb' - 'ee/spec/features/projects/members/member_leaves_project_spec.rb' + - 'ee/spec/features/projects/mirror_spec.rb' - 'ee/spec/features/projects/new_project_spec.rb' - 'ee/spec/features/projects/pipelines/pipeline_spec.rb' - 'ee/spec/features/projects/quality/test_case_list_spec.rb' @@ -1251,6 +1253,7 @@ Layout/LineLength: - 'ee/spec/lib/audit/details_spec.rb' - 'ee/spec/lib/banzai/filter/jira_private_image_link_filter_spec.rb' - 'ee/spec/lib/banzai/filter/references/epic_reference_filter_spec.rb' + - 'ee/spec/lib/banzai/filter/references/iterations_cadence_reference_filter_spec.rb' - 'ee/spec/lib/banzai/filter/references/label_reference_filter_spec.rb' - 'ee/spec/lib/banzai/filter/references/vulnerability_reference_filters_spec.rb' - 'ee/spec/lib/bulk_imports/projects/pipelines/issues_pipeline_spec.rb' @@ -1387,6 +1390,7 @@ Layout/LineLength: - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_event_streaming_destinations_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_groups_with_event_streaming_destinations_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_projects_with_external_status_checks_metric_spec.rb' + - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_security_scans_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_associating_group_milestones_to_releases_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_ci_builds_metric_spec.rb' - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/license_metric_spec.rb' @@ -1816,7 +1820,6 @@ Layout/LineLength: - 'ee/spec/services/merge_trains/create_pipeline_service_spec.rb' - 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb' - 'ee/spec/services/personal_access_tokens/create_service_audit_log_spec.rb' - - 'ee/spec/services/personal_access_tokens/rotation_verifier_service_spec.rb' - 'ee/spec/services/projects/alerting/notify_service_spec.rb' - 'ee/spec/services/projects/cleanup_service_spec.rb' - 'ee/spec/services/projects/gitlab_projects_import_service_spec.rb' @@ -1891,6 +1894,7 @@ Layout/LineLength: - 'ee/spec/support/shared_contexts/status_page/status_page_list_objects.rb' - 'ee/spec/support/shared_examples/controllers/analytics/cycle_analytics/shared_stage_shared_examples.rb' - 'ee/spec/support/shared_examples/controllers/concerns/description_diff_actions_shared_examples.rb' + - 'ee/spec/support/shared_examples/features/dashboard_saml_reauth_banner_shared_examples.rb' - 'ee/spec/support/shared_examples/features/epics_filtered_search_shared_examples.rb' - 'ee/spec/support/shared_examples/features/sidebar_shared_examples.rb' - 'ee/spec/support/shared_examples/finders/geo/registry_finder_shared_examples.rb' @@ -2065,6 +2069,7 @@ Layout/LineLength: - 'lib/api/settings.rb' - 'lib/api/snippet_repository_storage_moves.rb' - 'lib/api/snippets.rb' + - 'lib/api/submodules.rb' - 'lib/api/suggestions.rb' - 'lib/api/tags.rb' - 'lib/api/templates.rb' @@ -2426,6 +2431,7 @@ Layout/LineLength: - 'qa/qa/resource/registry_repository.rb' - 'qa/qa/resource/repository/push.rb' - 'qa/qa/resource/snippet.rb' + - 'qa/qa/resource/user_runners.rb' - 'qa/qa/resource/wiki/group_page.rb' - 'qa/qa/runtime/api/repository_storage_moves.rb' - 'qa/qa/runtime/env.rb' @@ -2434,6 +2440,7 @@ Layout/LineLength: - 'qa/qa/scenario/bootable.rb' - 'qa/qa/service/cluster_provider/gcloud.rb' - 'qa/qa/service/cluster_provider/k3s.rb' + - 'qa/qa/service/docker_run/gitlab_runner.rb' - 'qa/qa/service/praefect_manager.rb' - 'qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb' - 'qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb' @@ -2515,6 +2522,7 @@ Layout/LineLength: - 'qa/qa/tools/delete_test_users.rb' - 'qa/qa/tools/generate_perf_testdata.rb' - 'qa/qa/tools/initialize_gitlab_auth.rb' + - 'qa/qa/vendor/one_password/cli.rb' - 'qa/spec/git/repository_spec.rb' - 'qa/spec/resource/api_fabricator_spec.rb' - 'qa/spec/runtime/env_spec.rb' @@ -2698,6 +2706,7 @@ Layout/LineLength: - 'spec/features/groups/milestone_spec.rb' - 'spec/features/groups/milestones_sorting_spec.rb' - 'spec/features/groups/packages_spec.rb' + - 'spec/features/groups/participants_autocomplete_spec.rb' - 'spec/features/groups/settings/access_tokens_spec.rb' - 'spec/features/groups/settings/repository_spec.rb' - 'spec/features/groups_spec.rb' @@ -2734,6 +2743,7 @@ Layout/LineLength: - 'spec/features/merge_request/user_comments_on_diff_spec.rb' - 'spec/features/merge_request/user_creates_image_diff_notes_spec.rb' - 'spec/features/merge_request/user_creates_merge_request_spec.rb' + - 'spec/features/merge_request/user_edits_mr_spec.rb' - 'spec/features/merge_request/user_expands_diff_spec.rb' - 'spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb' - 'spec/features/merge_request/user_posts_diff_notes_spec.rb' @@ -3260,6 +3270,8 @@ Layout/LineLength: - 'spec/lib/gitlab/diff/inline_diff_marker_spec.rb' - 'spec/lib/gitlab/diff/position_tracer/line_strategy_spec.rb' - 'spec/lib/gitlab/diff/suggestion_spec.rb' + - 'spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb' + - 'spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb' - 'spec/lib/gitlab/email/failure_handler_spec.rb' - 'spec/lib/gitlab/email/handler/create_issue_handler_spec.rb' - 'spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb' @@ -3361,6 +3373,7 @@ Layout/LineLength: - 'spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb' - 'spec/lib/gitlab/lfs/client_spec.rb' - 'spec/lib/gitlab/mail_room/authenticator_spec.rb' + - 'spec/lib/gitlab/merge_requests/message_generator_spec.rb' - 'spec/lib/gitlab/metrics/background_transaction_spec.rb' - 'spec/lib/gitlab/metrics/boot_time_tracker_spec.rb' - 'spec/lib/gitlab/metrics/elasticsearch_rack_middleware_spec.rb' @@ -3406,6 +3419,7 @@ Layout/LineLength: - 'spec/lib/gitlab/route_map_spec.rb' - 'spec/lib/gitlab/sanitizers/exif_spec.rb' - 'spec/lib/gitlab/sanitizers/svg_spec.rb' + - 'spec/lib/gitlab/search/abuse_detection_spec.rb' - 'spec/lib/gitlab/search/found_blob_spec.rb' - 'spec/lib/gitlab/search_results_spec.rb' - 'spec/lib/gitlab/serializer/pagination_spec.rb' @@ -3484,6 +3498,7 @@ Layout/LineLength: - 'spec/mailers/emails/service_desk_spec.rb' - 'spec/mailers/notify_spec.rb' - 'spec/migrations/active_record/schema_spec.rb' + - 'spec/migrations/db/post_migrate/20240319005754_swap_columns_for_upstream_pipeline_id_between_ci_builds_and_ci_pipelines_spec.rb' - 'spec/models/active_session_spec.rb' - 'spec/models/alert_management/alert_spec.rb' - 'spec/models/analytics/cycle_analytics/aggregation_spec.rb' @@ -3835,6 +3850,7 @@ Layout/LineLength: - 'spec/requests/projects/merge_requests/context_commit_diffs_spec.rb' - 'spec/requests/projects/merge_requests_discussions_spec.rb' - 'spec/requests/projects/merge_requests_spec.rb' + - 'spec/requests/projects/releases_controller_spec.rb' - 'spec/requests/projects/settings/access_tokens_controller_spec.rb' - 'spec/requests/projects/tags_controller_spec.rb' - 'spec/requests/projects_controller_spec.rb' @@ -4129,6 +4145,7 @@ Layout/LineLength: - 'spec/support/helpers/live_debugger.rb' - 'spec/support/helpers/login_helpers.rb' - 'spec/support/helpers/merge_request_diff_helpers.rb' + - 'spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb' - 'spec/support/helpers/prometheus_helpers.rb' - 'spec/support/helpers/seed_repo.rb' - 'spec/support/helpers/selection_helper.rb' @@ -4171,6 +4188,7 @@ Layout/LineLength: - 'spec/support/shared_examples/features/editable_merge_request_shared_examples.rb' - 'spec/support/shared_examples/features/error_tracking_shared_example.rb' - 'spec/support/shared_examples/features/manage_applications_shared_examples.rb' + - 'spec/support/shared_examples/features/milestone_editing_shared_examples.rb' - 'spec/support/shared_examples/features/page_description_shared_examples.rb' - 'spec/support/shared_examples/features/sidebar/sidebar_milestone_shared_examples.rb' - 'spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb' diff --git a/.rubocop_todo/lint/redundant_cop_disable_directive.yml b/.rubocop_todo/lint/redundant_cop_disable_directive.yml index dddd82cc01e..1fb50dbba97 100644 --- a/.rubocop_todo/lint/redundant_cop_disable_directive.yml +++ b/.rubocop_todo/lint/redundant_cop_disable_directive.yml @@ -1,56 +1,5 @@ --- # Cop supports --autocorrect. Lint/RedundantCopDisableDirective: - # Offense count: 62 - # Temporarily disabled due to too many offenses + # Temporarily disabled Enabled: false - Exclude: - - 'app/controllers/groups/milestones_controller.rb' - - 'app/models/concerns/integrations/base/integration.rb' - - 'app/models/work_items/type.rb' - - 'app/policies/issue_policy.rb' - - 'app/services/ci/runners/set_runner_associated_projects_service.rb' - - 'app/services/work_items/data_sync/clone_service.rb' - - 'app/services/work_items/data_sync/move_service.rb' - - 'db/migrate/20241021082113_create_partitioned_ci_runners.rb' - - 'db/migrate/20241024204816_create_partitioned_ci_runner_managers.rb' - - 'db/post_migrate/20241021163518_drop_user_canonical_emails_table.rb' - - 'ee/app/controllers/ee/projects/settings/ci_cd_controller.rb' - - 'ee/app/models/ee/audit_events/project_audit_event.rb' - - 'ee/app/services/search/zoekt/routing_service.rb' - - 'ee/app/services/vulnerabilities/statistics/adjustment_service.rb' - - 'ee/db/geo/migrate/20210504143244_add_verification_to_merge_request_diff_registry.rb' - - 'ee/spec/features/groups/settings/domain_verification_spec.rb' - - 'ee/spec/features/projects/mirror_spec.rb' - - 'ee/spec/lib/banzai/filter/references/iterations_cadence_reference_filter_spec.rb' - - 'ee/spec/lib/gitlab/elastic/client_spec.rb' - - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_secure_pipelines_metric_spec.rb' - - 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_security_scans_metric_spec.rb' - - 'ee/spec/support/shared_examples/features/dashboard_saml_reauth_banner_shared_examples.rb' - - 'ee/spec/views/admin/users/show.html.haml_spec.rb' - - 'lib/api/submodules.rb' - - 'lib/gitlab/popen/runner.rb' - - 'qa/qa/resource/user_runners.rb' - - 'qa/qa/service/docker_run/gitlab_runner.rb' - - 'qa/qa/vendor/one_password/cli.rb' - - 'spec/components/previews/pajamas/banner_component_preview.rb' - - 'spec/features/groups/participants_autocomplete_spec.rb' - - 'spec/features/merge_request/user_edits_mr_spec.rb' - - 'spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb' - - 'spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb' - - 'spec/lib/gitlab/merge_requests/message_generator_spec.rb' - - 'spec/lib/gitlab/search/abuse_detection_spec.rb' - - 'spec/migrations/db/post_migrate/20240319005754_swap_columns_for_upstream_pipeline_id_between_ci_builds_and_ci_pipelines_spec.rb' - - 'spec/models/concerns/encrypted_user_password_spec.rb' - - 'spec/requests/api/alert_management_alerts_spec.rb' - - 'spec/requests/projects/releases_controller_spec.rb' - - 'spec/scripts/setup/tests-metadata_spec.rb' - - 'spec/services/ci/create_pipeline_service/logger_spec.rb' - - 'spec/services/packages/pypi/create_package_service_spec.rb' - - 'spec/services/pages_domains/create_acme_order_service_spec.rb' - - 'spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb' - - 'spec/support/helpers/wait_for_requests.rb' - - 'spec/support/shared_examples/features/milestone_editing_shared_examples.rb' - - 'spec/support/shared_examples/models/concerns/protected_ref_access_shared_examples.rb' - - 'tooling/danger/sidekiq_queues.rb' - - 'tooling/danger/stable_branch.rb' diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml index a9c27e1e4b6..46aba57920d 100644 --- a/.rubocop_todo/rspec/context_wording.yml +++ b/.rubocop_todo/rspec/context_wording.yml @@ -705,7 +705,6 @@ RSpec/ContextWording: - 'ee/spec/services/milestones/update_service_spec.rb' - 'ee/spec/services/package_metadata/ingestion/ingestion_service_spec.rb' - 'ee/spec/services/personal_access_tokens/revoke_invalid_tokens_spec.rb' - - 'ee/spec/services/personal_access_tokens/rotation_verifier_service_spec.rb' - 'ee/spec/services/projects/alerting/notify_service_spec.rb' - 'ee/spec/services/projects/create_from_template_service_spec.rb' - 'ee/spec/services/projects/destroy_service_spec.rb' diff --git a/.rubocop_todo/rspec/example_without_description.yml b/.rubocop_todo/rspec/example_without_description.yml index 04e9e33117b..e52e80352db 100644 --- a/.rubocop_todo/rspec/example_without_description.yml +++ b/.rubocop_todo/rspec/example_without_description.yml @@ -198,7 +198,6 @@ RSpec/ExampleWithoutDescription: - 'ee/spec/services/package_metadata/compressed_package_data_object_spec.rb' - 'ee/spec/services/package_metadata/data_object_fabricator_spec.rb' - 'ee/spec/services/package_metadata/data_objects/cve_enrichment_spec.rb' - - 'ee/spec/services/personal_access_tokens/rotation_verifier_service_spec.rb' - 'ee/spec/services/sbom/ingestion/occurrence_map_spec.rb' - 'ee/spec/services/security/scan_result_policies/sync_findings_to_approval_rules_service_spec.rb' - 'ee/spec/services/security/scan_result_policies/update_approvals_service_spec.rb' diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index 127c06d680d..50d2951ce1a 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -9d2ddd160b10e94d8ff252071373d278bfaf4208 +9fd3ef87a54f73b17bf775d30b2153e8b242cf1c diff --git a/app/assets/javascripts/access_level/constants.js b/app/assets/javascripts/access_level/constants.js index c13d5fee5f0..105c9f392cf 100644 --- a/app/assets/javascripts/access_level/constants.js +++ b/app/assets/javascripts/access_level/constants.js @@ -4,6 +4,7 @@ import { __, s__ } from '~/locale'; export const ACCESS_LEVEL_NO_ACCESS_INTEGER = 0; export const ACCESS_LEVEL_MINIMAL_ACCESS_INTEGER = 5; export const ACCESS_LEVEL_GUEST_INTEGER = 10; +export const ACCESS_LEVEL_PLANNER_INTEGER = 15; export const ACCESS_LEVEL_REPORTER_INTEGER = 20; export const ACCESS_LEVEL_DEVELOPER_INTEGER = 30; export const ACCESS_LEVEL_MAINTAINER_INTEGER = 40; @@ -14,6 +15,7 @@ export const ACCESS_LEVEL_ADMIN_INTEGER = 60; export const ACCESS_LEVEL_NO_ACCESS_STRING = 'NO_ACCESS'; export const ACCESS_LEVEL_MINIMAL_ACCESS_STRING = 'MINIMAL_ACCESS'; export const ACCESS_LEVEL_GUEST_STRING = 'GUEST'; +export const ACCESS_LEVEL_PLANNER_STRING = 'PLANNER'; export const ACCESS_LEVEL_REPORTER_STRING = 'REPORTER'; export const ACCESS_LEVEL_DEVELOPER_STRING = 'DEVELOPER'; export const ACCESS_LEVEL_MAINTAINER_STRING = 'MAINTAINER'; @@ -23,6 +25,7 @@ export const ACCESS_LEVELS_INTEGER_TO_STRING = { [ACCESS_LEVEL_NO_ACCESS_INTEGER]: ACCESS_LEVEL_NO_ACCESS_STRING, [ACCESS_LEVEL_MINIMAL_ACCESS_INTEGER]: ACCESS_LEVEL_MINIMAL_ACCESS_STRING, [ACCESS_LEVEL_GUEST_INTEGER]: ACCESS_LEVEL_GUEST_STRING, + [ACCESS_LEVEL_PLANNER_INTEGER]: ACCESS_LEVEL_PLANNER_STRING, [ACCESS_LEVEL_REPORTER_INTEGER]: ACCESS_LEVEL_REPORTER_STRING, [ACCESS_LEVEL_DEVELOPER_INTEGER]: ACCESS_LEVEL_DEVELOPER_STRING, [ACCESS_LEVEL_MAINTAINER_INTEGER]: ACCESS_LEVEL_MAINTAINER_STRING, @@ -32,6 +35,7 @@ export const ACCESS_LEVELS_INTEGER_TO_STRING = { const ACCESS_LEVEL_NO_ACCESS = __('No access'); const ACCESS_LEVEL_MINIMAL_ACCESS = __('Minimal Access'); const ACCESS_LEVEL_GUEST = __('Guest'); +const ACCESS_LEVEL_PLANNER = __('Planner'); const ACCESS_LEVEL_REPORTER = __('Reporter'); const ACCESS_LEVEL_DEVELOPER = __('Developer'); const ACCESS_LEVEL_MAINTAINER = __('Maintainer'); @@ -56,6 +60,16 @@ export const BASE_ROLES = [ 'MemberRole|The Guest role is for users who need visibility into a project or group but should not have the ability to make changes, such as external stakeholders.', ), }, + { + value: 'PLANNER', + text: ACCESS_LEVEL_PLANNER, + accessLevel: ACCESS_LEVEL_PLANNER_INTEGER, + memberRoleId: null, + occupiesSeat: true, + description: s__( + 'MemberRole|The Planner role is suitable for team members who need to manage projects and track work items but do not need to contribute code, such as project managers and scrum masters.', + ), + }, { value: 'REPORTER', text: ACCESS_LEVEL_REPORTER, diff --git a/app/assets/javascripts/branches/components/sort_dropdown.vue b/app/assets/javascripts/branches/components/sort_dropdown.vue index 9c94aefa22e..8173cdafcfa 100644 --- a/app/assets/javascripts/branches/components/sort_dropdown.vue +++ b/app/assets/javascripts/branches/components/sort_dropdown.vue @@ -45,6 +45,10 @@ export default { urlParams.search = this.searchTerm.length > 0 ? this.searchTerm : null; + if (urlParams.search) { + urlParams.state = 'all'; + } + const newUrl = mergeUrlParams(urlParams, this.projectBranchesFilteredPath); visitUrl(newUrl); }, diff --git a/app/assets/javascripts/pages/dashboard/todos/index/index.js b/app/assets/javascripts/pages/dashboard/todos/index/index.js index 2fe90c24e77..5a7f6f362cd 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/index.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/index.js @@ -1,3 +1,8 @@ +import initTodosApp from '~/todos'; import Todos from './todos'; -new Todos(); // eslint-disable-line no-new +if (gon.features.todosVueApplication) { + initTodosApp(); +} else { + new Todos(); // eslint-disable-line no-new +} diff --git a/app/assets/javascripts/pages/dashboard/todos/vue/index.js b/app/assets/javascripts/pages/dashboard/todos/vue/index.js deleted file mode 100644 index 3829b9845df..00000000000 --- a/app/assets/javascripts/pages/dashboard/todos/vue/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import initTodosApp from '~/todos'; - -initTodosApp(); diff --git a/app/assets/javascripts/pages/shared/wikis/components/clone_wiki_modal.vue b/app/assets/javascripts/pages/shared/wikis/components/clone_wiki_modal.vue index de31b2c7ad4..51a935b4809 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/clone_wiki_modal.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/clone_wiki_modal.vue @@ -11,16 +11,12 @@ import { } from '@gitlab/ui'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { getHTTPProtocol } from '~/lib/utils/url_utility'; -import { __, s__, sprintf } from '~/locale'; +import { __, sprintf } from '~/locale'; export default { i18n: { steps: { - step1: s__('WikiClone|Step 1: Clone repository'), - step2: s__('WikiClone|Step 2: Install and start Gollum'), - step2Directory: s__('WikiClone|Go to directory'), - step2Install: s__('WikiClone|Install Gollum'), - step2Start: s__('WikiClone|Start Gollum and edit locally'), + step1: __('Clone repository'), }, cloneWithSsh: __('Clone with SSH'), copyToClipboard: __('Copy to clipboard'), @@ -62,15 +58,6 @@ export default { cloneHttpUrlDisplay() { return `git clone ${this.cloneHttpUrl}`; // eslint-disable-line @gitlab/require-i18n-strings }, - directoryCommand() { - return `cd ${this.wikiPath}`; // eslint-disable-line @gitlab/require-i18n-strings - }, - installCommand() { - return 'gem install gollum'; // eslint-disable-line @gitlab/require-i18n-strings - }, - gollumCommand() { - return 'gollum'; - }, listItem() { return { text: __('Clone repository'), @@ -156,65 +143,6 @@ export default { -
-

- {{ $options.i18n.steps.step2 }} -

- - - - - - - - - - - - - - - -
diff --git a/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.stories.js b/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.stories.js index 19b7958a89b..179b5c1a57f 100644 --- a/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.stories.js +++ b/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.stories.js @@ -3,7 +3,7 @@ import CustomizableDashboard from './customizable_dashboard.vue'; export default { component: CustomizableDashboard, - title: '~/vue_shared/components/customizable_dashboard', + title: 'vue_shared/components/customizable_dashboard', }; const Template = (args, { argTypes }) => ({ diff --git a/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.stories.js b/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.stories.js index a1755400b07..962f2c102be 100644 --- a/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.stories.js +++ b/app/assets/javascripts/vue_shared/components/customizable_dashboard/panels_base.stories.js @@ -4,7 +4,7 @@ import PanelsBase from './panels_base.vue'; export default { component: PanelsBase, - title: '~/vue_shared/components/panels_base', + title: 'vue_shared/components/panels_base', }; const Template = (args, { argTypes }) => ({ diff --git a/app/assets/stylesheets/page_bundles/environments.scss b/app/assets/stylesheets/page_bundles/environments.scss index 39f01d944d3..f81a018b6e8 100644 --- a/app/assets/stylesheets/page_bundles/environments.scss +++ b/app/assets/stylesheets/page_bundles/environments.scss @@ -34,14 +34,6 @@ text-align: center; } - .no-btn { - border: 0; - background: none; - outline: none; - width: 100%; - text-align: left; - } - .environment-child-row { padding-left: 20px; } @@ -90,43 +82,6 @@ } } -.metric-area { - opacity: 0.25; -} - -.rect-text-metric { - fill: var(--white, $white); - stroke-width: 1; - stroke: var(--gray-600, $gray-600); -} - -.rect-axis-text { - fill: var(--white, $white); -} - -.selected-metric-line { - stroke: var(--gray-900, $gray-900); - stroke-width: 1; -} - -.deployment-line { - stroke: var(--white, $white); - stroke-width: 1; -} - -.divider-line { - stroke-width: 1; - stroke: var(--gray-600, $gray-600); -} - -.environments-actions { - .external-url, - .monitoring-url, - .terminal-button { - width: 38px; - } -} - /** * Deploy boards */ @@ -170,10 +125,6 @@ margin-left: 10px; } - &.deploy-board-error-message { - justify-content: center; - } - .deploy-board-empty-state-text { order: 2; flex-wrap: wrap; diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 2ba3cc682f1..7a4fb38f009 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -126,15 +126,9 @@ overflow: visible; } - - div:has(> .diff-file-is-active) { - box-shadow: 0 0 0 1px var(--gl-focus-ring-outer-color); - border-radius: #{$border-radius + $gl-spacing-scale-1}; - padding: $gl-spacing-scale-1; - - .diff-file-is-active { - margin-bottom: 0; - } + .diff-file-is-active { + outline: 1px solid var(--gl-focus-ring-outer-color); + outline-offset: 2px; } } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index e5a2656d8e5..8345156eceb 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -191,7 +191,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController allowed_to_push: [:access_level] } ] }, - :can_create_organization, :lets_encrypt_notification_email, :lets_encrypt_terms_of_service_accepted, :domain_denylist_file, diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index bdd38e2e438..05f57db74fd 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -13,17 +13,26 @@ class Dashboard::TodosController < Dashboard::ApplicationController urgency :low def index - @sort = pagination_params[:sort] - @todos = @todos.page(pagination_params[:page]) - @todos = @todos.with_entity_associations + push_frontend_feature_flag(:todos_vue_application, current_user) - return if redirect_out_of_range(@todos, todos_page_count(@todos)) + # When removing the `todos_vue_application`, also drop the #vue method below + if Feature.enabled?(:todos_vue_application, current_user) + render :vue + else + @sort = pagination_params[:sort] + @todos = @todos.page(pagination_params[:page]) + @todos = @todos.with_entity_associations - @allowed_todos = ::Todos::AllowedTargetFilterService.new(@todos, current_user).execute + return if redirect_out_of_range(@todos, todos_page_count(@todos)) + + @allowed_todos = ::Todos::AllowedTargetFilterService.new(@todos, current_user).execute + end end + # To be removed along with the `todos_vue_application` feature flag. + # Also make sure to remove the corresponding route in `config/routes/dashboard.rb`. def vue - redirect_to(dashboard_todos_path, status: :found) unless Feature.enabled?(:todos_vue_application, current_user) + redirect_to(dashboard_todos_path, status: :found) end def destroy diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 0c91631ab7b..9855ce49864 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -70,7 +70,7 @@ class Groups::MilestonesController < Groups::ApplicationController render json: { errors: [ format( - _("Someone edited this %{model_name} at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + _("Someone edited this %{model_name} at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs."), model_name: _('milestone') ) ] diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index 22810cdc37e..71da9bdcbc4 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -52,8 +52,7 @@ class RootController < Dashboard::ProjectsController when 'groups' redirect_to(dashboard_groups_path) when 'todos' - redirect_to(Feature.enabled?(:todos_vue_application, - current_user) ? vue_dashboard_todos_path : dashboard_todos_path) + redirect_to(dashboard_todos_path) when 'issues' redirect_to(issues_dashboard_path(assignee_username: current_user.username)) when 'merge_requests' diff --git a/app/finders/issues/confidentiality_filter.rb b/app/finders/issues/confidentiality_filter.rb index 282e56637fa..c277635f5f5 100644 --- a/app/finders/issues/confidentiality_filter.rb +++ b/app/finders/issues/confidentiality_filter.rb @@ -2,7 +2,7 @@ module Issues class ConfidentialityFilter < Issuables::BaseFilter - CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER + CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::PLANNER def initialize(current_user:, parent:, assignee_filter:, related_groups: nil, **kwargs) @current_user = current_user diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb index 7c0b79a404a..1bc621a2e3e 100644 --- a/app/graphql/resolvers/projects_resolver.rb +++ b/app/graphql/resolvers/projects_resolver.rb @@ -35,6 +35,10 @@ module Resolvers required: false, description: 'Filter projects by programming language name (case insensitive). For example: "css" or "ruby".' + before_connection_authorization do |projects, current_user| + ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute + end + def resolve_with_lookahead(**args) validate_args!(args) diff --git a/app/graphql/types/member_access_level_enum.rb b/app/graphql/types/member_access_level_enum.rb index 1789067ce0b..96e30349551 100644 --- a/app/graphql/types/member_access_level_enum.rb +++ b/app/graphql/types/member_access_level_enum.rb @@ -10,6 +10,7 @@ module Types end value 'GUEST', value: Gitlab::Access::GUEST, description: descriptions[Gitlab::Access::GUEST] + value 'PLANNER', value: Gitlab::Access::PLANNER, description: descriptions[Gitlab::Access::PLANNER] value 'REPORTER', value: Gitlab::Access::REPORTER, description: descriptions[Gitlab::Access::REPORTER] value 'DEVELOPER', value: Gitlab::Access::DEVELOPER, description: descriptions[Gitlab::Access::DEVELOPER] value 'MAINTAINER', value: Gitlab::Access::MAINTAINER, description: descriptions[Gitlab::Access::MAINTAINER] diff --git a/app/graphql/types/work_items/widgets/development_type.rb b/app/graphql/types/work_items/widgets/development_type.rb index d61045e621e..a709289d05b 100644 --- a/app/graphql/types/work_items/widgets/development_type.rb +++ b/app/graphql/types/work_items/widgets/development_type.rb @@ -16,7 +16,7 @@ module Types Types::WorkItems::ClosingMergeRequestType.connection_type, null: true, description: 'Merge requests that will close the work item when merged.' - field :related_branches, # rubocop:disable GraphQL/ExtractType -- no need to extract to related + field :related_branches, Types::WorkItems::RelatedBranchType.connection_type, calls_gitaly: true, description: 'Branches that have referred to the work item, but do not have an associated merge request.', diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 272fbb15587..c9b9f55be4b 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -502,6 +502,7 @@ module ApplicationSettingsHelper :pipeline_limit_per_project_user_sha, :invitation_flow_enforcement, :can_create_group, + :can_create_organization, :bulk_import_concurrent_pipeline_batch_limit, :concurrent_relation_batch_export_limit, :bulk_import_enabled, diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7feb19ad036..cba371d591f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -771,6 +771,7 @@ module ProjectsHelper Gitlab::Access::NO_ACCESS => _('No access'), Gitlab::Access::MINIMAL_ACCESS => _("Minimal Access"), Gitlab::Access::GUEST => _('Guest'), + Gitlab::Access::PLANNER => _('Planner'), Gitlab::Access::REPORTER => _('Reporter'), Gitlab::Access::DEVELOPER => _('Developer'), Gitlab::Access::MAINTAINER => _('Maintainer'), diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index e5083bf9982..8e302525086 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -98,11 +98,7 @@ module SidebarsHelper issues_dashboard_path: issues_dashboard_path(assignee_username: user.username), merge_request_dashboard_path: user.merge_request_dashboard_enabled? ? merge_requests_dashboard_path : nil, - todos_dashboard_path: if Feature.enabled?(:todos_vue_application, user) - vue_dashboard_todos_path - else - dashboard_todos_path - end, + todos_dashboard_path: dashboard_todos_path, create_new_menu_groups: create_new_menu_groups(group: group, project: project), merge_request_menu: create_merge_request_menu(user), diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index bd1aef6a61f..9a45b5d6b51 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -122,8 +122,14 @@ module Ci project = options&.dig(:trigger, :project) next unless project - scoped_variables.to_runner_variables.then do |all_variables| - ::ExpandVariables.expand(project, all_variables) + if ci_optimize_memory_for_variables_enabled? + scoped_variables.to_hash_variables.then do |all_variables| + ::ExpandVariables.expand(project, all_variables) + end + else + scoped_variables.to_runner_variables.then do |all_variables| + ::ExpandVariables.expand(project, all_variables) + end end end end @@ -223,8 +229,14 @@ module Ci branch = options&.dig(:trigger, :branch) return unless branch - scoped_variables.to_runner_variables.then do |all_variables| - ::ExpandVariables.expand(branch, all_variables) + if ci_optimize_memory_for_variables_enabled? + scoped_variables.to_hash_variables.then do |all_variables| + ::ExpandVariables.expand(branch, all_variables) + end + else + scoped_variables.to_runner_variables.then do |all_variables| + ::ExpandVariables.expand(branch, all_variables) + end end end @@ -339,6 +351,11 @@ module Ci } } end + + def ci_optimize_memory_for_variables_enabled? + ::Feature.enabled?(:ci_optimize_memory_for_variables, project) + end + strong_memoize_attr :ci_optimize_memory_for_variables_enabled? end end diff --git a/app/models/concerns/integrations/base/integration.rb b/app/models/concerns/integrations/base/integration.rb index dd6fd217ef3..4d7fe31c956 100644 --- a/app/models/concerns/integrations/base/integration.rb +++ b/app/models/concerns/integrations/base/integration.rb @@ -692,7 +692,7 @@ module Integrations def api_field_names fields # rubocop:disable Style/NumberedParameters -- existing code moved as is - .reject { _1[:type] == :password || _1[:name] == 'webhook' || (_1.key?(:if) && _1[:if] != true) } # rubocop:disable Style/NumberedParameters -- existing code moved as is + .reject { _1[:type] == :password || _1[:name] == 'webhook' || (_1.key?(:if) && _1[:if] != true) } .pluck(:name) # rubocop:disable Database/AvoidUsingPluckWithoutLimit -- existing code moved as is end diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb index fc2d38de268..91b20e644dc 100644 --- a/app/models/concerns/token_authenticatable_strategies/base.rb +++ b/app/models/concerns/token_authenticatable_strategies/base.rb @@ -2,14 +2,10 @@ module TokenAuthenticatableStrategies class Base - RANDOM_BYTES_LENGTH = 16 + TRUE_PROC = ->(_) { true } attr_reader :klass, :token_field, :expires_at_field, :options - def self.random_bytes - SecureRandom.random_bytes(RANDOM_BYTES_LENGTH) - end - def initialize(klass, token_field, options) @klass = klass @token_field = token_field @@ -94,6 +90,8 @@ module TokenAuthenticatableStrategies private + # If a `format_with_prefix` option is provided, it applies and returns the formatted token. + # Otherwise, default implementation returns the token as-is def prefix_for(token_owner_record) case prefix_option = options[:format_with_prefix] when nil @@ -105,18 +103,9 @@ module TokenAuthenticatableStrategies end end - # If a `format_with_prefix` option is provided, it applies and returns the formatted token. - # Otherwise, default implementation returns the token as-is - def format_token(token_owner_record, token) - prefix = prefix_for(token_owner_record) - - prefix ? "#{prefix}#{token}" : token - end - def write_new_token(token_owner_record) new_token = generate_available_token(token_owner_record) - formatted_token = format_token(token_owner_record, new_token) - set_token(token_owner_record, formatted_token) + set_token(token_owner_record, new_token) if expirable? token_owner_record[@expires_at_field] = @options[:expires_at].to_proc.call(token_owner_record) @@ -135,33 +124,30 @@ module TokenAuthenticatableStrategies end def generate_token(token_owner_record) - if @options[:token_generator] - @options[:token_generator].call + if token_generator_proc + "#{prefix_for(token_owner_record)}#{token_generator_proc.call}" # TODO: Make all tokens routable by default: https://gitlab.com/gitlab-org/gitlab/-/issues/500016 elsif generate_routable_token?(token_owner_record) - generate_routable_payload(@options[:routable_token], token_owner_record) + RoutableTokenGenerator.new( + token_owner_record, + routing_payload: options.dig(:routable_token, :payload), + prefix: prefix_for(token_owner_record) + ).generate_token else - Devise.friendly_token + "#{prefix_for(token_owner_record)}#{Devise.friendly_token}" end end def generate_routable_token?(token_owner_record) - @options[:routable_token] && token_owner_record.respond_to?(:user) && Feature.enabled?(:routable_token, token_owner_record.user) + @options.dig(:routable_token, :payload) && routing_condition_proc.call(token_owner_record) end - def default_routing_payload_hash - { - c: Settings.cell[:id]&.to_s(36), - r: self.class.random_bytes - } + def token_generator_proc + @options[:token_generator] end - def generate_routable_payload(routable_parts, token_owner_record) - payload_hash = default_routing_payload_hash.merge( - routable_parts.transform_values { |generator| generator.call(token_owner_record) } - ).compact_blank - - Base64.urlsafe_encode64(payload_hash.sort.map { |k, v| "#{k}:#{v}" }.join("\n"), padding: false) + def routing_condition_proc + @options.dig(:routable_token, :if) || TRUE_PROC end def relation(unscoped) diff --git a/app/models/concerns/token_authenticatable_strategies/routable_token_generator.rb b/app/models/concerns/token_authenticatable_strategies/routable_token_generator.rb new file mode 100644 index 00000000000..671506b2043 --- /dev/null +++ b/app/models/concerns/token_authenticatable_strategies/routable_token_generator.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +module TokenAuthenticatableStrategies + class RoutableTokenGenerator + RANDOM_BYTES_LENGTH = 16 + BASE64_PAYLOAD_LENGTH_HOLDER_BYTES = 2 + CRC_BYTES = 7 + VALID_ROUTING_KEYS = %i[c g o p u].freeze + REQUIRED_ROUTING_KEYS = %i[o].freeze + MAXIMUM_SIZE_OF_ROUTING_PAYLOAD = 159 + DEFAULT_ROUTING_PAYLOAD_HASH = + { + c: ->(_) { Settings.cell[:id] } + }.freeze + + PayloadTooLarge = Class.new(RuntimeError) + MissingRequiredRoutingKeys = Class.new(ArgumentError) + InvalidRoutingKeys = Class.new(ArgumentError) + + def self.random_bytes(length) + SecureRandom.random_bytes(length) + end + + attr_reader :token_owner_record, :routing_payload, :prefix + + def initialize(token_owner_record, routing_payload:, prefix: '') + @token_owner_record = token_owner_record + @routing_payload = routing_payload + @prefix = prefix + + validate_routing_keys! + end + + def generate_token + routing_hash + .then { |routing_hash| build_payload(routing_hash) } + .then { |payload| check_payload_size!(payload) } + .then { |payload| encode_payload(payload, self.class.random_bytes(RANDOM_BYTES_LENGTH)) } + .then { |encoded_payload| append_crc(encoded_payload) } + end + + private + + def validate_routing_keys! + check_required_routing_keys! + check_invalid_routing_keys! + end + + def routing_hash + routing_payload + .merge(DEFAULT_ROUTING_PAYLOAD_HASH) + .transform_values { |generator| format_value(generator.call(token_owner_record)) } + .compact_blank + .sort + end + + def build_payload(routing_hash) + routing_hash.map { |k, v| "#{k}:#{v}" }.join("\n") + end + + def format_value(value) + value.is_a?(Integer) ? value.to_s(36) : value + end + + def encode_payload(payload, random_bytes) + encodable_payload = "#{payload}#{random_bytes}#{[random_bytes.size].pack('C')}" + base64_payload = Base64.urlsafe_encode64(encodable_payload, padding: false) + base64_payload_length = base64_payload.size.to_s(36).rjust(BASE64_PAYLOAD_LENGTH_HOLDER_BYTES, '0') + "#{prefix}#{base64_payload}.#{base64_payload_length}" + end + + def append_crc(encoded_payload) + crc = Zlib.crc32(encoded_payload).to_s(36).rjust(CRC_BYTES, '0') + "#{encoded_payload}#{crc}" + end + + def check_required_routing_keys! + missing_keys = REQUIRED_ROUTING_KEYS - routing_payload.keys + return if missing_keys.empty? + + raise MissingRequiredRoutingKeys, missing_keys_error_message(missing_keys) + end + + def check_invalid_routing_keys! + invalid_keys = routing_payload.keys - VALID_ROUTING_KEYS + return if invalid_keys.empty? + + raise InvalidRoutingKeys, invalid_keys_error_message(invalid_keys) + end + + def check_payload_size!(payload) + return payload if payload.size <= MAXIMUM_SIZE_OF_ROUTING_PAYLOAD + + raise PayloadTooLarge, payload_size_error_message(payload.size) + end + + def missing_keys_error_message(missing_keys) + "Missing required routing keys: #{missing_keys.map(&:inspect).join(', ')}. " \ + "Required routing keys are: #{REQUIRED_ROUTING_KEYS.map(&:inspect).join(', ')}." + end + + def invalid_keys_error_message(invalid_keys) + "Invalid routing keys: #{invalid_keys.map(&:inspect).join(', ')}. " \ + "Valid routing keys are: #{VALID_ROUTING_KEYS.map(&:inspect).join(', ')}." + end + + def payload_size_error_message(size) + "Routing payload is too big: #{size}. " \ + "Maximum size is #{MAXIMUM_SIZE_OF_ROUTING_PAYLOAD}." + end + end +end diff --git a/app/models/group.rb b/app/models/group.rb index b17c85dbe5c..fd9db4013bc 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -553,6 +553,10 @@ class Group < Namespace add_member(user, :guest, current_user: current_user) end + def add_planner(user, current_user = nil) + add_member(user, :planner, current_user: current_user) + end + def add_reporter(user, current_user = nil) add_member(user, :reporter, current_user: current_user) end diff --git a/app/models/issue.rb b/app/models/issue.rb index 00c49f93683..3dda7d58045 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -835,7 +835,7 @@ class Issue < ApplicationRecord elsif project.personal? && project.team.owner?(user) true elsif confidential? && !assignee_or_author?(user) - project.member?(user, Gitlab::Access::REPORTER) + project.member?(user, Gitlab::Access::PLANNER) elsif project.public? || (project.internal? && !user.external?) project.feature_available?(:issues, user) else @@ -848,7 +848,7 @@ class Issue < ApplicationRecord return false unless namespace.is_a?(::Group) if confidential? && !assignee_or_author?(user) - namespace.member?(user, Gitlab::Access::REPORTER) + namespace.member?(user, Gitlab::Access::PLANNER) else namespace.member?(user) end diff --git a/app/models/member.rb b/app/models/member.rb index 880ace110d0..ad554ed43d7 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -189,6 +189,7 @@ class Member < ApplicationRecord scope :has_access, -> { active.where('access_level > 0') } scope :guests, -> { active.where(access_level: GUEST) } + scope :planners, -> { active.where(access_level: PLANNER) } scope :reporters, -> { active.where(access_level: REPORTER) } scope :developers, -> { active.where(access_level: DEVELOPER) } scope :maintainers, -> { active.where(access_level: MAINTAINER) } diff --git a/app/models/project.rb b/app/models/project.rb index 4910b37616f..cd4e04ddfe5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -538,7 +538,7 @@ class Project < ApplicationRecord with_options to: :team do delegate :members, prefix: true delegate :add_member, :add_members, :member? - delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role + delegate :add_guest, :add_planner, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role delegate :has_user? end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 5aac6062078..2ef643e7e18 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -11,6 +11,10 @@ class ProjectTeam add_member(user, :guest, current_user: current_user) end + def add_planner(user, current_user: nil) + add_member(user, :planner, current_user: current_user) + end + def add_reporter(user, current_user: nil) add_member(user, :reporter, current_user: current_user) end @@ -89,6 +93,10 @@ class ProjectTeam @guests ||= fetch_members(Gitlab::Access::GUEST) end + def planners + @planners ||= fetch_members(Gitlab::Access::PLANNER) + end + def reporters @reporters ||= fetch_members(Gitlab::Access::REPORTER) end @@ -152,6 +160,10 @@ class ProjectTeam max_member_access(user.id) == Gitlab::Access::GUEST end + def planner?(user) + max_member_access(user.id) == Gitlab::Access::PLANNER + end + def reporter?(user) max_member_access(user.id) == Gitlab::Access::REPORTER end diff --git a/app/models/system/broadcast_message.rb b/app/models/system/broadcast_message.rb index 8bcaf4a1e58..28fd9c0422c 100644 --- a/app/models/system/broadcast_message.rb +++ b/app/models/system/broadcast_message.rb @@ -7,6 +7,7 @@ module System ALLOWED_TARGET_ACCESS_LEVELS = [ Gitlab::Access::GUEST, + Gitlab::Access::PLANNER, Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, diff --git a/app/models/user.rb b/app/models/user.rb index fef2aad2355..b43fbc6f988 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1424,7 +1424,7 @@ class User < ApplicationRecord # # This logic is duplicated from `Ability#project_abilities` into a SQL form. def projects_where_can_admin_issues - authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled + authorized_projects(Gitlab::Access::PLANNER).non_archived.with_issues_enabled end # rubocop: disable CodeReuse/ServiceClass diff --git a/app/models/work_items/type.rb b/app/models/work_items/type.rb index 2bf41c206d8..3803999400b 100644 --- a/app/models/work_items/type.rb +++ b/app/models/work_items/type.rb @@ -41,7 +41,7 @@ module WorkItems issue: { name: TYPE_NAMES[:issue], icon_name: 'issue-type-issue', enum_value: 0, id: 1 }, incident: { name: TYPE_NAMES[:incident], icon_name: 'issue-type-incident', enum_value: 1, id: 2 }, test_case: { name: TYPE_NAMES[:test_case], icon_name: 'issue-type-test-case', enum_value: 2, id: 3 }, ## EE-only - requirement: { name: TYPE_NAMES[:requirement], icon_name: 'issue-type-requirements', enum_value: 3, id: 4 }, ## EE-only # rubocop:disable Layout/LineLength -- Only comment exceeds length + requirement: { name: TYPE_NAMES[:requirement], icon_name: 'issue-type-requirements', enum_value: 3, id: 4 }, ## EE task: { name: TYPE_NAMES[:task], icon_name: 'issue-type-task', enum_value: 4, id: 5 }, objective: { name: TYPE_NAMES[:objective], icon_name: 'issue-type-objective', enum_value: 5, id: 6 }, ## EE-only key_result: { name: TYPE_NAMES[:key_result], icon_name: 'issue-type-keyresult', enum_value: 6, id: 7 }, ## EE-only diff --git a/app/policies/board_policy.rb b/app/policies/board_policy.rb index eaffffd68af..b95232252e5 100644 --- a/app/policies/board_policy.rb +++ b/app/policies/board_policy.rb @@ -16,11 +16,11 @@ class BoardPolicy < BasePolicy enable :read_issue end - condition(:reporter_of_group_projects) do + condition(:planner_of_group_projects) do next unless @user group_projects_for(user: @user, group: @subject.resource_parent) - .visible_to_user_and_access_level(@user, ::Gitlab::Access::REPORTER) + .visible_to_user_and_access_level(@user, ::Gitlab::Access::PLANNER) .exists? end @@ -28,7 +28,7 @@ class BoardPolicy < BasePolicy enable :create_non_backlog_issues end - rule { is_group_board & reporter_of_group_projects }.policy do + rule { is_group_board & planner_of_group_projects }.policy do enable :create_non_backlog_issues end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 20915e0f42d..768206b9fda 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -13,6 +13,8 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy condition(:has_access) { access_level != GroupMember::NO_ACCESS } condition(:guest) { access_level >= GroupMember::GUEST } + # This is not a linear condition (some policies available for planner might not be available for higher access levels) + condition(:planner) { access_level == GroupMember::PLANNER } condition(:developer) { access_level >= GroupMember::DEVELOPER } condition(:owner) { access_level >= GroupMember::OWNER } condition(:maintainer) { access_level >= GroupMember::MAINTAINER } @@ -135,6 +137,21 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy enable :award_emoji end + rule { planner }.policy do + enable :planner_access + enable :guest_access + enable :admin_label + enable :admin_milestone + enable :admin_issue_board + enable :admin_issue_board_list + enable :admin_issue + enable :update_issue + enable :destroy_issue + enable :read_confidential_issues + enable :read_crm_organization + enable :read_crm_contact + end + rule { admin | organization_owner }.policy do enable :read_group end @@ -403,7 +420,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy rule { can?(:admin_group) | can?(:admin_runner) }.enable :admin_group_or_admin_runner # Should be matched with ProjectPolicy#read_internal_note - rule { admin | reporter }.enable :read_internal_note + rule { admin | reporter | planner }.enable :read_internal_note rule { can?(:remove_group) }.enable :view_edit_page diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb index 9af84818730..64d242d9373 100644 --- a/app/policies/issuable_policy.rb +++ b/app/policies/issuable_policy.rb @@ -12,6 +12,11 @@ class IssuablePolicy < BasePolicy @user && @subject.assignee_or_author?(@user) end + desc "User has planner or reporter access" + condition(:planner_or_reporter_access) do + can?(:reporter_access) || can?(:planner_access) + end + condition(:is_author) { @subject&.author == @user } condition(:is_incident) { @subject.incident_type_issue? } @@ -53,7 +58,7 @@ class IssuablePolicy < BasePolicy enable :admin_incident_management_timeline_event end - rule { can?(:reporter_access) }.policy do + rule { planner_or_reporter_access }.policy do enable :create_timelog end diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb index 20150cc7c3e..8a1f4895b0f 100644 --- a/app/policies/issue_policy.rb +++ b/app/policies/issue_policy.rb @@ -13,10 +13,12 @@ class IssuePolicy < IssuablePolicy false end + # rubocop:disable Cop/UserAdmin -- specifically check the admin attribute desc "User can read confidential issues" condition(:can_read_confidential) do - @user && (@user.admin? || can?(:reporter_access) || assignee_or_author?) # rubocop:disable Cop/UserAdmin + @user && (@user.admin? || planner_or_reporter_access? || assignee_or_author?) end + # rubocop:enable Cop/UserAdmin desc "Project belongs to a group, crm is enabled and user can read contacts in source group" condition(:can_read_crm_contacts, scope: :subject) do @@ -45,13 +47,11 @@ class IssuePolicy < IssuablePolicy end end - # rubocop:disable Gitlab/FeatureFlagWithoutActor -- this is a on/off toggle # group level issues license for now is equivalent to epics license. We'll have to migrate epics license to # work items context once epics are fully migrated to work items. condition(:group_level_issues_license_available) do epics_license_available? end - # rubocop:enable Gitlab/FeatureFlagWithoutActor rule { group_issue & can?(:read_group) }.policy do enable :create_note @@ -145,7 +145,7 @@ class IssuePolicy < IssuablePolicy enable :set_issue_crm_contacts end - rule { can?(:reporter_access) }.policy do + rule { planner_or_reporter_access }.policy do enable :mark_note_as_internal end diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb index 27bcd816a08..a76770ee054 100644 --- a/app/policies/merge_request_policy.rb +++ b/app/policies/merge_request_policy.rb @@ -44,7 +44,7 @@ class MergeRequestPolicy < IssuablePolicy enable :set_merge_request_metadata end - rule { can?(:reporter_access) }.policy do + rule { planner_or_reporter_access }.policy do enable :mark_note_as_internal end diff --git a/app/policies/namespaces/group_project_namespace_shared_policy.rb b/app/policies/namespaces/group_project_namespace_shared_policy.rb index 295ab59076a..650db3ddc0f 100644 --- a/app/policies/namespaces/group_project_namespace_shared_policy.rb +++ b/app/policies/namespaces/group_project_namespace_shared_policy.rb @@ -19,6 +19,10 @@ module Namespaces enable :reopen_issue end + rule { can?(:planner_access) }.policy do + enable :reopen_issue + end + rule { can?(:guest_access) }.policy do enable :read_work_item enable :read_issue diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index e08f9d470a1..8417ad406f8 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -15,6 +15,10 @@ class ProjectPolicy < BasePolicy desc "User has guest access" condition(:guest) { team_member? } + # This is not a linear condition (some policies available for planner might not be available for higher access levels) + desc "User has planner access" + condition(:planner) { team_access_level == Gitlab::Access::PLANNER } + desc "User has reporter access" condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER } @@ -311,6 +315,11 @@ class ProjectPolicy < BasePolicy Feature.enabled?(:hide_projects_of_banned_users) && @subject.created_and_owned_by_banned_user? end + desc "User has either planner or reporter access" + condition(:planner_or_reporter_access) do + can?(:reporter_access) || can?(:planner_access) + end + # `:read_project` may be prevented in EE, but `:read_project_for_iids` should # not. rule { guest | admin | organization_owner }.enable :read_project_for_iids @@ -320,6 +329,7 @@ class ProjectPolicy < BasePolicy rule { can?(:read_all_resources) }.enable :read_confidential_issues rule { guest }.enable :guest_access + rule { planner }.enable :planner_access rule { reporter }.enable :reporter_access rule { developer }.enable :developer_access rule { maintainer }.enable :maintainer_access @@ -329,6 +339,7 @@ class ProjectPolicy < BasePolicy rule { can?(:owner_access) }.policy do enable :guest_access + enable :planner_access enable :reporter_access enable :developer_access enable :maintainer_access @@ -379,6 +390,29 @@ class ProjectPolicy < BasePolicy enable :read_upload end + rule { can?(:planner_access) }.policy do + enable :guest_access + enable :admin_issue_board + enable :admin_issue_board_list + enable :update_issue + enable :reopen_issue + enable :admin_issue + enable :destroy_issue + enable :read_confidential_issues + enable :create_design + enable :update_design + enable :move_design + enable :destroy_design + enable :admin_label + enable :admin_milestone + enable :download_wiki_code + enable :create_wiki + enable :admin_wiki + enable :read_merge_request + enable :download_code + enable :export_work_items + end + rule { can?(:reporter_access) & can?(:create_issue) }.enable :create_incident rule { can?(:reporter_access) & can?(:read_environment) }.enable :read_freeze_period @@ -503,8 +537,8 @@ class ProjectPolicy < BasePolicy rule { owner | admin | organization_owner | guest | group_member | group_requester }.prevent :request_access rule { ~request_access_enabled }.prevent :request_access - rule { can?(:developer_access) & can?(:create_issue) }.enable :import_issues - rule { can?(:reporter_access) & can?(:create_work_item) }.enable :import_work_items + rule { (can?(:planner_access) | can?(:developer_access)) & can?(:create_issue) }.enable :import_issues + rule { planner_or_reporter_access & can?(:create_work_item) }.enable :import_work_items rule { can?(:developer_access) }.policy do enable :create_package @@ -747,6 +781,7 @@ class ProjectPolicy < BasePolicy # If this project is public or internal we want to prevent all aside from a few public policies rule { public_or_internal & ~project_allowed_for_job_token_by_scope }.policy do prevent :guest_access + prevent :planner_access prevent :public_access prevent :reporter_access prevent :developer_access @@ -854,6 +889,7 @@ class ProjectPolicy < BasePolicy # `:read_project_for_iids` is not prevented by this condition, as it is # used for cross-project reference checks. prevent :guest_access + prevent :planner_access prevent :public_access prevent :public_user_access prevent :reporter_access @@ -1013,7 +1049,7 @@ class ProjectPolicy < BasePolicy end # Should be matched with GroupPolicy#read_internal_note - rule { admin | can?(:reporter_access) }.enable :read_internal_note + rule { admin | planner_or_reporter_access }.enable :read_internal_note rule { can?(:developer_access) & namespace_catalog_available }.policy do enable :read_namespace_catalog @@ -1117,7 +1153,7 @@ class ProjectPolicy < BasePolicy return -1 if @user.nil? return -1 unless user_is_user? - lookup_access_level! + @team_access_level ||= lookup_access_level! end def lookup_access_level! diff --git a/app/services/ci/runners/set_runner_associated_projects_service.rb b/app/services/ci/runners/set_runner_associated_projects_service.rb index 9c18f672c24..ec79b0c433c 100644 --- a/app/services/ci/runners/set_runner_associated_projects_service.rb +++ b/app/services/ci/runners/set_runner_associated_projects_service.rb @@ -30,7 +30,7 @@ module Ci response = ServiceResponse.success runner.transaction do - current_project_ids = runner.project_ids # rubocop:disable CodeReuse/ActiveRecord -- reasonable use + current_project_ids = runner.project_ids response = associate_new_projects(new_project_ids, current_project_ids) response = disassociate_old_projects(new_project_ids, current_project_ids) if response.success? diff --git a/app/services/groups/open_issues_count_service.rb b/app/services/groups/open_issues_count_service.rb index 6ab2fda0893..70ef3daa08f 100644 --- a/app/services/groups/open_issues_count_service.rb +++ b/app/services/groups/open_issues_count_service.rb @@ -37,12 +37,14 @@ module Groups end def public_only? - !user_is_at_least_reporter? + # Although PLANNER is not a linear access level, it can be considered so for the purpose of issues visibility + # because the same permissions apply to all levels higher than Gitlab::Access::PLANNER + !user_is_at_least_planner? end - def user_is_at_least_reporter? - strong_memoize(:user_is_at_least_reporter) do - group.member?(user, Gitlab::Access::REPORTER) + def user_is_at_least_planner? + strong_memoize(:user_is_at_least_planner) do + group.member?(user, Gitlab::Access::PLANNER) end end diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb index 52a70637a5c..061720d44bd 100644 --- a/app/services/projects/open_issues_count_service.rb +++ b/app/services/projects/open_issues_count_service.rb @@ -21,12 +21,12 @@ module Projects end def public_only? - !user_is_at_least_reporter? + !user_is_at_least_planner? end - def user_is_at_least_reporter? - strong_memoize(:user_is_at_least_reporter) do - @project.member?(@user, Gitlab::Access::REPORTER) + def user_is_at_least_planner? + strong_memoize(:user_is_at_least_planner) do + @project.member?(@user, Gitlab::Access::PLANNER) end end @@ -57,7 +57,7 @@ module Projects end end - # We only show issues count including confidential for reporters, who are allowed to view confidential issues. + # We only show issues count including confidential for planners, who are allowed to view confidential issues. # This will still show a discrepancy on issues number but should be less than before. # Check https://gitlab.com/gitlab-org/gitlab-foss/issues/38418 description. diff --git a/app/services/todos/destroy/confidential_issue_service.rb b/app/services/todos/destroy/confidential_issue_service.rb index 331c4a12681..42f9ce66e77 100644 --- a/app/services/todos/destroy/confidential_issue_service.rb +++ b/app/services/todos/destroy/confidential_issue_service.rb @@ -3,7 +3,7 @@ module Todos module Destroy # Service class for deleting todos that belongs to confidential issues. - # It deletes todos for users that are not at least reporters, issue author or assignee. + # It deletes todos for users that are not at least planners, issue author or assignee. # # Accepts issue_id or project_id as argument. # When issue_id is passed it deletes matching todos for one confidential issue. diff --git a/app/services/todos/destroy/entity_leave_service.rb b/app/services/todos/destroy/entity_leave_service.rb index 387c5ce063a..b49cbacc75c 100644 --- a/app/services/todos/destroy/entity_leave_service.rb +++ b/app/services/todos/destroy/entity_leave_service.rb @@ -19,8 +19,10 @@ module Todos def execute return unless entity && user - # if at least reporter, all entities including confidential issues can be accessed - return if user_has_reporter_access? + # If at least planner, all entities including confidential issues can be accessed. Although PLANNER is not a + # linear access level, it can be considered so for the purpose of issuables visibility because the same + # permissions apply to all levels higher than Gitlab::Access::PLANNER + return if user_has_planner_access? remove_confidential_resource_todos remove_group_todos @@ -52,7 +54,7 @@ module Todos Todo .for_type(Issue.name) .for_internal_notes - .for_project(non_authorized_reporter_projects) # Only Reporter+ can read internal notes + .for_project(non_authorized_planner_projects) # Only Planner+ can read internal notes .for_user(user) .delete_all end @@ -65,9 +67,9 @@ module Todos .for_user(user) .delete_all - # MRs require reporter access, so remove those todos that are not authorized + # MRs require planner access, so remove those todos that are not authorized Todo - .for_project(non_authorized_reporter_projects) + .for_project(non_authorized_planner_projects) .for_type(MergeRequest.name) .for_user(user) .delete_all @@ -87,30 +89,30 @@ module Todos when Project { id: entity.id } when Namespace - { namespace_id: non_authorized_reporter_groups } + { namespace_id: non_authorized_planner_groups } end Project.where(condition) # rubocop: disable CodeReuse/ActiveRecord end - def authorized_reporter_projects - user.authorized_projects(Gitlab::Access::REPORTER).select(:id) + def authorized_planner_projects + user.authorized_projects(Gitlab::Access::PLANNER).select(:id) end def authorized_guest_projects user.authorized_projects(Gitlab::Access::GUEST).select(:id) end - def non_authorized_reporter_projects - projects.id_not_in(authorized_reporter_projects) + def non_authorized_planner_projects + projects.id_not_in(authorized_planner_projects) end def non_authorized_guest_projects projects.id_not_in(authorized_guest_projects) end - def authorized_reporter_groups - GroupsFinder.new(user, min_access_level: Gitlab::Access::REPORTER).execute.select(:id) + def authorized_planner_groups + GroupsFinder.new(user, min_access_level: Gitlab::Access::PLANNER).execute.select(:id) end # rubocop: disable CodeReuse/ActiveRecord @@ -124,15 +126,15 @@ module Todos end # rubocop: enable CodeReuse/ActiveRecord - def non_authorized_reporter_groups + def non_authorized_planner_groups entity.self_and_descendants.select(:id) - .id_not_in(authorized_reporter_groups) + .id_not_in(authorized_planner_groups) end - def user_has_reporter_access? + def user_has_planner_access? return unless entity.is_a?(Namespace) - entity.member?(User.find(user.id), Gitlab::Access::REPORTER) + entity.member?(User.find(user.id), Gitlab::Access::PLANNER) end def confidential_issues @@ -141,7 +143,7 @@ module Todos Issue .in_projects(projects) .confidential_only - .not_in_projects(authorized_reporter_projects) + .not_in_projects(authorized_planner_projects) .not_authored_by(user) .id_not_in(assigned_ids) end diff --git a/app/services/work_items/data_sync/clone_service.rb b/app/services/work_items/data_sync/clone_service.rb index f9e45f91f51..298e375e10f 100644 --- a/app/services/work_items/data_sync/clone_service.rb +++ b/app/services/work_items/data_sync/clone_service.rb @@ -41,7 +41,7 @@ module WorkItems return error(error_message, :unprocessable_entity) end - if target_namespace.pending_delete? # rubocop:disable Style/GuardClause -- does not read right with other checks above + if target_namespace.pending_delete? error_message = s_('CloneWorkItem|Cannot clone work item to target namespace as it is pending deletion.') return error(error_message, :unprocessable_entity) diff --git a/app/services/work_items/data_sync/move_service.rb b/app/services/work_items/data_sync/move_service.rb index e2152dc7a14..14c8e719c93 100644 --- a/app/services/work_items/data_sync/move_service.rb +++ b/app/services/work_items/data_sync/move_service.rb @@ -39,7 +39,7 @@ module WorkItems return error(error_message, :unprocessable_entity) end - if target_namespace.pending_delete? # rubocop:disable Style/GuardClause -- does not read right with other checks above + if target_namespace.pending_delete? error_message = s_('MoveWorkItem|Cannot move work item to target namespace as it is pending deletion.') return error(error_message, :unprocessable_entity) diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index c226c69725f..d7b799ce9f2 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -10,13 +10,13 @@ .top-area = gl_tabs_nav({ class: 'gl-grow gl-border-b-0' }) do = gl_tab_link_to s_('Branches|Overview'), project_branches_path(@project), { item_active: @mode == 'overview', title: s_('Branches|Show overview of the branches') } - = gl_tab_link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), { title: s_('Branches|Show active branches') } - = gl_tab_link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), { title: s_('Branches|Show stale branches') } + = gl_tab_link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), { item_active: @mode == 'active', title: s_('Branches|Show active branches') } + = gl_tab_link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), { item_active: @mode == 'stale', title: s_('Branches|Show stale branches') } = gl_tab_link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), { item_active: %w[overview active stale].exclude?(@mode), title: s_('Branches|Show all branches') } .nav-controls #js-branches-sort-dropdown{ data: { - project_branches_filtered_path: project_branches_path(@project, state: 'all'), + project_branches_filtered_path: project_branches_path(@project, state: @mode), sort_options: branches_sort_options_hash.to_json, show_dropdown: @mode == 'overview' ? 'false' : 'true', sorted_by: @sort } diff --git a/app/views/shared/wikis/git_access.html.haml b/app/views/shared/wikis/git_access.html.haml index e45d8d7d1c3..ffb6e38a202 100644 --- a/app/views/shared/wikis/git_access.html.haml +++ b/app/views/shared/wikis/git_access.html.haml @@ -6,29 +6,6 @@ .wiki-page-header.has-sidebar-toggle.gl-flex-col.gl-py-5 %h1.gl-heading-1{ class: '!gl-mt-0' }= s_('WikiClone|Clone Wiki repository') - %h2.gl-heading-3{ class: '!gl-mb-0' }= s_('WikiClone|Step 1: Clone repository') - - %h5= s_("WikiClone|Clone repository") = wiki_sidebar_toggle_button - = render "shared/clone_panel", container: @wiki - -.wiki-git-access.gl-mt-3 - %h2.gl-heading-3= s_('WikiClone|Step 2: Install and start Gollum') - - %h5.gl-mt-2= s_("WikiClone|Go to directory") - %pre.dark - :preserve - cd #{h @wiki.path} - - %h5= s_("WikiClone|Install Gollum") - %pre.dark - :preserve - gem install gollum - - %h5.gl-mt-2= s_("WikiClone|Start Gollum and edit locally") - %pre.dark - :preserve - gollum - = render 'shared/wikis/sidebar' diff --git a/config/feature_flags/gitlab_com_derisk/routable_token.yml b/config/feature_flags/beta/ci_optimize_memory_for_variables.yml similarity index 62% rename from config/feature_flags/gitlab_com_derisk/routable_token.yml rename to config/feature_flags/beta/ci_optimize_memory_for_variables.yml index 91cfabaabde..f9070e1fe46 100644 --- a/config/feature_flags/gitlab_com_derisk/routable_token.yml +++ b/config/feature_flags/beta/ci_optimize_memory_for_variables.yml @@ -1,9 +1,9 @@ --- -name: routable_token -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/486946 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169581 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/499537 -milestone: '17.6' -group: group::tenant scale -type: gitlab_com_derisk +name: ci_optimize_memory_for_variables +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/499707 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173126 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/502800 +milestone: '17.7' +group: group::pipeline authoring +type: beta default_enabled: false diff --git a/data/whats_new/202411210001_17_06.yml b/data/whats_new/202411210001_17_06.yml new file mode 100644 index 00000000000..4c714d052bd --- /dev/null +++ b/data/whats_new/202411210001_17_06.yml @@ -0,0 +1,47 @@ +- name: Use self-hosted model for GitLab Duo Chat + description: | + You can now host your own supported large language models (LLMs) and configure them to enable self-hosted GitLab Duo Chat. This feature is in beta and available with an Ultimate and Duo Enterprise subscription on GitLab self-managed. With self-hosted models, you can use models hosted either on-premise or in a private cloud to enable GitLab Duo Chat or Code Suggestions (introduced as a beta feature in GitLab 17.5). For Code Suggestions, we currently support open-source Mistral models on vLLM or AWS Bedrock, Claude 3.5 Sonnet on AWS Bedrock, and OpenAI models on Azure OpenAI. For Chat, we currently support open-source Mistral models on vLLM or AWS Bedrock, and Claude 3.5 Sonnet on AWS Bedrock. By enabling self-hosted models, you can leverage the power of generative AI while maintaining complete data sovereignty and privacy. + stage: ai-powered + self-managed: true + gitlab-com: false + available_in: [Ultimate] + documentation_link: https://docs.gitlab.com/ee/administration/self_hosted_models/ + image_url: https://about.gitlab.com/images/17_6/self-hosted-models-ui-17.6.png + published_at: 2024-11-21 + release: 17.6 + +- name: Vulnerability report grouping + description: | + Users require the ability to view vulnerabilities in groups. This will help security analysts optimize their triage tasks by utilizing bulk actions. In addition users can see how many vulnerabilities match their group; i.e. how many OWASP Top 10 vulnerabilities are there? + stage: security_risk_management + self-managed: true + gitlab-com: true + available_in: [Ultimate] + documentation_link: https://docs.gitlab.com/ee/user/application_security/vulnerability_report/#group-vulnerabilities + image_url: https://about.gitlab.com/images/17_6/vulnerability_report_grouping.png + published_at: 2024-11-21 + release: 17.6 + +- name: Display release notes on deployment details page + description: | + Have you ever wondered what might be included in a deployment you’ve been asked to approve? In past versions, you could create a release with a detailed description about its content and instructions for testing, but the related environment-specific deployment did not show this data. We are happy to share that GitLab now displays the release notes under the related deployment details page. + stage: deploy + self-managed: true + gitlab-com: true + available_in: [Free, Premium, Ultimate] + documentation_link: https://docs.gitlab.com/ee/ci/environments/deployment_approvals.html#view-blocked-deployments + image_url: https://about.gitlab.com/images/17_6/deploy-automatically-show-release-notes.png + published_at: 2024-11-21 + release: 17.6 + +- name: Enhanced merge request reviewer assignments + description: | + Now, when assigning reviewers, the sidebar creates a connection between the approval requirements for your merge request and reviewers. View each approval rule, then select from approvers who can satisfy that approval rule and move the merge request forward for you. If you use optional CODEOWNER sections those rules are also shown in the sidebar to help you identify appropriate subject matter experts for your changes. + stage: create + self-managed: true + gitlab-com: true + available_in: [Free, Premium, Ultimate] + documentation_link: https://docs.gitlab.com/ee/user/project/merge_requests/reviews/#request-a-review + image_url: https://about.gitlab.com/images/17_6/create-enhanced-reviewer-assignment.png + published_at: 2024-11-21 + release: 17.6 diff --git a/db/docs/views/postgres_table_sizes.yml b/db/docs/views/postgres_table_sizes.yml new file mode 100644 index 00000000000..9134c07a3b2 --- /dev/null +++ b/db/docs/views/postgres_table_sizes.yml @@ -0,0 +1,10 @@ +--- +view_name: postgres_table_sizes +classes: + - Gitlab::Database::PostgresTableSize +feature_categories: + - database +description: SQL view to get information about postgres table sizes +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173113 +milestone: '17.7' +gitlab_schema: gitlab_shared diff --git a/db/migrate/20241021082113_create_partitioned_ci_runners.rb b/db/migrate/20241021082113_create_partitioned_ci_runners.rb index 79c3fec3018..d61018f8d3a 100644 --- a/db/migrate/20241021082113_create_partitioned_ci_runners.rb +++ b/db/migrate/20241021082113_create_partitioned_ci_runners.rb @@ -35,7 +35,6 @@ class CreatePartitionedCiRunners < Gitlab::Database::Migration[2.2] def create_partitioned_table(name) options = 'PARTITION BY LIST (runner_type)' - # rubocop: disable Migration/EnsureFactoryForTable -- we'll reuse the ci_runners factory once migrated create_table name, primary_key: PARTITIONED_TABLE_PK, options: options do |t| t.bigint :id, null: false t.bigint :creator_id @@ -88,6 +87,5 @@ class CreatePartitionedCiRunners < Gitlab::Database::Migration[2.2] t.index %i[token_expires_at id], name: "idx_#{name}_on_token_expires_at_desc_and_id_desc", order: { token_expires_at: :desc, runner_type: :asc, id: :desc } end - # rubocop: enable Migration/EnsureFactoryForTable end end diff --git a/db/migrate/20241024204816_create_partitioned_ci_runner_managers.rb b/db/migrate/20241024204816_create_partitioned_ci_runner_managers.rb index d2e4376d258..f7d6dd49807 100644 --- a/db/migrate/20241024204816_create_partitioned_ci_runner_managers.rb +++ b/db/migrate/20241024204816_create_partitioned_ci_runner_managers.rb @@ -28,7 +28,6 @@ class CreatePartitionedCiRunnerManagers < Gitlab::Database::Migration[2.2] def create_partitioned_table(name) options = 'PARTITION BY LIST (runner_type)' - # rubocop: disable Migration/EnsureFactoryForTable -- we'll reuse the ci_runner_machines factory once migrated create_table name, primary_key: PARTITIONED_TABLE_PK, options: options do |t| t.bigint :id, null: false t.bigint :runner_id, null: false @@ -59,6 +58,5 @@ class CreatePartitionedCiRunnerManagers < Gitlab::Database::Migration[2.2] name: "index_#{name}_on_patch_version" t.index :version, name: "index_#{name}_on_version" end - # rubocop: enable Migration/EnsureFactoryForTable end end diff --git a/db/migrate/20241119101703_add_postgres_table_sizes_view.rb b/db/migrate/20241119101703_add_postgres_table_sizes_view.rb new file mode 100644 index 00000000000..8eb99f979a3 --- /dev/null +++ b/db/migrate/20241119101703_add_postgres_table_sizes_view.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class AddPostgresTableSizesView < Gitlab::Database::Migration[2.2] + milestone '17.7' + + def up + execute(<<~SQL) + CREATE OR REPLACE VIEW postgres_table_sizes AS + SELECT + schemaname || '.' || relname as identifier, + schemaname as schema_name, + relname as table_name, + pg_size_pretty(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname))) as total_size, + pg_size_pretty(pg_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname))) as table_size, + pg_size_pretty(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) - + pg_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname))) as index_size, + pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) as size_in_bytes + FROM pg_stat_user_tables + WHERE pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) IS NOT NULL + ORDER BY pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) DESC; + SQL + end + + def down + execute(<<~SQL) + DROP VIEW postgres_table_sizes + SQL + end +end diff --git a/db/post_migrate/20241021163518_drop_user_canonical_emails_table.rb b/db/post_migrate/20241021163518_drop_user_canonical_emails_table.rb index 5f9beb6e4b3..06b46022872 100644 --- a/db/post_migrate/20241021163518_drop_user_canonical_emails_table.rb +++ b/db/post_migrate/20241021163518_drop_user_canonical_emails_table.rb @@ -12,7 +12,7 @@ class DropUserCanonicalEmailsTable < Gitlab::Database::Migration[2.2] create_table :user_canonical_emails do |t| t.timestamps_with_timezone t.references :user, index: false, null: false, foreign_key: { on_delete: :cascade } - t.string :canonical_email, null: false, index: true # rubocop:disable Migration/AddLimitToStringColumns -- limit ignored in original migration + t.string :canonical_email, null: false, index: true end add_index :user_canonical_emails, [:user_id, :canonical_email], unique: true diff --git a/db/schema_migrations/20241119101703 b/db/schema_migrations/20241119101703 new file mode 100644 index 00000000000..c7a32ed36de --- /dev/null +++ b/db/schema_migrations/20241119101703 @@ -0,0 +1 @@ +0bff9c7868fd9cbc139610f9f4a39e7ccc54b059ea1cec5d17fbf67725f83121 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5833f88fb8e..6e3a0d918c2 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17459,6 +17459,18 @@ CREATE VIEW postgres_sequences AS LEFT JOIN pg_attribute ON (((dep_pg_class.oid = pg_attribute.attrelid) AND (pg_depend.refobjsubid = pg_attribute.attnum)))) WHERE (seq_pg_class.relkind = 'S'::"char"); +CREATE VIEW postgres_table_sizes AS + SELECT (((pg_stat_user_tables.schemaname)::text || '.'::text) || (pg_stat_user_tables.relname)::text) AS identifier, + pg_stat_user_tables.schemaname AS schema_name, + pg_stat_user_tables.relname AS table_name, + pg_size_pretty(pg_total_relation_size((((quote_ident((pg_stat_user_tables.schemaname)::text) || '.'::text) || quote_ident((pg_stat_user_tables.relname)::text)))::regclass)) AS total_size, + pg_size_pretty(pg_relation_size((((quote_ident((pg_stat_user_tables.schemaname)::text) || '.'::text) || quote_ident((pg_stat_user_tables.relname)::text)))::regclass)) AS table_size, + pg_size_pretty((pg_total_relation_size((((quote_ident((pg_stat_user_tables.schemaname)::text) || '.'::text) || quote_ident((pg_stat_user_tables.relname)::text)))::regclass) - pg_relation_size((((quote_ident((pg_stat_user_tables.schemaname)::text) || '.'::text) || quote_ident((pg_stat_user_tables.relname)::text)))::regclass))) AS index_size, + pg_total_relation_size((((quote_ident((pg_stat_user_tables.schemaname)::text) || '.'::text) || quote_ident((pg_stat_user_tables.relname)::text)))::regclass) AS size_in_bytes + FROM pg_stat_user_tables + WHERE (pg_total_relation_size((((quote_ident((pg_stat_user_tables.schemaname)::text) || '.'::text) || quote_ident((pg_stat_user_tables.relname)::text)))::regclass) IS NOT NULL) + ORDER BY (pg_total_relation_size((((quote_ident((pg_stat_user_tables.schemaname)::text) || '.'::text) || quote_ident((pg_stat_user_tables.relname)::text)))::regclass)) DESC; + CREATE TABLE programming_languages ( id bigint NOT NULL, name character varying NOT NULL, diff --git a/doc/administration/auth/index.md b/doc/administration/auth/index.md index e0235bb67a4..e8e6c6483a1 100644 --- a/doc/administration/auth/index.md +++ b/doc/administration/auth/index.md @@ -23,12 +23,12 @@ and the following external authentication and authorization providers: NOTE: UltraAuth has removed their software which supports OmniAuth integration. We have therefore removed all references to UltraAuth integration. -## SaaS vs self-managed comparison +## GitLab.com compared to self-managed The external authentication and authorization providers may support the following capabilities. For more information, see the links shown on this page for each external provider. -| Capability | SaaS | Self-managed | +| Capability | GitLab.com | Self-managed | |-------------------------------------------------|-----------------------------------------|------------------------------------| | **User Provisioning** | SCIM
SAML 1 | LDAP 1
SAML 1
[OmniAuth Providers](../../integration/omniauth.md#supported-providers) 1
SCIM | | **User Detail Updating** (not group management) | Not Available | LDAP Sync | diff --git a/doc/administration/reporting/git_abuse_rate_limit.md b/doc/administration/reporting/git_abuse_rate_limit.md index c49e6b6e719..88aa7f5539f 100644 --- a/doc/administration/reporting/git_abuse_rate_limit.md +++ b/doc/administration/reporting/git_abuse_rate_limit.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/reporting/ip_addr_restrictions.md b/doc/administration/reporting/ip_addr_restrictions.md index f4050ea6f48..7828a9f24f9 100644 --- a/doc/administration/reporting/ip_addr_restrictions.md +++ b/doc/administration/reporting/ip_addr_restrictions.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/review_abuse_reports.md b/doc/administration/review_abuse_reports.md index 7821e5e9ce0..3a1ecb231b9 100644 --- a/doc/administration/review_abuse_reports.md +++ b/doc/administration/review_abuse_reports.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/review_spam_logs.md b/doc/administration/review_spam_logs.md index 27f5ae0eca3..c77969d64dd 100644 --- a/doc/administration/review_spam_logs.md +++ b/doc/administration/review_spam_logs.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/administration/settings/account_and_limit_settings.md b/doc/administration/settings/account_and_limit_settings.md index f4daddc58ed..3ea880bd045 100644 --- a/doc/administration/settings/account_and_limit_settings.md +++ b/doc/administration/settings/account_and_limit_settings.md @@ -245,13 +245,10 @@ When you require expiration dates for new access tokens: DETAILS: **Tier:** Premium, Ultimate -**Offering:** Self-managed +**Offering:** Self-managed, GitLab Dedicated > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163726) in GitLab 17.5 [with a feature flag](../feature_flags.md) named `allow_top_level_group_owners_to_create_service_accounts` for GitLab self-managed. Disabled by default. -> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125835) in GitLab 17.6. Feature flag `allow_top_level_group_owners_to_create_service_accounts` removed. - -FLAG: -On GitLab self-managed, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../feature_flags.md) named `allow_top_level_group_owners_to_create_service_accounts`. On GitLab.com, this feature is available. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172502) in GitLab 17.6. Feature flag `allow_top_level_group_owners_to_create_service_accounts` removed. By default, in GitLab self-managed, top-level group Owners can not create service accounts. GitLab administrators can allow top-level group Owners to create service accounts. diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md index 1bd626603bf..f8db7722f62 100644 --- a/doc/api/access_requests.md +++ b/doc/api/access_requests.md @@ -18,6 +18,7 @@ following levels are recognized: - No access (`0`) - Minimal access (`5`) - Guest (`10`) +- Planner (`15`) - Reporter (`20`) - Developer (`30`) - Maintainer (`40`) diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md index ce8f1d63e89..3cd65154da2 100644 --- a/doc/api/broadcast_messages.md +++ b/doc/api/broadcast_messages.md @@ -127,6 +127,7 @@ The `target_access_levels` are defined in the `Gitlab::Access` module. The following levels are valid: - Guest (`10`) +- Planner (`15`) - Reporter (`20`) - Developer (`30`) - Maintainer (`40`) @@ -198,6 +199,7 @@ The `target_access_levels` are defined in the `Gitlab::Access` module. The following levels are valid: - Guest (`10`) +- Planner (`15`) - Reporter (`20`) - Developer (`30`) - Maintainer (`40`) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 6115305727f..5d2c71c37ae 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -39234,6 +39234,7 @@ Access level of a group or project member. | `MAINTAINER` | The Maintainer role is primarily used for managing code reviews, approvals, and administrative settings for projects. This role can also manage project memberships. | | `MINIMAL_ACCESS` | The Minimal Access role is for users who need the least amount of access into groups and projects. You can assign this role as a default, before giving a user another role with more permissions. | | `OWNER` | The Owner role is normally assigned to the individual or team responsible for managing and maintaining the group or creating the project. This role has the highest level of administrative control, and can manage all aspects of the group or project, including managing other Owners. | +| `PLANNER` | The Planner role is suitable for team members who need to manage projects and track work items but do not need to contribute code. | | `REPORTER` | The Reporter role is suitable for team members who need to stay informed about a project or group but do not actively contribute code. | ### `MemberAccessLevelName` @@ -39246,6 +39247,7 @@ Name of access levels of a group or project member. | `GUEST` | Guest access. | | `MAINTAINER` | Maintainer access. | | `OWNER` | Owner access. | +| `PLANNER` | Planner access. | | `REPORTER` | Reporter access. | ### `MemberApprovalStatusType` diff --git a/doc/api/group_access_tokens.md b/doc/api/group_access_tokens.md index 56d3da2483e..b2f4bacc942 100644 --- a/doc/api/group_access_tokens.md +++ b/doc/api/group_access_tokens.md @@ -116,7 +116,7 @@ POST /groups/:id/access_tokens | `id` | integer or string | yes | ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) | | `name` | String | yes | Name of the group access token | | `scopes` | `Array[String]` | yes | [List of scopes](../user/group/settings/group_access_tokens.md#scopes-for-a-group-access-token) | -| `access_level` | Integer | no | Access level. Valid values are `10` (Guest), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). | +| `access_level` | Integer | no | Access level. Valid values are `10` (Guest), `15` (Planner), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). | | `expires_at` | Date | yes | Expiration date of the access token in ISO format (`YYYY-MM-DD`). The date cannot be set later than the [maximum allowable lifetime of an access token](../user/profile/personal_access_tokens.md#access-token-expiration). | ```shell diff --git a/doc/api/invitations.md b/doc/api/invitations.md index 7baf6e33024..db48d74033f 100644 --- a/doc/api/invitations.md +++ b/doc/api/invitations.md @@ -21,6 +21,7 @@ levels are defined in the `Gitlab::Access` module. Currently, these levels are v - No access (`0`) - Minimal access (`5`) - Guest (`10`) +- Planner (`15`) - Reporter (`20`) - Developer (`30`) - Maintainer (`40`) diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index aaf339a34d7..b2a06eb5b27 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -100,7 +100,7 @@ Supported attributes: |:----------|:--------|:---------|:-------------------------------------| | `name` | string | yes | The name of the member role. | | `description` | string | no | The description of the member role. | -| `base_access_level` | integer | yes | Base access level for configured role. Valid values are `10` (Guest), `20` (Reporter), `30` (Developer), `40` (Maintainer), or `50` (Owner).| +| `base_access_level` | integer | yes | Base access level for configured role. Valid values are `10` (Guest), `15` (Planner), `20` (Reporter), `30` (Developer), `40` (Maintainer), or `50` (Owner).| | `admin_cicd_variables` | boolean | no | Permission to create, read, update, and delete CI/CD variables. | | `admin_compliance_framework` | boolean | no | Permission to administer compliance frameworks. | | `admin_group_member` | boolean | no | Permission to add, remove and assign members in a group. | diff --git a/doc/api/members.md b/doc/api/members.md index 9456f4b876f..7835ce8e767 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -18,6 +18,7 @@ in the `Gitlab::Access` module as `access_level`. - No access (`0`) - Minimal access (`5`) - Guest (`10`) +- Planner (`15`) - Reporter (`20`) - Developer (`30`) - Maintainer (`40`) diff --git a/doc/api/milestones.md b/doc/api/milestones.md index e449fc45536..fa72ee4b60c 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -123,8 +123,9 @@ Parameters: ## Delete project milestone > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. -Only for users with at least the Reporter role for the project. +Only for users with at least the Planner role for the project. ```plaintext DELETE /projects/:id/milestones/:milestone_id @@ -170,8 +171,9 @@ Parameters: ## Promote project milestone to a group milestone > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. -Only for users with at least the Reporter role for the group. +Only for users with at least the Planner role for the group. ```plaintext POST /projects/:id/milestones/:milestone_id/promote diff --git a/doc/api/project_access_tokens.md b/doc/api/project_access_tokens.md index 70c5390a41d..714c1ad590d 100644 --- a/doc/api/project_access_tokens.md +++ b/doc/api/project_access_tokens.md @@ -122,7 +122,7 @@ POST projects/:id/access_tokens | `id` | integer or string | yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths) | | `name` | string | yes | Name of the project access token | | `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#scopes-for-a-project-access-token) | -| `access_level` | integer | no | Access level. Valid values are `10` (Guest), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). Defaults to `40`. | +| `access_level` | integer | no | Access level. Valid values are `10` (Guest), `15` (Planner), `20` (Reporter), `30` (Developer), `40` (Maintainer), and `50` (Owner). Defaults to `40`. | | `expires_at` | date | yes | Expiration date of the access token in ISO format (`YYYY-MM-DD`). The date cannot be set later than the [maximum allowable lifetime of an access token](../user/profile/personal_access_tokens.md#access-token-expiration). | ```shell diff --git a/doc/ci/test_cases/index.md b/doc/ci/test_cases/index.md index 264824b3b76..aaa0d1453b0 100644 --- a/doc/ci/test_cases/index.md +++ b/doc/ci/test_cases/index.md @@ -30,9 +30,11 @@ For more information, see [Product Stage Direction - Plan](https://about.gitlab. ## Create a test case +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. To create a test case in a GitLab project: @@ -51,7 +53,7 @@ Prerequisites: - Non-confidential test case in a public project: You don't have to be a member of the project. - Non-confidential test case in a private project: You must have at least the Guest role for the project. -- Confidential test case (regardless of project visibility): You must have at least the Reporter role for the project. +- Confidential test case (regardless of project visibility): You must have at least the Planner role for the project. To view a test case: @@ -63,11 +65,13 @@ To view a test case: ## Edit a test case +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + You can edit a test case's title and description. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. - Users demoted to the Guest role can continue to edit the test cases they created when they were in the higher role. @@ -81,12 +85,13 @@ To edit a test case: ## Make a test case confidential > - Introduced for [new](https://gitlab.com/gitlab-org/gitlab/-/issues/422121) and [existing](https://gitlab.com/gitlab-org/gitlab/-/issues/422120) test cases in GitLab 16.5. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. If you're working on a test case that contains private information, you can make it confidential. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. To make a test case confidential: @@ -98,11 +103,13 @@ or editing an existing one. ## Archive a test case +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + When you want to stop using a test case, you can archive it. You can [reopen an archived test case](#reopen-an-archived-test-case) later. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. To archive a test case, on the test case's page, select **Archive test case**. @@ -114,11 +121,13 @@ To view archived test cases: ## Reopen an archived test case +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + If you decide to start using an archived test case again, you can reopen it. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. To reopen an archived test case: diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 1f3580523a5..a22e139989c 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -1320,6 +1320,7 @@ When writing alt text: - Try to avoid repeating text you've already used in the topic. - Do not use inline styling like bold, italics, or backticks. Screen readers read `**text**` as `star star text star star`. +- Use an empty alt text tag (`alt=""`) instead of omitting the tag altogether when the image does not add any unique information to the page. For example, when the image is decorative or is already fully described in the body text or caption. An empty alt tag tells assistive technologies that you have omitted the text intentionally, while a missing alt tag is ambiguous. #### Automatic screenshot generator diff --git a/doc/development/fe_guide/accessibility/best_practices.md b/doc/development/fe_guide/accessibility/best_practices.md index ce495c992a2..ab0fee65a16 100644 --- a/doc/development/fe_guide/accessibility/best_practices.md +++ b/doc/development/fe_guide/accessibility/best_practices.md @@ -31,12 +31,13 @@ You can read more about enabling browser-specific keyboard navigation on [a11ypr ## Quick checklist -- [Text](#text-inputs-with-accessible-names), - [select](#select-inputs-with-accessible-names), - [checkbox](#checkbox-inputs-with-accessible-names), - [radio](#radio-inputs-with-accessible-names), +- [Text](https://design.gitlab.com/components/text-input#accessibility), + [textarea](https://design.gitlab.com/components/textarea#accessibility), + [select](https://design.gitlab.com/components/select#accessibility), + [checkbox](https://design.gitlab.com/components/checkbox#accessibility), + [radio](https://design.gitlab.com/components/radio-button#accessibility), [file](#file-inputs-with-accessible-names), - and [toggle](#gltoggle-components-with-accessible-names) inputs have accessible names. + and [toggle](https://design.gitlab.com/components/toggle#accessibility) inputs have accessible names. - [Buttons](#buttons-and-links-with-descriptive-accessible-names), [links](#buttons-and-links-with-descriptive-accessible-names), and [images](#images-with-accessible-names) have descriptive accessible names. @@ -45,7 +46,7 @@ You can read more about enabling browser-specific keyboard navigation on [a11ypr - [Clickable icons](#icons-that-are-clickable) are buttons, that is, `` is used and not ``. - Icon-only buttons have an `aria-label`. - Interactive elements can be [accessed with the Tab key](#support-keyboard-only-use) and have a visible focus state. -- Elements with [tooltips](#tooltips) are focusable using the Tab key. +- Elements with [tooltips](https://design.gitlab.com/components/tooltip#accessibility) are focusable using the Tab key. - Are any `role`, `tabindex` or `aria-*` attributes unnecessary? - Can any `div` or `span` elements be replaced with a more semantic [HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) like `p`, `button`, or `time`? @@ -85,151 +86,6 @@ Note that [when using `GlFormGroup`](https://bootstrap-vue.org/docs/components/f - Passing only a `label` prop renders a `fieldset` with a `legend` containing the `label` value. - Passing both a `label` and a `label-for` prop renders a `label` that points to the form input with the same `label-for` ID. -#### Text inputs with accessible names - -When using `GlFormGroup`, the `label` prop alone does not give the input an accessible name. -The `label-for` prop must also be provided to give the input an accessible name. - -Text input examples: - -```html - - - - - - - - - -``` - -`textarea` examples: - -```html - - - - - - - - - -``` - -Alternatively, you can use a plain `label` element: - -```html - - - - - - - -``` - -#### Select inputs with accessible names - -Select input examples: - -```html - - - - - - - - - -``` - -#### Checkbox inputs with accessible names - -Single checkbox: - -```html - - - {{ __('Task complete') }} - - - - - {{ __('Task complete') }} - -``` - -Multiple checkboxes: - -```html - - - {{ __('Task 1') }} - {{ __('Task 2') }} - - - - - - - - - - {{ __('Task 1') }} - {{ __('Task 2') }} - - - - - - -``` - -#### Radio inputs with accessible names - -Single radio input: - -```html - - - {{ __('Opened') }} - - - - - {{ __('Opened') }} - -``` - -Multiple radio inputs: - -```html - - - {{ __('Opened') }} - {{ __('Closed') }} - - - - - - - - - - {{ __('Opened') }} - {{ __('Closed') }} - - - - - - -``` - #### File inputs with accessible names File input examples: @@ -244,27 +100,6 @@ File input examples: ``` -#### GlToggle components with accessible names - -`GlToggle` examples: - -```html - - - - - -``` - -#### GlFormCombobox components with accessible names - -`GlFormCombobox` example: - -```html - - -``` - #### Images with accessible names Image examples: @@ -295,14 +130,6 @@ Buttons and links should have accessible names that are descriptive enough to be {{ __("GitLab's accessibility page") }} ``` -#### Links styled like buttons - -Links can be styled like buttons using `GlButton`. - -```html - {{ __('Link styled as a button') }} -``` - ## Role In general, avoid using `role`. @@ -355,7 +182,7 @@ Use interactive elements instead of `div` and `span` tags. For example: - If the element should be clickable, use a `button` (`GlButton`). -- If the element should be text editable, use an [`input` or `textarea`](#text-inputs-with-accessible-names). +- If the element should be text editable, use an [`input`](https://design.gitlab.com/components/text-input#accessibility) or [`textarea`](https://design.gitlab.com/components/textarea#accessibility). Once the markup is semantically complete, use CSS to update it to its desired visual state. @@ -451,28 +278,6 @@ Icons that are clickable are semantically buttons, so they should be rendered as ``` -## Tooltips - -When adding tooltips, we must ensure that the element with the tooltip can receive focus so keyboard users can see the tooltip. -If the element is a static one, such as an icon, we can enclose it in a button, which already is -focusable, so we don't have to add `tabindex=0` to the icon. - -The following code snippet is a good example of an icon with a tooltip. - -- It is automatically focusable, as it is a button. -- It is given an accessible name with `aria-label`, as it is a button with no text. - -```html - -``` - ## Hiding elements Use the following table to hide elements from users, when appropriate. diff --git a/doc/development/identity_verification.md b/doc/development/identity_verification.md index ac207f663cf..83fb38279f4 100644 --- a/doc/development/identity_verification.md +++ b/doc/development/identity_verification.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/internal_analytics/service_ping/index.md b/doc/development/internal_analytics/service_ping/index.md index 886cf9e33fe..a10fff92ef0 100644 --- a/doc/development/internal_analytics/service_ping/index.md +++ b/doc/development/internal_analytics/service_ping/index.md @@ -89,17 +89,15 @@ sequenceDiagram - `elapsed` - Amount of time which passed since Service Ping report process started and moment of error occurrence - `message` - Error message -
-   
-   {
-     "uuid"=>"02333324-1cd7-4c3b-a45b-a4993f05fb1d",
-     "hostname"=>"127.0.0.1",
-     "version"=>"14.7.0-pre",
-     "elapsed"=>0.006946,
-     "message"=>'PG::UndefinedColumn: ERROR:  column \"non_existent_attribute\" does not exist\nLINE 1: SELECT COUNT(non_existent_attribute) FROM \"issues\" /*applica...'
-   }
-   
-   
+ ```ruby + { + "uuid"=>"02333324-1cd7-4c3b-a45b-a4993f05fb1d", + "hostname"=>"127.0.0.1", + "version"=>"14.7.0-pre", + "elapsed"=>0.006946, + "message"=>'PG::UndefinedColumn: ERROR: column \"non_existent_attribute\" does not exist\nLINE 1: SELECT COUNT(non_existent_attribute) FROM \"issues\" /*applica...' + } + ``` 1. Finally, the timing metadata information that is used for diagnostic purposes is submitted to the Versions application. It consists of a list of metric identifiers and the time it took to calculate the metrics: diff --git a/doc/development/spam_protection_and_captcha/exploratory_testing.md b/doc/development/spam_protection_and_captcha/exploratory_testing.md index a2edf4c3210..7e0d462d856 100644 --- a/doc/development/spam_protection_and_captcha/exploratory_testing.md +++ b/doc/development/spam_protection_and_captcha/exploratory_testing.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/spam_protection_and_captcha/graphql_api.md b/doc/development/spam_protection_and_captcha/graphql_api.md index dca01aff3c7..37a0e6969fa 100644 --- a/doc/development/spam_protection_and_captcha/graphql_api.md +++ b/doc/development/spam_protection_and_captcha/graphql_api.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/spam_protection_and_captcha/index.md b/doc/development/spam_protection_and_captcha/index.md index b134c242195..7b8c6994f34 100644 --- a/doc/development/spam_protection_and_captcha/index.md +++ b/doc/development/spam_protection_and_captcha/index.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/spam_protection_and_captcha/model_and_services.md b/doc/development/spam_protection_and_captcha/model_and_services.md index e8944a8dc39..e17dad45a61 100644 --- a/doc/development/spam_protection_and_captcha/model_and_services.md +++ b/doc/development/spam_protection_and_captcha/model_and_services.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/spam_protection_and_captcha/rest_api.md b/doc/development/spam_protection_and_captcha/rest_api.md index eeadfe4b21b..ba92bf00da6 100644 --- a/doc/development/spam_protection_and_captcha/rest_api.md +++ b/doc/development/spam_protection_and_captcha/rest_api.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/development/spam_protection_and_captcha/web_ui.md b/doc/development/spam_protection_and_captcha/web_ui.md index fc6d168b9e5..2fb001427f2 100644 --- a/doc/development/spam_protection_and_captcha/web_ui.md +++ b/doc/development/spam_protection_and_captcha/web_ui.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review. --- diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md index 901ae785dd8..e07a372a889 100644 --- a/doc/integration/akismet.md +++ b/doc/integration/akismet.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/integration/arkose.md b/doc/integration/arkose.md index ea77ea48edb..8fe2333a09f 100644 --- a/doc/integration/arkose.md +++ b/doc/integration/arkose.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md index 946b462ccb7..2c9bbfb74d9 100644 --- a/doc/integration/recaptcha.md +++ b/doc/integration/recaptcha.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/security/email_verification.md b/doc/security/email_verification.md index 7f91370ea7c..058644c003a 100644 --- a/doc/security/email_verification.md +++ b/doc/security/email_verification.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/security/identity_verification.md b/doc/security/identity_verification.md index d5e098a98d3..a920a7f9ae8 100644 --- a/doc/security/identity_verification.md +++ b/doc/security/identity_verification.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/update/package/convert_to_ee.md b/doc/update/package/convert_to_ee.md index d680d77c1e5..834aae804fc 100644 --- a/doc/update/package/convert_to_ee.md +++ b/doc/update/package/convert_to_ee.md @@ -4,123 +4,123 @@ group: Distribution info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Convert Community Edition to Enterprise Edition +# Convert a Linux package CE instance to EE DETAILS: **Tier:** Free, Premium, Ultimate **Offering:** Self-managed -To convert an existing GitLab Community Edition (CE) server installed using the Linux -packages to GitLab [Enterprise Edition](https://about.gitlab.com/pricing/) (EE), you install the EE -package on top of CE. +You can convert an existing Linux package instance from Community Edition (CE) to Enterprise Edition (EE). +To convert the instance, you install the EE Linux package on top of the CE instance. -Converting from the same version of CE to EE is not explicitly necessary, and any standard upgrade -(for example, CE 12.0 to EE 12.1) should work. However, in the following steps we assume that -you are upgrading the same version (for example, CE 12.1 to EE 12.1), which is **recommended**. +You don't need the same version of CE to EE. For example, CE 17.0 to EE 17.1 should work. However, upgrading the same +version (for example, CE 17.1 to EE 17.1) is **recommended**. WARNING: -When updating to EE from CE, avoid reverting back to CE if you plan to go to EE again in the -future. Reverting back to CE can cause -[database issues](package_troubleshooting.md#500-error-when-accessing-project--settings--repository) -that may require Support intervention. +After you convert from EE from CE, don't revert back to CE if you plan to go to EE again. Reverting back to CE can cause +[database issues](package_troubleshooting.md#500-error-when-accessing-project--settings--repository) that may require +Support intervention. -The steps can be summed up to: +## Convert from CE to EE + +To convert a Linux package CE instance to EE: 1. Make a [GitLab backup](../../administration/backup_restore/backup_gitlab.md). - 1. Find the installed GitLab version: - **For Debian/Ubuntu** + ::Tabs + + :::TabTitle Debian/Ubuntu ```shell sudo apt-cache policy gitlab-ce | grep Installed ``` - The output should be similar to: `Installed: 13.0.4-ce.0`. In that case, - the equivalent Enterprise Edition version is: `13.0.4-ee.0`. Write this - value down. + Note down the returned version. - **For CentOS/RHEL** + :::TabTitle CentOS/RHEL ```shell sudo rpm -q gitlab-ce ``` - The output should be similar to: `gitlab-ce-13.0.4-ce.0.el8.x86_64`. In that - case, the equivalent Enterprise Edition version is: - `gitlab-ee-13.0.4-ee.0.el8.x86_64`. Write this value down. + Note down the returned version. -1. Add the `gitlab-ee` [Apt or Yum repository](https://packages.gitlab.com/gitlab/gitlab-ee/install): + ::EndTabs - **For Debian/Ubuntu** +1. Add the `gitlab-ee` [Apt or Yum repository](https://packages.gitlab.com/gitlab/gitlab-ee/install). These commands + find your OS version and automatically set up the repository. If you are not comfortable installing the repository + through a piped script, you can first [check the script's contents](https://packages.gitlab.com/gitlab/gitlab-ee/install). + + ::Tabs + + :::TabTitle Debian/Ubuntu ```shell curl --silent "https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh" | sudo bash ``` - **For CentOS/RHEL** + :::TabTitle CentOS/RHEL ```shell curl --silent "https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh" | sudo bash ``` - The above command finds your OS version and automatically set up the - repository. If you are not comfortable installing the repository through a - piped script, you can first - [check its contents](https://packages.gitlab.com/gitlab/gitlab-ee/install). + ::EndTabs - NOTE: - If you want to use `dpkg`/`rpm` instead of `apt-get`/`yum`, go through the first - step to find the current GitLab version, then follow - [Upgrade using a manually downloaded package](index.md#by-using-a-downloaded-package), - and then [add your license](../../administration/license.md). + To use `dpkg` or `rpm` instead of using `apt-get` or `yum` follow + [Upgrade using a manually downloaded package](index.md#by-using-a-downloaded-package). -1. Install the `gitlab-ee` package. The install automatically - uninstalls the `gitlab-ce` package on your GitLab server. `reconfigure` - Omnibus right after the `gitlab-ee` package is installed. **Make sure that you - install the exact same GitLab version**: +1. Install the `gitlab-ee` Linux package. The install automatically uninstalls the `gitlab-ce` package on your GitLab. - **For Debian/Ubuntu** + ::Tabs + + :::TabTitle Debian/Ubuntu ```shell ## Make sure the repositories are up-to-date sudo apt-get update ## Install the package using the version you wrote down from step 1 - sudo apt-get install gitlab-ee=13.0.4-ee.0 + sudo apt-get install gitlab-ee=17.1.0-ee.0 ## Reconfigure GitLab sudo gitlab-ctl reconfigure ``` - **For CentOS/RHEL** + :::TabTitle CentOS/RHEL ```shell ## Install the package using the version you wrote down from step 1 - sudo yum install gitlab-ee-13.0.4-ee.0.el8.x86_64 + sudo yum install gitlab-ee-17.1.0-ee.0.el9.x86_64 ## Reconfigure GitLab sudo gitlab-ctl reconfigure ``` -1. Now activate GitLab Enterprise Edition by [adding your license](../../administration/license.md). + ::EndTabs -1. After you confirm that GitLab is working as expected, you may remove the old - Community Edition repository: +1. [Add your license](../../administration/license.md) to activate Enterprise Edition. +1. Confirm that GitLab is working as expected, then you can remove the old Community Edition repository: - **For Debian/Ubuntu** + ::Tabs + + :::TabTitle Debian/Ubuntu ```shell sudo rm /etc/apt/sources.list.d/gitlab_gitlab-ce.list ``` - **For CentOS/RHEL** + :::TabTitle CentOS/RHEL ```shell sudo rm /etc/yum.repos.d/gitlab_gitlab-ce.repo ``` -1. Optional. [Set up the Elasticsearch integration](../../integration/advanced_search/elasticsearch.md) to enable [advanced search](../../user/search/advanced_search.md). + ::EndTabs + +1. Optional. [Set up the Elasticsearch integration](../../integration/advanced_search/elasticsearch.md) to enable + [advanced search](../../user/search/advanced_search.md). That's it! You can now use GitLab Enterprise Edition! To upgrade to a newer version, follow [Upgrading Linux package instances](index.md). diff --git a/doc/user/application_security/policies/pipeline_execution_policies.md b/doc/user/application_security/policies/pipeline_execution_policies.md index 336a933a751..cf696e728b2 100644 --- a/doc/user/application_security/policies/pipeline_execution_policies.md +++ b/doc/user/application_security/policies/pipeline_execution_policies.md @@ -242,6 +242,21 @@ You can [define project or group variables in the UI](../../../ci/variables/inde To prevent a regular pipeline from triggering, users can push a commit to a protected branch with `[skip ci]` in the commit message. However, jobs defined with a pipeline execution policy are always triggered, as the policy ignores the `[skip ci]` directive. This prevents developers from skipping the execution of jobs defined in the policy, which ensures that critical security and compliance checks are always performed. +## Interaction with scan execution policies + +When you use pipeline execution policies with the `override_ci` strategy, be aware that this can affect the behavior of [scan execution policies](scan_execution_policies.md): + +- The scan execution policy may be overridden if both pipeline execution policies and scan execution policies are configured for a project, and the pipeline execution policy uses the `override_ci` strategy. + +This is because the `override_ci` strategy removes all CI/CD configuration that is defined on the project level, including policies. + +To ensure that both pipeline execution policies and scan execution policies are applied: + +- Consider using a different strategy for pipeline execution policies, such as `inject_ci`. +- If you must use `override_ci`, include the scanner templates that you require in your pipeline execution policy to maintain the desired security scans. + +Support for improvements in the integration between these policy types is proposed in [issue 504434](https://gitlab.com/gitlab-org/gitlab/-/issues/504434). + ## Examples These examples demonstrate what you can achieve with pipeline execution policies. diff --git a/doc/user/application_security/policies/scan_execution_policies.md b/doc/user/application_security/policies/scan_execution_policies.md index 3ee20bde2cf..08c28ccddf6 100644 --- a/doc/user/application_security/policies/scan_execution_policies.md +++ b/doc/user/application_security/policies/scan_execution_policies.md @@ -41,6 +41,7 @@ If any of the following cases are true, use [pipeline execution policies](pipeli - You can assign a maximum of five rules to each policy. - You can assign a maximum of five scan execution policies to each security policy project. +- Scan execution policies may be [overridden](pipeline_execution_policies.md#interaction-with-scan-execution-policies) by pipeline execution policies when you use `override_ci` strategy. ## Jobs diff --git a/doc/user/crm/index.md b/doc/user/crm/index.md index bb580b596ae..8cb8f8bea40 100644 --- a/doc/user/crm/index.md +++ b/doc/user/crm/index.md @@ -14,6 +14,7 @@ DETAILS: > - In GitLab 14.8 and later, you can [create contacts and organizations only in top-level groups](https://gitlab.com/gitlab-org/gitlab/-/issues/350634). > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/346082) in GitLab 15.0. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/346082) in GitLab 15.1. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. With customer relations management (CRM) you can create a record of contacts (individuals) and organizations (companies) and relate them to issues. @@ -26,12 +27,12 @@ For more information about what is planned for the future, see [issue 2256](http ## Permissions -| Permission | Guest | Group Reporter | Group Developer, Maintainer, and Owner | -| ---------- | ---------------- | -------------- | -------------------------------------- | -| View contacts/organizations | | ✓ | ✓ | -| View issue contacts | | ✓ | ✓ | -| Add/remove issue contacts | | ✓ | ✓ | -| Create/edit contacts/organizations | | | ✓ | +| Permission | Guest | Planner | Group Reporter | Group Developer, Maintainer, and Owner | +|------------------------------------|-------|---------|----------------|----------------------------------------| +| View contacts/organizations | | ✓ | ✓ | ✓ | +| View issue contacts | | ✓ | ✓ | ✓ | +| Add/remove issue contacts | | ✓ | ✓ | ✓ | +| Create/edit contacts/organizations | | | | ✓ | ## Enable customer relations management (CRM) @@ -73,7 +74,7 @@ To configure the contact source for a group or subgroup: Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To view a group's contacts: @@ -137,7 +138,7 @@ To change the state of a contact: Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To view a group's organizations: @@ -192,7 +193,7 @@ issues are linked to contacts matching the email addresses in the sender and CC Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To view a contact's issues, select a contact from the issue sidebar, or: @@ -204,7 +205,7 @@ To view a contact's issues, select a contact from the issue sidebar, or: Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To view an organization's issues: @@ -217,7 +218,7 @@ To view an organization's issues: Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. You can view contacts associated with an issue in the right sidebar. @@ -233,7 +234,7 @@ API. Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To add [active](#change-the-state-of-a-contact) contacts to an issue use the `/add_contacts [contact:address@example.com]` [quick action](../project/quick_actions.md). @@ -246,7 +247,7 @@ API. Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To remove contacts from an issue use the `/remove_contacts [contact:address@example.com]` [quick action](../project/quick_actions.md). diff --git a/doc/user/gitlab_duo_chat/index.md b/doc/user/gitlab_duo_chat/index.md index 6931a564838..36664db7a6d 100644 --- a/doc/user/gitlab_duo_chat/index.md +++ b/doc/user/gitlab_duo_chat/index.md @@ -123,7 +123,7 @@ If you have selected code in the editor, this selection is sent along with your ### In the editor window -> - [Generally available](https://gitlab.com/groups/gitlab-org/-/epics/15218) in the GitLab Workflow extension for VS Code 5.15.0. +> - Introduced as [generally available](https://gitlab.com/groups/gitlab-org/-/epics/15218) in the GitLab Workflow extension for VS Code 5.15.0. To open GitLab Duo Chat in the editor window, use any of these methods: @@ -195,7 +195,7 @@ After GitLab Duo Chat opens: ### In the editor window -> - [Generally available](https://gitlab.com/groups/gitlab-org/editor-extensions/-/epics/80) in GitLab Duo 3.0.0. +> - Introduced as [generally available](https://gitlab.com/groups/gitlab-org/editor-extensions/-/epics/80) in GitLab Duo 3.0.0. To open GitLab Duo Chat in the editor window, use any of these methods: diff --git a/doc/user/group/epics/epic_boards.md b/doc/user/group/epics/epic_boards.md index 6e23a47bf67..73dcd687920 100644 --- a/doc/user/group/epics/epic_boards.md +++ b/doc/user/group/epics/epic_boards.md @@ -11,6 +11,7 @@ DETAILS: **Offering:** GitLab.com, Self-managed, GitLab Dedicated > - Displaying total weight on the top of lists [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/364503) in GitLab 15.11. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Epic boards build on the existing [epic tracking functionality](index.md) and [labels](../../project/labels.md). Your epics appear as cards in vertical lists, organized by their assigned @@ -36,7 +37,7 @@ To view an epic board: Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To create a new epic board: @@ -59,7 +60,7 @@ To change these options later, [edit the board](#edit-the-scope-of-an-epic-board Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. - A minimum of two boards present in a group. To delete the active epic board: @@ -85,7 +86,7 @@ To delete the active epic board: Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To create a new list: @@ -115,7 +116,7 @@ list view that's removed. You can always create it again later if you need. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To remove a list from an epic board: @@ -128,7 +129,7 @@ To remove a list from an epic board: Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. - You must have [created a list](#create-a-new-list) first. To create an epic from a list in epic board: @@ -171,7 +172,7 @@ You can move epics and lists by dragging them. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To move an epic, select the epic card and drag it to another position in its current list or into another list. Learn about possible effects in [Dragging epics between lists](#dragging-epics-between-lists). @@ -190,7 +191,7 @@ Your epic is moved to the top of the list even if other epics are hidden by a fi Prerequisites: -- You must at least have the Reporter role for a group. +- You must at least have the Planner role for a group. To move an epic to the start of the list: @@ -208,7 +209,7 @@ Your epic is moved to the bottom of the list even if other epics are hidden by a Prerequisites: -- You must at least have the Reporter role for a group. +- You must at least have the Planner role for a group. To move an epic to the end of the list: @@ -230,7 +231,7 @@ and the target list. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To edit the scope of an epic board: diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md index 6fb1d38c0cc..6b854229a4c 100644 --- a/doc/user/group/epics/manage_epics.md +++ b/doc/user/group/epics/manage_epics.md @@ -15,9 +15,11 @@ to them. ## Create an epic +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the epic's group. +- You must have at least the Planner role for the epic's group. To create an epic in the group you're in: @@ -62,6 +64,8 @@ The parent epic's start date then reflects this change and propagates upwards to ## Edit an epic +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + After you create an epic, you can edit the following details: - Title @@ -73,7 +77,7 @@ After you create an epic, you can edit the following details: Prerequisites: -- You must have at least the Reporter role for the epic's group. +- You must have at least the Planner role for the epic's group. To edit an epic's title or description: @@ -89,12 +93,13 @@ To edit an epic's start date, due date, or labels: ### Reorder list items in the epic description > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15260) in GitLab 15.1. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. When you view an epic that has a list in the description, you can also reorder the list items. Prerequisites: -- You must have at least the Reporter role for the project, be the author of the epic, or be +- You must have at least the Planner role for the project, be the author of the epic, or be assigned to the epic. - The epic's description must have an [ordered, unordered](../../markdown.md#lists), or [task](../../markdown.md#task-lists) list. @@ -108,13 +113,15 @@ To reorder list items, when viewing an epic: ### Bulk edit epics -Users with at least the Reporter role can manage epics. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + +Users with at least the Planner role can manage epics. When bulk editing epics in a group, you can edit their labels. Prerequisites: -- You must have at least the Reporter role for the parent epic's group. +- You must have at least the Planner role for the parent epic's group. To update multiple epics at the same time: @@ -177,10 +184,12 @@ If you find a bug, use the ### Change assignee on an epic +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: - Your administrator must have [enabled the new look for epics](epic_work_items.md). -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To change the assignee on an epic: @@ -225,9 +234,11 @@ On epic boards, the color shows on the epic's card accent: ### Change an epic's color +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the epic's group. +- You must have at least the Planner role for the epic's group. To change an epic's color: @@ -243,10 +254,11 @@ The epic's color is updated. ## Delete an epic > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/452189) in GitLab 16.11. In GitLab 16.10 and earlier, if you delete an epic, all its child epics and their descendants are deleted as well. If needed, you can [remove child epics](#remove-a-child-epic-from-a-parent-epic) from the parent epic before you delete it. +> - [Allowed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) Planner role to delete an epic in GitLab 17.7. Prerequisites: -- You must have the Owner role for the epic's group. +- You must have the Planner or Owner role for the epic's group. To delete an epic: @@ -257,9 +269,11 @@ Deleting an epic releases all existing issues from their associated epic in the ## Close an epic +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the epic's group. +- You must have at least the Planner role for the epic's group. To close an epic: @@ -269,11 +283,13 @@ You can also use the `/close` [quick action](../../project/quick_actions.md). ## Reopen a closed epic +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + You can reopen an epic that was closed. Prerequisites: -- You must have at least the Reporter role for the epic's group. +- You must have at least the Planner role for the epic's group. To do so, either: @@ -310,12 +326,14 @@ To view epics in a group: ### Who can view an epic +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Whether you can view an epic depends on the [group visibility level](../../public_access.md) and the epic's [confidentiality status](#make-an-epic-confidential): - Public group and a non-confidential epic: Anyone can view the epic. - Private group and non-confidential epic: You must have at least the Guest role for the group. -- Confidential epic (regardless of group visibility): You must have at least the Reporter +- Confidential epic (regardless of group visibility): You must have at least the Planner role for the group. ### Cached epic count @@ -388,6 +406,8 @@ or newest items to be shown first. ## Make an epic confidential +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + If you're working on items that contain private information, you can make an epic confidential. NOTE: @@ -398,7 +418,7 @@ to learn how to create a confidential merge request. Prerequisites: -- You must have at least the Reporter role for the epic's group. +- You must have at least the Planner role for the epic's group. To make an epic confidential: @@ -492,10 +512,12 @@ To address risks to timely delivery of your planned work, incorporate a review o #### Change health status of an epic +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: - Your administrator must have [enabled the new look for epics](epic_work_items.md). -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To change the health status of an epic: diff --git a/doc/user/group/iterations/index.md b/doc/user/group/iterations/index.md index a82f448148d..2dec2fdf2a3 100644 --- a/doc/user/group/iterations/index.md +++ b/doc/user/group/iterations/index.md @@ -44,10 +44,11 @@ configure iteration cadences to automatically roll over incomplete issues to the ### Create an iteration cadence > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To create an iteration cadence: @@ -91,7 +92,7 @@ For example `https://gitlab.com/gitlab-org/sample-data-templates/sample-gitlab-p Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To edit an iteration cadence: @@ -151,10 +152,11 @@ to satisfy the requirement that there are at least two upcoming iterations sched ### Delete an iteration cadence > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. Deleting an iteration cadence also deletes all iterations in that cadence. @@ -180,13 +182,14 @@ On GitLab.com, this is the `automation-bot1` user. ## Create an iteration manually > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. When an iteration cadence has automatic scheduling enabled, iterations are created on schedule. If you disable that option, you can create iterations manually. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. - There must be at least one iteration cadence in the group and [automatic scheduling must be disabled](#turn-on-and-off-automatic-scheduling-for-an-iteration-cadence) for the iteration cadence. @@ -202,10 +205,11 @@ To create an iteration: ## Edit an iteration > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To edit an iteration: @@ -220,10 +224,11 @@ To edit an iteration: ## Delete an iteration > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. - [Automatic scheduling must be disabled](#turn-on-and-off-automatic-scheduling-for-an-iteration-cadence) for the iteration cadence. To delete an iteration: diff --git a/doc/user/group/moderate_users.md b/doc/user/group/moderate_users.md index f33ccd27122..6ad3542341f 100644 --- a/doc/user/group/moderate_users.md +++ b/doc/user/group/moderate_users.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/group/reporting/git_abuse_rate_limit.md b/doc/user/group/reporting/git_abuse_rate_limit.md index 6c4d49c93b3..829547688b3 100644 --- a/doc/user/group/reporting/git_abuse_rate_limit.md +++ b/doc/user/group/reporting/git_abuse_rate_limit.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 8a8be4de627..4b5ec6991c2 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -586,7 +586,7 @@ For example: - [SAML SSO for self-managed GitLab instances](../../../integration/saml.md) - [Glossary](../../../integration/saml.md#glossary) - [Blog post: The ultimate guide to enabling SAML and SSO on GitLab.com](https://about.gitlab.com/blog/2023/09/14/the-ultimate-guide-to-enabling-saml/) -- [Authentication comparison between SaaS and self-managed](../../../administration/auth/index.md#saas-vs-self-managed-comparison) +- [Authentication comparison between SaaS and self-managed](../../../administration/auth/index.md#gitlabcom-compared-to-self-managed) - [Passwords for users created through integrated authentication](../../../security/passwords_for_integrated_authentication_methods.md) - [SAML Group Sync](group_sync.md) diff --git a/doc/user/okrs.md b/doc/user/okrs.md index 4b9d3812382..3443ac74350 100644 --- a/doc/user/okrs.md +++ b/doc/user/okrs.md @@ -95,9 +95,11 @@ its parent's objective. ## Edit title and description +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To edit an OKR: @@ -114,10 +116,11 @@ To edit an OKR: > - Changing activity sort order [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378949) in GitLab 15.8. > - Filtering activity [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/389971) in GitLab 15.10. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/334812) in GitLab 15.10. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. You can view all the [system notes](project/system_notes.md) related to the OKR. By default they are sorted by **Oldest first**. You can always change the sorting order to **Newest first**, which is remembered across sessions. @@ -128,6 +131,8 @@ You can add [comments](discussions/index.md) and reply to threads in OKRs. ## Assign users +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + To show who is responsible for an OKR, you can assign users to it. Users on GitLab Free can assign one user per OKR. @@ -136,7 +141,7 @@ See also [multiple assignees for issues](project/issues/multiple_assignees_for_i Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To change the assignee on an OKR: @@ -147,9 +152,11 @@ To change the assignee on an OKR: ## Assign labels +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. Use [labels](project/labels.md) to organize OKRs among teams. @@ -163,13 +170,14 @@ To add labels to an OKR: ## Add an objective to a milestone > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) in GitLab 15.7. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. You can add an objective to a [milestone](project/milestones/index.md). You can see the milestone title when you view an objective. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To add an objective to a milestone: @@ -181,6 +189,7 @@ To add an objective to a milestone: ## Set progress > - Setting progress for key results [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382433) in GitLab 15.8. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Show how much of the work needed to achieve an objective is finished. @@ -193,7 +202,7 @@ value is updated, the automation updates all parents again to show the average. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To set progress of an objective or key result: @@ -207,6 +216,7 @@ To set progress of an objective or key result: ## Set health status > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381899) in GitLab 15.7. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. To better track the risk in meeting your goals, you can assign a [health status](project/issues/managing_issues.md#health-status) to each objective and key result. @@ -215,7 +225,7 @@ as planned or need attention to stay on schedule. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To set health status of an OKR: @@ -226,10 +236,11 @@ To set health status of an OKR: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/386877) in GitLab 16.0. > - Quick action `/promote_to` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412534) in GitLab 16.1. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To promote a key result: @@ -274,12 +285,14 @@ To copy the objective's or key result's email address: ## Close an OKR +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + When an OKR is achieved, you can close it. The OKR is marked as closed but is not deleted. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To close an OKR: @@ -354,10 +367,11 @@ To add an existing key result to an objective: ### Reorder objective and key result children > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385887) in GitLab 16.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. By default, child OKRs are ordered by creation date. To reorder them, drag them around. @@ -365,6 +379,7 @@ To reorder them, drag them around. ### Schedule OKR check-in reminders > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/422761) in GitLab 16.4 [with a flag](../administration/feature_flags.md) named `okr_checkin_reminders`. Disabled by default. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. FLAG: On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../administration/feature_flags.md) named `okr_checkin_reminders`. @@ -380,7 +395,7 @@ Reminders are sent on Tuesdays. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. - There must be at least one objective with at least one key result in the project. - You can schedule reminders only for top-level objectives. Scheduling a check-in reminder for child objectives has no effect. @@ -410,10 +425,11 @@ To turn off a check-in reminder, enter: ## Set an objective as a parent > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/11198) in GitLab 16.6. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. - The parent objective and child OKR must belong to the same project. To set an objective as a parent of an OKR: @@ -436,6 +452,8 @@ leaking out. ### Make an OKR confidential +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + By default, OKRs are public. You can make an OKR confidential when you create or edit it. @@ -450,7 +468,7 @@ Select that checkbox and then select **Create objective** or **Create key result Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. - A **confidential objective** can have only confidential [child objectives or key results](#child-objectives-and-key-results): - To make an objective confidential: If it has any child objectives or key results, you must first @@ -468,7 +486,9 @@ To change the confidentiality of an existing OKR: ### Who can see confidential OKRs -When an OKR is made confidential, only users with at least the Reporter role for the project have +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + +When an OKR is made confidential, only users with at least the Planner role for the project have access to the OKR. Users with Guest or [Minimal](permissions.md#users-with-minimal-access) roles can't access the OKR even if they were actively participating before the change. @@ -502,6 +522,7 @@ system note in the OKR's comments, for example: ## Lock discussion > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/398649) in GitLab 16.9 [with a flag](../administration/feature_flags.md) named `work_items_beta`. Disabled by default. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. FLAG: On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../administration/feature_flags.md) named `work_items_beta`. @@ -513,7 +534,7 @@ When you do, only project members can add and edit comments. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. To lock an OKR: diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 23285366db6..6f427c4ff70 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -35,6 +35,7 @@ You can assign users a default role or a [custom role](custom_roles.md). The available default roles are: - Guest (This role applies to [private and internal projects](../user/public_access.md) only.) +- Planner - Reporter - Developer - Maintainer @@ -70,38 +71,38 @@ The following tables list the project permissions available for each role. Project permissions for [analytics](../user/analytics/index.md) features including value streams, usage trends, product analytics, and insights. -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -| ------------------------------------------------------------------------------------------ | :---: | :------: | :-------: | :--------: | :---: | ----- | -| View [issue analytics](group/issues_analytics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [value stream analytics](group/value_stream_analytics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [CI/CD analytics](analytics/ci_cd_analytics.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [code review analytics](analytics/code_review_analytics.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [DORA metrics](analytics/ci_cd_analytics.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [merge request analytics](analytics/merge_request_analytics.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [repository analytics](analytics/repository_analytics.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [Value Streams Dashboard & AI Impact analytics](analytics/value_streams_dashboard.md) | | ✓ | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------------------------------ | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View [issue analytics](group/issues_analytics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [value stream analytics](group/value_stream_analytics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [CI/CD analytics](analytics/ci_cd_analytics.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [code review analytics](analytics/code_review_analytics.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [DORA metrics](analytics/ci_cd_analytics.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [merge request analytics](analytics/merge_request_analytics.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [repository analytics](analytics/repository_analytics.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [Value Streams Dashboard & AI Impact analytics](analytics/value_streams_dashboard.md) | | | ✓ | ✓ | ✓ | ✓ | | ### Application security Project permissions for [application security](application_security/secure_your_application.md) features including dependency management, security analyzers, security policies, and vulnerability management. -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-------------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ | | -| View licenses in [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ | | -| View [security dashboard](application_security/security_dashboard/index.md) | | | ✓ | ✓ | ✓ | | -| View [vulnerability report](application_security/vulnerability_report/index.md) | | | ✓ | ✓ | ✓ | | -| Create [vulnerability manually](application_security/vulnerability_report/index.md#manually-add-a-vulnerability) | | | ✓ | ✓ | ✓ | | -| Create [issue](application_security/vulnerabilities/index.md#create-a-gitlab-issue-for-a-vulnerability) from vulnerability finding | | | ✓ | ✓ | ✓ | | -| Create [on-demand DAST scans](application_security/dast/on-demand_scan.md) | | | ✓ | ✓ | ✓ | | -| Run [on-demand DAST scans](application_security/dast/on-demand_scan.md) | | | ✓ | ✓ | ✓ | | -| Create [individual security policies](application_security/policies/index.md) | | | ✓ | ✓ | ✓ | | -| Change [individual security policies](application_security/policies/index.md) | | | ✓ | ✓ | ✓ | | -| Delete [individual security policies](application_security/policies/index.md) | | | ✓ | ✓ | ✓ | | -| Create [CVE ID request](application_security/cve_id_request.md) | | | | ✓ | ✓ | | -| Change vulnerability status | | | | ✓ | ✓ | The `admin_vulnerability` permission was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/412693) from the Developer role in GitLab 17.0. | -| Create or assign [security policy project](application_security/policies/index.md) | | | | | ✓ | | -| Manage [security configurations](application_security/configuration/index.md) | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ---------------------------------------------------------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View [dependency list](application_security/dependency_list/index.md) | | | | ✓ | ✓ | ✓ | | +| View licenses in [dependency list](application_security/dependency_list/index.md) | | | | ✓ | ✓ | ✓ | | +| View [security dashboard](application_security/security_dashboard/index.md) | | | | ✓ | ✓ | ✓ | | +| View [vulnerability report](application_security/vulnerability_report/index.md) | | | | ✓ | ✓ | ✓ | | +| Create [vulnerability manually](application_security/vulnerability_report/index.md#manually-add-a-vulnerability) | | | | ✓ | ✓ | ✓ | | +| Create [issue](application_security/vulnerabilities/index.md#create-a-gitlab-issue-for-a-vulnerability) from vulnerability finding | | | | ✓ | ✓ | ✓ | | +| Create [on-demand DAST scans](application_security/dast/on-demand_scan.md) | | | | ✓ | ✓ | ✓ | | +| Run [on-demand DAST scans](application_security/dast/on-demand_scan.md) | | | | ✓ | ✓ | ✓ | | +| Create [individual security policies](application_security/policies/index.md) | | | | ✓ | ✓ | ✓ | | +| Change [individual security policies](application_security/policies/index.md) | | | | ✓ | ✓ | ✓ | | +| Delete [individual security policies](application_security/policies/index.md) | | | | ✓ | ✓ | ✓ | | +| Create [CVE ID request](application_security/cve_id_request.md) | | | | | ✓ | ✓ | | +| Change vulnerability status | | | | | ✓ | ✓ | The `admin_vulnerability` permission was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/412693) from the Developer role in GitLab 17.0. | +| Create or assign [security policy project](application_security/policies/index.md) | | | | | | ✓ | | +| Manage [security configurations](application_security/configuration/index.md) | | | | | | ✓ | | ### CI/CD @@ -114,44 +115,44 @@ Project permissions for [application security](application_security/secure_your_ Project Owners can perform any listed action, and can delete pipelines: -| Action | Non-member | Guest | Reporter | Developer | Maintainer | Notes | -|------------------------------------------------------------------|:----------:|:-----:|:--------:|:---------:|:----------:|-------| -| View existing artifacts | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members and guests: Only if the project is public. | -| View list of jobs | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public and **Public pipelines** is enabled in **Project Settings > CI/CD**.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | -| View artifacts | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public, **Public pipelines** is enabled in **Project Settings > CI/CD**, and [`artifacts:public: false`](../ci/yaml/index.md#artifactspublic) is not set on the job.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD** and `artifacts:public: false` is not set on the job.
Reporters: Only if `artifacts:public: false` is not set on the job. | -| Download artifacts | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public, **Public pipelines** is enabled in **Project Settings > CI/CD**, and [`artifacts:public: false`](../ci/yaml/index.md#artifactspublic) is not set on the job.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD** and `artifacts:public: false` is not set on the job.
Reporters: Only if `artifacts:public: false` is not set on the job. | -| View [environments](../ci/environments/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members and guests: Only if the project is public. | -| View job logs and job details page | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public and **Public pipelines** is enabled in **Project Settings > CI/CD**.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | -| View pipelines and pipeline details pages | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public and **Public pipelines** is enabled in **Project Settings > CI/CD**.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | -| View pipelines tab in MR | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members and guests: Only if the project is public. | -| View [vulnerabilities in a pipeline](application_security/vulnerability_report/pipeline.md#view-vulnerabilities-in-a-pipeline) | | ✓ | ✓ | ✓ | ✓ | Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | -| Run deployment job for a protected environment | | | ✓ | ✓ | ✓ | Reporters: Only if the user is [part of a group with access to the protected environment](../ci/environments/protected_environments.md#deployment-only-access-to-protected-environments).
Developers and maintainers: Only if the user is [allowed to deploy to the protected branch](../ci/environments/protected_environments.md#protecting-environments). | -| View [agents for Kubernetes](clusters/agent/index.md) | | | | ✓ | ✓ | | -| View project [Secure Files](../api/secure_files.md) | | | | ✓ | ✓ | | -| Download project [Secure Files](../api/secure_files.md) | | | | ✓ | ✓ | | -| View a job with [debug logging](../ci/variables/index.md#enable-debug-logging) | | | | ✓ | ✓ | | -| Create [environments](../ci/environments/index.md) | | | | ✓ | ✓ | | -| Delete [environments](../ci/environments/index.md) | | | | ✓ | ✓ | | -| Stop [environments](../ci/environments/index.md) | | | | ✓ | ✓ | | -| Run CI/CD pipeline | | | | ✓ | ✓ | | -| Run CI/CD pipeline for a protected branch | | | | ✓ | ✓ | Developers and maintainers: Only if the user is [allowed to merge or push to the protected branch](../ci/pipelines/index.md#pipeline-security-on-protected-branches). | -| Run CI/CD job | | | | ✓ | ✓ | | -| Delete job logs or job artifacts | | | | ✓ | ✓ | Developers: Only if the job was triggered by the user and runs for a non-protected branch. | -| Enable [review apps](../ci/review_apps/index.md) | | | | ✓ | ✓ | | -| Cancel jobs | | | | ✓ | ✓ | Cancellation permissions can be [restricted in the pipeline settings](../ci/pipelines/settings.md#restrict-roles-that-can-cancel-pipelines-or-jobs). | -| Retry jobs | | | | ✓ | ✓ | | -| Read [Terraform](infrastructure/index.md) state | | | | ✓ | ✓ | | -| Run [interactive web terminals](../ci/interactive_web_terminal/index.md) | | | | ✓ | ✓ | | -| Use pipeline editor | | | | ✓ | ✓ | | -| Manage [agents for Kubernetes](clusters/agent/index.md) | | | | | ✓ | | -| Manage CI/CD settings | | | | | ✓ | | -| Manage job triggers | | | | | ✓ | | -| Manage project CI/CD variables | | | | | ✓ | | -| Manage project [Secure Files](../api/secure_files.md) | | | | | ✓ | | -| Manage [Terraform](infrastructure/index.md) state | | | | | ✓ | | -| Add project runners to project | | | | | ✓ | | -| Clear runner caches manually | | | | | ✓ | | -| Enable instance runners in project | | | | | ✓ | | +| Action | Non-member | Guest | Planner | Reporter | Developer | Maintainer | Notes | +| ------------------------------------------------------------------------------------------------------------------------------ | :--------: | :---: | :-----: | :------: | :-------: | :--------: | ----- | +| View existing artifacts | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members and guests: Only if the project is public. | +| View list of jobs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public and **Public pipelines** is enabled in **Project Settings > CI/CD**.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | +| View artifacts | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public, **Public pipelines** is enabled in **Project Settings > CI/CD**, and [`artifacts:public: false`](../ci/yaml/index.md#artifactspublic) is not set on the job.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD** and `artifacts:public: false` is not set on the job.
Reporters: Only if `artifacts:public: false` is not set on the job. | +| Download artifacts | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public, **Public pipelines** is enabled in **Project Settings > CI/CD**, and [`artifacts:public: false`](../ci/yaml/index.md#artifactspublic) is not set on the job.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD** and `artifacts:public: false` is not set on the job.
Reporters: Only if `artifacts:public: false` is not set on the job. | +| View [environments](../ci/environments/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members and guests: Only if the project is public. | +| View job logs and job details page | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public and **Public pipelines** is enabled in **Project Settings > CI/CD**.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | +| View pipelines and pipeline details pages | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members: Only if the project is public and **Public pipelines** is enabled in **Project Settings > CI/CD**.
Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | +| View pipelines tab in MR | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members and guests: Only if the project is public. | +| View [vulnerabilities in a pipeline](application_security/vulnerability_report/pipeline.md#view-vulnerabilities-in-a-pipeline) | | ✓ | ✓ | ✓ | ✓ | ✓ | Guests: Only if **Public pipelines** is enabled in **Project Settings > CI/CD**. | +| Run deployment job for a protected environment | | | | ✓ | ✓ | ✓ | Reporters: Only if the user is [part of a group with access to the protected environment](../ci/environments/protected_environments.md#deployment-only-access-to-protected-environments).
Developers and maintainers: Only if the user is [allowed to deploy to the protected branch](../ci/environments/protected_environments.md#protecting-environments). | +| View [agents for Kubernetes](clusters/agent/index.md) | | | | | ✓ | ✓ | | +| View project [Secure Files](../api/secure_files.md) | | | | | ✓ | ✓ | | +| Download project [Secure Files](../api/secure_files.md) | | | | | ✓ | ✓ | | +| View a job with [debug logging](../ci/variables/index.md#enable-debug-logging) | | | | | ✓ | ✓ | | +| Create [environments](../ci/environments/index.md) | | | | | ✓ | ✓ | | +| Delete [environments](../ci/environments/index.md) | | | | | ✓ | ✓ | | +| Stop [environments](../ci/environments/index.md) | | | | | ✓ | ✓ | | +| Run CI/CD pipeline | | | | | ✓ | ✓ | | +| Run CI/CD pipeline for a protected branch | | | | | ✓ | ✓ | Developers and maintainers: Only if the user is [allowed to merge or push to the protected branch](../ci/pipelines/index.md#pipeline-security-on-protected-branches). | +| Run CI/CD job | | | | | ✓ | ✓ | | +| Delete job logs or job artifacts | | | | | ✓ | ✓ | Developers: Only if the job was triggered by the user and runs for a non-protected branch. | +| Enable [review apps](../ci/review_apps/index.md) | | | | | ✓ | ✓ | | +| Cancel jobs | | | | | ✓ | ✓ | Cancellation permissions can be [restricted in the pipeline settings](../ci/pipelines/settings.md#restrict-roles-that-can-cancel-pipelines-or-jobs). | +| Retry jobs | | | | | ✓ | ✓ | | +| Read [Terraform](infrastructure/index.md) state | | | | | ✓ | ✓ | | +| Run [interactive web terminals](../ci/interactive_web_terminal/index.md) | | | | | ✓ | ✓ | | +| Use pipeline editor | | | | | ✓ | ✓ | | +| Manage [agents for Kubernetes](clusters/agent/index.md) | | | | | | ✓ | | +| Manage CI/CD settings | | | | | | ✓ | | +| Manage job triggers | | | | | | ✓ | | +| Manage project CI/CD variables | | | | | | ✓ | | +| Manage project [Secure Files](../api/secure_files.md) | | | | | | ✓ | | +| Manage [Terraform](infrastructure/index.md) state | | | | | | ✓ | | +| Add project runners to project | | | | | | ✓ | | +| Clear runner caches manually | | | | | | ✓ | | +| Enable instance runners in project | | | | | | ✓ | | This table shows granted privileges for jobs triggered by specific roles. @@ -159,7 +160,7 @@ Project Owners can do any listed action, but no users can push source and LFS to Guest users and members with the Reporter role cannot do any of these actions. | Action | Developer | Maintainer | Notes | -|----------------------------------------------|:---------:|:----------:|-------| +| -------------------------------------------- | :-------: | :--------: | ----- | | Clone source and LFS from current project | ✓ | ✓ | | | Clone source and LFS from public projects | ✓ | ✓ | | | Clone source and LFS from internal projects | ✓ | ✓ | Developers and Maintainers: Only if the triggering user is not an external user. | @@ -174,224 +175,228 @@ Guest users and members with the Reporter role cannot do any of these actions. Project permissions for [compliance](compliance/index.md) features including compliance center, audit events, compliance frameworks, and licenses. -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|---------------------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View [allowed and denied licenses in MR](compliance/license_scanning_of_cyclonedx_files/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be have at least the Reporter role, even if the project is internal. Users with the Guest role on GitLab.com are able to perform this action only on public projects because internal visibility is not available. | -| View [audit events](compliance/audit_events.md) | | | ✓ | ✓ | ✓ | Users can only view events based on their individual actions. For more details, see the [prerequisites](compliance/audit_events.md#prerequisites). | -| View licenses in [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ | | -| Manage [audit streams](compliance/audit_event_streaming.md) | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View [allowed and denied licenses in MR](compliance/license_scanning_of_cyclonedx_files/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be have at least the Reporter role, even if the project is internal. Users with the Guest role on GitLab.com are able to perform this action only on public projects because internal visibility is not available. | +| View [audit events](compliance/audit_events.md) | | | | ✓ | ✓ | ✓ | Users can only view events based on their individual actions. For more details, see the [prerequisites](compliance/audit_events.md#prerequisites). | +| View licenses in [dependency list](application_security/dependency_list/index.md) | | | | ✓ | ✓ | ✓ | | +| Manage [audit streams](compliance/audit_event_streaming.md) | | | | | | ✓ | | ### Machine learning model registry and experiment Project permissions for [model registry](project/ml/model_registry/index.md) and [model experiments](project/ml/experiment_tracking/index.md). -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| -| View [models and versions](project/ml/model_registry/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members can only view models and versions in public projects with the **Everyone with access** visibility level. Non-members can't view internal projects, even if they're logged in. | -| View [model experiments](project/ml/experiment_tracking/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members can only view model experiments in public projects with the **Everyone with access** visibility level. Non-members can't view internal projects, even if they're logged in. | -| Create models, versions, and artifacts | | | ✓ | ✓ | ✓ | You can also upload and download artifacts with the package registry API, which uses it's own set of permissions. | -| Edit models, versions, and artifacts | | | ✓ | ✓ | ✓ | | -| Create experiments and candidates | | | ✓ | ✓ | ✓ | | -| Edit experiments and candidates | | | ✓ | ✓ | ✓ | | -| Delete experiments and candidates | | | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | :---: | +| View [models and versions](project/ml/model_registry/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members can only view models and versions in public projects with the **Everyone with access** visibility level. Non-members can't view internal projects, even if they're logged in. | +| View [model experiments](project/ml/experiment_tracking/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Non-members can only view model experiments in public projects with the **Everyone with access** visibility level. Non-members can't view internal projects, even if they're logged in. | +| Create models, versions, and artifacts | | | | ✓ | ✓ | ✓ | You can also upload and download artifacts with the package registry API, which uses it's own set of permissions. | +| Edit models, versions, and artifacts | | | | ✓ | ✓ | ✓ | | +| Create experiments and candidates | | | | ✓ | ✓ | ✓ | | +| Edit experiments and candidates | | | | ✓ | ✓ | ✓ | | +| Delete experiments and candidates | | | | ✓ | ✓ | ✓ | | ### Monitoring Project permissions for monitoring including [error tracking](../operations/error_tracking.md) and [incident management](../operations/incident_management/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|---------------------------------------------------------------------------------------------------------------------| :----:| :------: | :-------: | :--------: | :---: | :---: | -| View an [incident](../operations/incident_management/incidents.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Assign an [incident management](../operations/incident_management/index.md) alert | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Participate in on-call rotation for [Incident Management](../operations/incident_management/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [alerts](../operations/incident_management/alerts.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [error tracking](../operations/error_tracking.md) list | | ✓ | ✓ | ✓ | ✓ | | -| View [escalation policies](../operations/incident_management/escalation_policies.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [on-call schedules](../operations/incident_management/oncall_schedules.md) | | ✓ | ✓ | ✓ | ✓ | | -| Create [incident](../operations/incident_management/incidents.md) | | ✓ | ✓ | ✓ | ✓ | | -| Change [alert status](../operations/incident_management/alerts.md#change-an-alerts-status) | | ✓ | ✓ | ✓ | ✓ | | -| Change [incident severity](../operations/incident_management/manage_incidents.md#change-severity) | | ✓ | ✓ | ✓ | ✓ | | -| Change [incident escalation status](../operations/incident_management/manage_incidents.md#change-status) | | | ✓ | ✓ | ✓ | | -| Change [incident escalation policy](../operations/incident_management/manage_incidents.md#change-escalation-policy) | | | ✓ | ✓ | ✓ | | -| Manage [error tracking](../operations/error_tracking.md) | | | | ✓ | ✓ | | -| Manage [escalation policies](../operations/incident_management/escalation_policies.md) | | | | ✓ | ✓ | | -| Manage [on-call schedules](../operations/incident_management/oncall_schedules.md) | | | | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | :---: | +| View an [incident](../operations/incident_management/incidents.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Assign an [incident management](../operations/incident_management/index.md) alert | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Participate in on-call rotation for [Incident Management](../operations/incident_management/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [alerts](../operations/incident_management/alerts.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [error tracking](../operations/error_tracking.md) list | | | ✓ | ✓ | ✓ | ✓ | | +| View [escalation policies](../operations/incident_management/escalation_policies.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [on-call schedules](../operations/incident_management/oncall_schedules.md) | | | ✓ | ✓ | ✓ | ✓ | | +| Create [incident](../operations/incident_management/incidents.md) | | | ✓ | ✓ | ✓ | ✓ | | +| Change [alert status](../operations/incident_management/alerts.md#change-an-alerts-status) | | | ✓ | ✓ | ✓ | ✓ | | +| Change [incident severity](../operations/incident_management/manage_incidents.md#change-severity) | | | ✓ | ✓ | ✓ | ✓ | | +| Change [incident escalation status](../operations/incident_management/manage_incidents.md#change-status) | | | | ✓ | ✓ | ✓ | | +| Change [incident escalation policy](../operations/incident_management/manage_incidents.md#change-escalation-policy) | | | | ✓ | ✓ | ✓ | | +| Manage [error tracking](../operations/error_tracking.md) | | | | | ✓ | ✓ | | +| Manage [escalation policies](../operations/incident_management/escalation_policies.md) | | | | | ✓ | ✓ | | +| Manage [on-call schedules](../operations/incident_management/oncall_schedules.md) | | | | | ✓ | ✓ | | ### Project planning Project permissions for [issues](project/issues/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-----------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View issues | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Create issues | ✓ | ✓ | ✓ | ✓ | ✓ | Authors and assignees can modify the title and description even if they don't have the Reporter role. | -| View [confidential issues](project/issues/confidential_issues.md) | | ✓ | ✓ | ✓ | ✓ | | -| Update metadata on issues | | ✓ | ✓ | ✓ | ✓ | Metadata includes labels, assignees, milestones, epics, weight, confidentiality, time tracking, and more.

Guest users can only set metadata when creating an issue. They cannot change the metadata on existing issues. | -| Close / reopen issues | | ✓ | ✓ | ✓ | ✓ | Authors and assignees can close and reopen issues even if they don't have the Reporter role. | -| Manage [design management](project/issues/design_management.md) files | | ✓ | ✓ | ✓ | ✓ | | -| Manage [issue boards](project/issue_board.md) | | ✓ | ✓ | ✓ | ✓ | | -| Manage [milestones](project/milestones/index.md) | | ✓ | ✓ | ✓ | ✓ | | -| Archive or reopen [requirements](project/requirements/index.md) | | ✓ | ✓ | ✓ | ✓ | Authors and assignees can archive and re-open even if they don't have the Reporter role. | -| Create or edit [requirements](project/requirements/index.md) | | ✓ | ✓ | ✓ | ✓ | Authors and assignees can modify the title and description even if they don't have the Reporter role.| -| Import or export [requirements](project/requirements/index.md) | | ✓ | ✓ | ✓ | ✓ | | -| Archive [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | | -| Create [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | | -| Move [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | | -| Reopen [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | | -| Manage [Feature flags](../operations/feature_flags.md) | | | ✓ | ✓ | ✓ | | -| Delete issues | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| --------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | :---: | +| View issues | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Create issues | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Authors and assignees can modify the title and description even if they don't have the Reporter role. | +| View [confidential issues](project/issues/confidential_issues.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Update metadata on issues | | ✓ | ✓ | ✓ | ✓ | ✓ | Metadata includes labels, assignees, milestones, epics, weight, confidentiality, time tracking, and more.

Guest users can only set metadata when creating an issue. They cannot change the metadata on existing issues. | +| Add internal note | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Close / reopen issues | | ✓ | ✓ | ✓ | ✓ | ✓ | Authors and assignees can close and reopen issues even if they don't have the Reporter role. | +| Manage [design management](project/issues/design_management.md) files | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Manage [issue boards](project/issue_board.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Manage [milestones](project/milestones/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Archive or reopen [requirements](project/requirements/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | Authors and assignees can archive and re-open even if they don't have the Reporter role. | +| Create or edit [requirements](project/requirements/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | Authors and assignees can modify the title and description even if they don't have the Reporter role. | +| Import or export [requirements](project/requirements/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Archive [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Create [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Move [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Reopen [test cases](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Manage [Feature flags](../operations/feature_flags.md) | | | | ✓ | ✓ | ✓ | | +| Delete issues | | ✓ | | | | ✓ | | Project permissions for [tasks](tasks.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View tasks | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Create tasks | ✓ | ✓ | ✓ | ✓ | ✓ | Guest users can create tasks for issues they authored. Authors and assignees can modify the title and description even if they don't have the Reporter role. | -| Add a linked item | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Remove from issue | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Edit tasks | | ✓ | ✓ | ✓ | ✓ | | -| Delete tasks | | | | | ✓ | Authors of tasks can delete them even if they don't have the Owner role. | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View tasks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Create tasks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Guest users can create tasks for issues they authored. Authors and assignees can modify the title and description even if they don't have the Reporter role. | +| Add a linked item | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Remove from issue | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Edit tasks | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Add internal note | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Delete tasks | | ✓ | | | | ✓ | Authors of tasks can delete them even if they don't have the Owner role. | Project permissions for [OKRs](okrs.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View OKRs | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Create OKRs | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Add a child OKR | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Add a linked item | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Edit OKRs | | ✓ | ✓ | ✓ | ✓ | | -| Change confidentiality in OKR | | ✓ | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View OKRs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Create OKRs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Add a child OKR | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Add a linked item | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Edit OKRs | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Change confidentiality in OKR | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Add internal note | | ✓ | ✓ | ✓ | ✓ | ✓ | | Project permissions for [wikis](project/wiki/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -| ----------------- | :---: | :------: | :-------: | :--------: | :---: | ----- | -| View wiki | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Create wiki pages | | | ✓ | ✓ | ✓ | | -| Edit wiki pages | | | ✓ | ✓ | ✓ | | -| Delete wiki pages | | | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Create wiki pages | | ✓ | | ✓ | ✓ | ✓ | | +| Edit wiki pages | | ✓ | | ✓ | ✓ | ✓ | | +| Delete wiki pages | | ✓ | | ✓ | ✓ | ✓ | | ### Packages and registry Project permissions for [container registry](../user/packages/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| Pull an image from the container registry | ✓ | ✓ | ✓ | ✓ | ✓ | The ability to view the container registry and pull images is controlled by the [container registry's visibility permissions](packages/container_registry/index.md#container-registry-visibility-permissions). | -| Push an image to the container registry | | | ✓ | ✓ | ✓ | | -| Delete a container registry image | | | ✓ | ✓ | ✓ | | -| Manage cleanup policies | | | | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Pull an image from the container registry | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | The ability to view the container registry and pull images is controlled by the [container registry's visibility permissions](packages/container_registry/index.md#container-registry-visibility-permissions). | +| Push an image to the container registry | | | | ✓ | ✓ | ✓ | | +| Delete a container registry image | | | | ✓ | ✓ | ✓ | | +| Manage cleanup policies | | | | | ✓ | ✓ | | Project permissions for [package registry](../user/packages/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-----------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| Pull a package | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | -| Publish a package | | | ✓ | ✓ | ✓ | | -| Delete a package | | | | ✓ | ✓ | | -| Delete a file associated with a package | | | | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| --------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Pull a package | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | +| Publish a package | | | | ✓ | ✓ | ✓ | | +| Delete a package | | | | | ✓ | ✓ | | +| Delete a file associated with a package | | | | | ✓ | ✓ | | ### Projects Project permissions for [project features](project/organize_work_with_projects.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|---------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| Download project | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | -| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Reposition comments on images (posted by any user) | ✓ | ✓ | ✓ | ✓ | ✓ | Applies only to comments on [Design Management](project/issues/design_management.md) designs. | -| View [Insights](project/insights/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [Requirements](project/requirements/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [time tracking](project/time_tracking.md) reports | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | -| View [snippets](snippets.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [project traffic statistics](../api/project_statistics.md) | | ✓ | ✓ | ✓ | ✓ | | -| Create [snippets](snippets.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [releases](project/releases/index.md) | | | ✓ | ✓ | ✓ | Guest users can access GitLab [**Releases**](project/releases/index.md) for downloading assets but are not allowed to download the source code nor see [repository information like commits and release evidence](project/releases/index.md#view-a-release-and-download-assets). | -| Manage [releases](project/releases/index.md) | | | | ✓ | ✓ | If the [tag is protected](project/protected_tags.md), this depends on the access given to Developers and Maintainers. | -| Configure [webhooks](project/integrations/webhooks.md) | | | | ✓ | ✓ | | -| Manage [project access tokens](project/settings/project_access_tokens.md) | | | | ✓ | ✓ | For self-managed GitLab, project access tokens are available in all tiers. For GitLab.com, project access tokens are supported in the Premium and Ultimate tier (excluding [trial licenses](https://about.gitlab.com/free-trial/)). | -| [Export project](project/settings/import_export.md) | | | | ✓ | ✓ | | -| Rename project | | | | ✓ | ✓ | | -| Edit project badges | | | | ✓ | ✓ | | -| Edit project settings | | | | ✓ | ✓ | | -| Change [project features visibility](public_access.md) level | | | | ✓ | ✓ | A Maintainer or Owner can't change project features visibility level if [project visibility](public_access.md) is set to private. | -| Edit comments (posted by any user) | | | | ✓ | ✓ | | -| Add [deploy keys](project/deploy_keys/index.md) | | | | ✓ | ✓ | | -| Manage [Project Operations](../operations/index.md) | | | | ✓ | ✓ | | -| View [Usage Quotas](storage_usage_quotas.md) page | | | | ✓ | ✓ | | -| Globally delete [snippets](snippets.md) | | | | ✓ | ✓ | | -| Globally edit [snippets](snippets.md) | | | | ✓ | ✓ | | -| Archive project | | | | | ✓ | | -| Change project visibility level | | | | | ✓ | | -| Delete project | | | | | ✓ | | -| Disable notification emails | | | | | ✓ | | -| Transfer project | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Download project | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | +| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Reposition comments on images (posted by any user) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Applies only to comments on [Design Management](project/issues/design_management.md) designs. | +| View [Insights](project/insights/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [Requirements](project/requirements/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [time tracking](project/time_tracking.md) reports | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | +| View [snippets](snippets.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [project traffic statistics](../api/project_statistics.md) | | | ✓ | ✓ | ✓ | ✓ | | +| Create [snippets](snippets.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [releases](project/releases/index.md) | | ✓ | | ✓ | ✓ | ✓ | Guest users can access GitLab [**Releases**](project/releases/index.md) for downloading assets but are not allowed to download the source code nor see [repository information like commits and release evidence](project/releases/index.md#view-a-release-and-download-assets). | +| Manage [releases](project/releases/index.md) | | | | | ✓ | ✓ | If the [tag is protected](project/protected_tags.md), this depends on the access given to Developers and Maintainers. | +| Configure [webhooks](project/integrations/webhooks.md) | | | | | ✓ | ✓ | | +| Manage [project access tokens](project/settings/project_access_tokens.md) | | | | | ✓ | ✓ | For self-managed GitLab, project access tokens are available in all tiers. For GitLab.com, project access tokens are supported in the Premium and Ultimate tier (excluding [trial licenses](https://about.gitlab.com/free-trial/)). | +| [Export project](project/settings/import_export.md) | | | | | ✓ | ✓ | | +| Rename project | | | | | ✓ | ✓ | | +| Edit project badges | | | | | ✓ | ✓ | | +| Edit project settings | | | | | ✓ | ✓ | | +| Change [project features visibility](public_access.md) level | | | | | ✓ | ✓ | A Maintainer or Owner can't change project features visibility level if [project visibility](public_access.md) is set to private. | +| Edit comments (posted by any user) | | | | | ✓ | ✓ | | +| Add [deploy keys](project/deploy_keys/index.md) | | | | | ✓ | ✓ | | +| Manage [Project Operations](../operations/index.md) | | | | | ✓ | ✓ | | +| View [Usage Quotas](storage_usage_quotas.md) page | | | | | ✓ | ✓ | | +| Globally delete [snippets](snippets.md) | | | | | ✓ | ✓ | | +| Globally edit [snippets](snippets.md) | | | | | ✓ | ✓ | | +| Archive project | | | | | | ✓ | | +| Change project visibility level | | | | | | ✓ | | +| Delete project | | | | | | ✓ | | +| Disable notification emails | | | | | | ✓ | | +| Transfer project | | | | | | ✓ | | Project permissions for [GitLab Pages](project/pages/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-----------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View GitLab Pages protected by [access control](project/pages/pages_access_control.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Manage GitLab Pages | | | | ✓ | ✓ | | -| Manage GitLab Pages domain and certificates | | | | ✓ | ✓ | | -| Remove GitLab Pages | | | | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| -------------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View GitLab Pages protected by [access control](project/pages/pages_access_control.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Manage GitLab Pages | | | | | ✓ | ✓ | | +| Manage GitLab Pages domain and certificates | | | | | ✓ | ✓ | | +| Remove GitLab Pages | | | | | ✓ | ✓ | | ### Repository Project permissions for [repository](project/repository/index.md) features including source code, branches, push rules, and more: -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-----------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View project code | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. In GitLab 15.9 and later, users with the Guest role and an Ultimate license can view private repository content if an administrator (on self-managed or GitLab Dedicated) or group owner (on GitLab.com) gives those users permission. The administrator or group owner can create a [custom role](custom_roles.md) through the API or UI and assign that role to the users. | -| Pull project code | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | -| View commit status | | ✓ | ✓ | ✓ | ✓ | | -| Create commit status | | | ✓ | ✓ | ✓ | If the [branch is protected](project/repository/branches/protected.md), this depends on the access given to Developers and Maintainers. | -| Update commit status | | | ✓ | ✓ | ✓ | If the [branch is protected](project/repository/branches/protected.md), this depends on the access given to Developers and Maintainers. | -| Create [Git tags](project/repository/tags/index.md) | | | ✓ | ✓ | ✓ | | -| Delete [Git tags](project/repository/tags/index.md) | | | ✓ | ✓ | ✓ | | -| Create new [branches](project/repository/branches/index.md) | | | ✓ | ✓ | ✓ | | -| Delete non-protected branches | | | ✓ | ✓ | ✓ | | -| Force push to non-protected branches | | | ✓ | ✓ | ✓ | | -| Push to non-protected branches | | | ✓ | ✓ | ✓ | | -| Manage [protected branches](project/repository/branches/protected.md) | | | | ✓ | ✓ | | -| Delete protected branches | | | | ✓ | ✓ | | -| Push to protected branches | | | | ✓ | ✓ | If the [branch is protected](project/repository/branches/protected.md), this depends on the access given to Developers and Maintainers. | -| Manage [protected tags](project/protected_tags.md) | | | | ✓ | ✓ | | -| Manage [push rules](project/repository/push_rules.md) | | | | ✓ | ✓ | | -| Remove fork relationship | | | | | ✓ | | -| Force push to protected branches | | | | | | Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [protected branches](project/repository/branches/protected.md#allow-force-push-on-a-protected-branch). | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| --------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View project code | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. In GitLab 15.9 and later, users with the Guest role and an Ultimate license can view private repository content if an administrator (on self-managed or GitLab Dedicated) or group owner (on GitLab.com) gives those users permission. The administrator or group owner can create a [custom role](custom_roles.md) through the API or UI and assign that role to the users. | +| Pull project code | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | +| View commit status | | | ✓ | ✓ | ✓ | ✓ | | +| Create commit status | | | | ✓ | ✓ | ✓ | If the [branch is protected](project/repository/branches/protected.md), this depends on the access given to Developers and Maintainers. | +| Update commit status | | | | ✓ | ✓ | ✓ | If the [branch is protected](project/repository/branches/protected.md), this depends on the access given to Developers and Maintainers. | +| Create [Git tags](project/repository/tags/index.md) | | | | ✓ | ✓ | ✓ | | +| Delete [Git tags](project/repository/tags/index.md) | | | | ✓ | ✓ | ✓ | | +| Create new [branches](project/repository/branches/index.md) | | | | ✓ | ✓ | ✓ | | +| Delete non-protected branches | | | | ✓ | ✓ | ✓ | | +| Force push to non-protected branches | | | | ✓ | ✓ | ✓ | | +| Push to non-protected branches | | | | ✓ | ✓ | ✓ | | +| Manage [protected branches](project/repository/branches/protected.md) | | | | | ✓ | ✓ | | +| Delete protected branches | | | | | ✓ | ✓ | | +| Push to protected branches | | | | | ✓ | ✓ | If the [branch is protected](project/repository/branches/protected.md), this depends on the access given to Developers and Maintainers. | +| Manage [protected tags](project/protected_tags.md) | | | | | ✓ | ✓ | | +| Manage [push rules](project/repository/push_rules.md) | | | | | ✓ | ✓ | | +| Remove fork relationship | | | | | | ✓ | | +| Force push to protected branches | | | | | | | Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [protected branches](project/repository/branches/protected.md#allow-force-push-on-a-protected-branch). | Project permissions for [merge requests](project/merge_requests/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|--------------------------------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| [View](project/merge_requests/index.md#view-merge-requests) a merge request | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | -| Create [snippets](snippets.md) | | ✓ | ✓ | ✓ | ✓ | | -| Create [merge request](project/merge_requests/creating_merge_requests.md) | | | ✓ | ✓ | ✓ | In projects that accept contributions from external members, users can create, edit, and close their own merge requests. For **private** projects, this excludes the Guest role as those users [cannot clone private projects](public_access.md#private-projects-and-groups). For **internal** projects, includes users with read-only access to the project, as [they can clone internal projects](public_access.md#internal-projects-and-groups). | -| Update merge request including assign, review, Code Suggestions, approve, labels, lock and resolve threads | | | ✓ | ✓ | ✓ | For information on eligible approvers for merge requests, see [Eligible approvers](project/merge_requests/approvals/rules.md#eligible-approvers). | -| Manage [merge request settings](project/merge_requests/approvals/settings.md) | | | | ✓ | ✓ | | -| Manage [merge request approval rules](project/merge_requests/approvals/rules.md) | | | | ✓ | ✓ | | -| Delete merge request | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ---------------------------------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| [View](project/merge_requests/index.md#view-merge-requests) a merge request | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | On self-managed GitLab instances, users with the Guest role are able to perform this action only on public and internal projects (not on private projects). [External users](../administration/external_users.md) must be given explicit access (at least the **Reporter** role) even if the project is internal. Users with the Guest role on GitLab.com are only able to perform this action on public projects because internal visibility is not available. | +| Create [snippets](snippets.md) | | | ✓ | ✓ | ✓ | ✓ | | +| Create [merge request](project/merge_requests/creating_merge_requests.md) | | | | ✓ | ✓ | ✓ | In projects that accept contributions from external members, users can create, edit, and close their own merge requests. For **private** projects, this excludes the Guest role as those users [cannot clone private projects](public_access.md#private-projects-and-groups). For **internal** projects, includes users with read-only access to the project, as [they can clone internal projects](public_access.md#internal-projects-and-groups). | +| Update merge request including assign, review, Code Suggestions, approve, labels, lock and resolve threads | | | | ✓ | ✓ | ✓ | For information on eligible approvers for merge requests, see [Eligible approvers](project/merge_requests/approvals/rules.md#eligible-approvers). | +| Manage [merge request settings](project/merge_requests/approvals/settings.md) | | | | | ✓ | ✓ | | +| Manage [merge request approval rules](project/merge_requests/approvals/rules.md) | | | | | ✓ | ✓ | | +| Add internal note | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Delete merge request | | | | | | ✓ | | ### User management Project permissions for [user management](project/members/index.md). -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| Manage [team members](project/members/index.md) | | | | ✓ | ✓ | Maintainers cannot create, demote, or remove Owners, and they cannot promote users to the Owner role. They also cannot approve Owner role access requests. | -| Share (invite) projects with groups | | | | ✓ | ✓ | When [Share Group Lock](project/members/sharing_projects_groups.md#prevent-a-project-from-being-shared-with-groups) is enabled the project can't be shared with other groups. It does not affect group with group sharing. | -| View 2FA status of members | | | | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Manage [team members](project/members/index.md) | | | | | ✓ | ✓ | Maintainers cannot create, demote, or remove Owners, and they cannot promote users to the Owner role. They also cannot approve Owner role access requests. | +| Share (invite) projects with groups | | | | | ✓ | ✓ | When [Share Group Lock](project/members/sharing_projects_groups.md#prevent-a-project-from-being-shared-with-groups) is enabled the project can't be shared with other groups. It does not affect group with group sharing. | +| View 2FA status of members | | | | | ✓ | ✓ | | ### GitLab Duo Project permissions for [GitLab Duo](gitlab_duo/index.md): -| Action | Non-member | Guest | Reporter | Developer | Maintainer | Owner | Notes | -| -------------------------------------------------------------------------------------- | ---------- | ----- | -------- | --------- | ---------- | ----- | ----- | -| Use Duo features | | ✓ | ✓ | ✓ | ✓ | ✓ | Code Suggestions requires a [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats). | -| Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-project) | | | | | ✓ | ✓ | | +| Action | Non-member | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| -------------------------------------------------------------------------------------- | ---------- | ----- | ------- | -------- | --------- | ---------- | ----- | ----- | +| Use Duo features | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Code Suggestions requires a [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats). | +| Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-project) | | | | | | ✓ | ✓ | | ## Group members permissions @@ -404,177 +409,178 @@ The following table lists group permissions available for each role: Group permission for [analytics](../user/analytics/index.md) features including value streams, product analytics, and insights: -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-----------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View [Insights](project/insights/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [Insights](project/insights/index.md) charts | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [Issue analytics](group/issues_analytics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View Contribution analytics | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View value stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ | | -| View [Productivity analytics](analytics/productivity_analytics.md) | | ✓ | ✓ | ✓ | ✓ | | -| View [Group DevOps Adoption](group/devops_adoption/index.md) | | ✓ | ✓ | ✓ | ✓ | | -| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ | | -| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------ | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View [Insights](project/insights/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [Insights](project/insights/index.md) charts | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [Issue analytics](group/issues_analytics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View Contribution analytics | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View value stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| View [Productivity analytics](analytics/productivity_analytics.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View [Group DevOps Adoption](group/devops_adoption/index.md) | | | ✓ | ✓ | ✓ | ✓ | | +| View metrics dashboard annotations | | | ✓ | ✓ | ✓ | ✓ | | +| Create/edit/delete metrics dashboard annotations | | | | ✓ | ✓ | ✓ | | ### Application security group permissions Group permissions for [Application Security](application_security/secure_your_application.md) features including dependency management, security analyzers, security policies, and vulnerability management. -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ | | -| View [vulnerability report](application_security/vulnerability_report/index.md) | | | ✓ | ✓ | ✓ | | -| View [security dashboard](application_security/security_dashboard/index.md) | | | ✓ | ✓ | ✓ | | -| Create [security policy project](application_security/policies/index.md) | | | | | ✓ | | -| Assign [security policy project](application_security/policies/index.md) | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View [dependency list](application_security/dependency_list/index.md) | | | | ✓ | ✓ | ✓ | | +| View [vulnerability report](application_security/vulnerability_report/index.md) | | | | ✓ | ✓ | ✓ | | +| View [security dashboard](application_security/security_dashboard/index.md) | | | | ✓ | ✓ | ✓ | | +| Create [security policy project](application_security/policies/index.md) | | | | | | ✓ | | +| Assign [security policy project](application_security/policies/index.md) | | | | | | ✓ | | ### CI/CD group permissions Group permissions for [CI/CD](../ci/index.md) features including runners, variables, and protected environments: -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|---------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View group runners | | | | ✓ | ✓ | | -| Manage group-level Kubernetes cluster | | | | ✓ | ✓ | | -| Manage group runners | | | | | ✓ | | -| Manage group level CI/CD variables | | | | | ✓ | | -| Manage group protected environments | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View group runners | | | | | ✓ | ✓ | | +| Manage group-level Kubernetes cluster | | | | | ✓ | ✓ | | +| Manage group runners | | | | | | ✓ | | +| Manage group level CI/CD variables | | | | | | ✓ | | +| Manage group protected environments | | | | | | ✓ | | ### Compliance group permissions Group permissions for [compliance](compliance/index.md) features including compliance center, audit events, compliance frameworks, and licenses. -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|---------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View [audit events](compliance/audit_events.md) | | | ✓ | ✓ | ✓ | Users can view only events based on their individual actions. For more details, see the [prerequisites](compliance/audit_events.md#prerequisites). | -| View licenses in the [dependency list](application_security/dependency_list/index.md) | | | ✓ | ✓ | ✓ | | -| View the [compliance center](compliance/compliance_center/index.md) | | | | | ✓ | | -| Manage [compliance frameworks](group/compliance_frameworks.md) | | | | | ✓ | | -| Assign [compliance frameworks](group/compliance_frameworks.md) to projects | | | | | ✓ | | -| Manage [audit streams](compliance/audit_event_streaming.md) | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View [audit events](compliance/audit_events.md) | | | | ✓ | ✓ | ✓ | Users can view only events based on their individual actions. For more details, see the [prerequisites](compliance/audit_events.md#prerequisites). | +| View licenses in the [dependency list](application_security/dependency_list/index.md) | | | | ✓ | ✓ | ✓ | | +| View the [compliance center](compliance/compliance_center/index.md) | | | | | | ✓ | | +| Manage [compliance frameworks](group/compliance_frameworks.md) | | | | | | ✓ | | +| Assign [compliance frameworks](group/compliance_frameworks.md) to projects | | | | | | ✓ | | +| Manage [audit streams](compliance/audit_event_streaming.md) | | | | | | ✓ | | ### GitLab Duo group permissions Group permissions for [GitLab Duo](../user/gitlab_duo/index.md): -| Action | Non-member | Guest | Reporter | Developer | Maintainer | Owner | Notes | -| --------------------------------------------------------------------------------------------------------- | ---------- | ----- | -------- | --------- | ---------- | ----- | ----- | -| Use Duo features | | | ✓ | ✓ | ✓ | ✓ | Requires [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats). | -| Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-group) | | | | | ✓ | ✓ | | -| Configure [self-hosted models](../administration/self_hosted_models/configure_duo_features.md) | | | | | | ✓ | | -| Enable [beta and experimental features](gitlab_duo/turn_on_off.md#turn-on-beta-and-experimental-features) | | | | | | ✓ | | -| Purchase [Duo seats](../subscriptions/subscription-add-ons.md#purchase-additional-gitlab-duo-seats) | | | | | | ✓ | | +| Action | Non-member | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| --------------------------------------------------------------------------------------------------------- | :--------: | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Use Duo features | | | | ✓ | ✓ | ✓ | ✓ | Requires [user being assigned a seat to gain access to a Duo add-on](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-seats). | +| Configure [Duo feature availability](gitlab_duo/turn_on_off.md#turn-off-for-a-group) | | | | | | ✓ | ✓ | | +| Configure [self-hosted models](../administration/self_hosted_models/configure_duo_features.md) | | | | | | | ✓ | | +| Enable [beta and experimental features](gitlab_duo/turn_on_off.md#turn-on-beta-and-experimental-features) | | | | | | | ✓ | | +| Purchase [Duo seats](../subscriptions/subscription-add-ons.md#purchase-additional-gitlab-duo-seats) | | | | | | | ✓ | | ### Groups group permissions Group permissions for [group features](../user/group/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|--------------------------------------------------------------------------------------------|-------|----------|-----------|------------|-------|-------| -| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Create project in group | | | ✓ | ✓ | ✓ | Developers, Maintainers and Owners: Only if the project creation role is set at the [instance level](../administration/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects) or the [group level](group/index.md#specify-who-can-add-projects-to-a-group).

Developers: Developers can push commits to the default branch of a new project only if the [default branch protection](group/manage.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected". | -| View group [audit events](compliance/audit_events.md) | | | ✓ | ✓ | ✓ | Developers and Maintainers can only view events based on their individual actions. For more details, see the [prerequisites](compliance/audit_events.md#prerequisites). | -| Create subgroup | | | | ✓ | ✓ | Maintainers: Only if users with the Maintainer role [can create subgroups](group/subgroups/index.md#change-who-can-create-subgroups). | -| Edit [epic](group/epics/index.md) comments (posted by any user) | | | | ✓ | ✓ | | -| Fork project into a group | | | | ✓ | ✓ | | -| View [Billing](../subscriptions/gitlab_com/index.md#view-gitlabcom-subscription) | | | | | ✓ | Does not apply to subgroups | -| View group [Usage Quotas](storage_usage_quotas.md) page | | | | | ✓ | Does not apply to subgroups | -| [Migrate group](group/import/index.md) | | | | | ✓ | | -| Delete group | | | | | ✓ | | -| Manage [subscriptions, storage, and compute minutes](../subscriptions/gitlab_com/index.md) | | | | | ✓ | | -| Manage [group access tokens](group/settings/group_access_tokens.md) | | | | | ✓ | | -| Change group visibility level | | | | | ✓ | | -| Edit group settings | | | | | ✓ | | -| Configure project templates | | | | | ✓ | | -| Configure [SAML SSO](group/saml_sso/index.md) | | | | | ✓ | Does not apply to subgroups | -| Disable notification emails | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------------------------------------------------ | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Create project in group | | | | ✓ | ✓ | ✓ | Developers, Maintainers and Owners: Only if the project creation role is set at the [instance level](../administration/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects) or the [group level](group/index.md#specify-who-can-add-projects-to-a-group).

Developers: Developers can push commits to the default branch of a new project only if the [default branch protection](group/manage.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected". | +| View group [audit events](compliance/audit_events.md) | | | | ✓ | ✓ | ✓ | Developers and Maintainers can only view events based on their individual actions. For more details, see the [prerequisites](compliance/audit_events.md#prerequisites). | +| Create subgroup | | | | | ✓ | ✓ | Maintainers: Only if users with the Maintainer role [can create subgroups](group/subgroups/index.md#change-who-can-create-subgroups). | +| Edit [epic](group/epics/index.md) comments (posted by any user) | | ✓ | | | ✓ | ✓ | | +| Fork project into a group | | | | | ✓ | ✓ | | +| View [Billing](../subscriptions/gitlab_com/index.md#view-gitlabcom-subscription) | | | | | | ✓ | Does not apply to subgroups | +| View group [Usage Quotas](storage_usage_quotas.md) page | | | | | | ✓ | Does not apply to subgroups | +| [Migrate group](group/import/index.md) | | | | | | ✓ | | +| Delete group | | | | | | ✓ | | +| Manage [subscriptions, storage, and compute minutes](../subscriptions/gitlab_com/index.md) | | | | | | ✓ | | +| Manage [group access tokens](group/settings/group_access_tokens.md) | | | | | | ✓ | | +| Change group visibility level | | | | | | ✓ | | +| Edit group settings | | | | | | ✓ | | +| Configure project templates | | | | | | ✓ | | +| Configure [SAML SSO](group/saml_sso/index.md) | | | | | | ✓ | Does not apply to subgroups | +| Disable notification emails | | | | | | ✓ | | ### Project planning group permissions Group permissions for project planning features including iterations, milestones, and labels: -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|-------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| Manage group labels | | ✓ | ✓ | ✓ | ✓ | | -| Manage group milestones | | ✓ | ✓ | ✓ | ✓ | | -| Manage iterations | | ✓ | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Manage group labels | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Manage group milestones | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Manage iterations | | ✓ | ✓ | ✓ | ✓ | ✓ | | Group permissions for [epics](group/epics/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|:------------------------------------------------------------------------------|:------|:---------|:----------|:-----------|:------|:------| -| View epic | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Create epic | | ✓ | ✓ | ✓ | ✓ | | -| Edit epic | | ✓ | ✓ | ✓ | ✓ | | -| Delete epic | | | | | ✓ | | -| Manage [epic boards](group/epics/epic_boards.md) | | ✓ | ✓ | ✓ | ✓ | | -| Add issue to an [epic](group/epics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | You must have permission to [view the epic](group/epics/manage_epics.md#who-can-view-an-epic) and edit the issue. | -| Add/remove [child epics](group/epics/manage_epics.md#multi-level-child-epics) | ✓ | ✓ | ✓ | ✓ | ✓ | You must have permission to [view](group/epics/manage_epics.md#who-can-view-an-epic) the parent and child epics. | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------------------------------------------------------------------- | ----- | ------- | -------- | --------- | ---------- | ----- | ----- | +| View epic | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Create epic | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Edit epic | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Delete epic | | ✓ | | | | ✓ | | +| Manage [epic boards](group/epics/epic_boards.md) | | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Add issue to an [epic](group/epics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | You must have permission to [view the epic](group/epics/manage_epics.md#who-can-view-an-epic) and edit the issue. | +| Add/remove [child epics](group/epics/manage_epics.md#multi-level-child-epics) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | You must have permission to [view](group/epics/manage_epics.md#who-can-view-an-epic) the parent and child epics. | +| Add internal note | | ✓ | ✓ | ✓ | ✓ | ✓ | | Group permissions for [wikis](project/wiki/group.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|----------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| View group wiki | ✓ | ✓ | ✓ | ✓ | ✓ | Guests: In addition, if your group is public or internal, all users who can see the group can also see group wiki pages. | -| Create group wiki pages | | | ✓ | ✓ | ✓ | | -| Edit group wiki pages | | | ✓ | ✓ | ✓ | | -| Delete group wiki pages | | | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ----------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View group wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Guests: In addition, if your group is public or internal, all users who can see the group can also see group wiki pages. | +| Create group wiki pages | | ✓ | | ✓ | ✓ | ✓ | | +| Edit group wiki pages | | ✓ | | ✓ | ✓ | ✓ | | +| Delete group wiki pages | | ✓ | | ✓ | ✓ | ✓ | | ### Packages and registries group permissions Group permissions for [container registry](../user/packages/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|---------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| Pull a container registry image | ✓ | ✓ | ✓ | ✓ | ✓ | Guests can only view events based on their individual actions. | -| Pull a container image using the dependency proxy | ✓ | ✓ | ✓ | ✓ | ✓ | | -| Delete a container registry image | | | ✓ | ✓ | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Pull a container registry image | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Guests can only view events based on their individual actions. | +| Pull a container image using the dependency proxy | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| Delete a container registry image | | | | ✓ | ✓ | ✓ | | Group permissions for [package registry](../user/packages/index.md): -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -| ---------------------------------------- | :---: | :------: | :-------: | :--------: | :---: | ----- | -| Pull packages | | ✓ | ✓ | ✓ | ✓ | | -| Publish packages | | | ✓ | ✓ | ✓ | | -| Delete packages | | | | ✓ | ✓ | | -| Manage package settings | | | | | ✓ | | -| Manage dependency proxy cleanup policies | | | | | ✓ | | -| Enable dependency proxy | | | | | ✓ | | -| Disable dependency proxy | | | | | ✓ | | -| Purge the dependency proxy for a group | | | | | ✓ | | -| Enable package request forwarding | | | | | ✓ | | -| Disable package request forwarding | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ---------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Pull packages | | | ✓ | ✓ | ✓ | ✓ | | +| Publish packages | | | | ✓ | ✓ | ✓ | | +| Delete packages | | | | | ✓ | ✓ | | +| Manage package settings | | | | | | ✓ | | +| Manage dependency proxy cleanup policies | | | | | | ✓ | | +| Enable dependency proxy | | | | | | ✓ | | +| Disable dependency proxy | | | | | | ✓ | | +| Purge the dependency proxy for a group | | | | | | ✓ | | +| Enable package request forwarding | | | | | | ✓ | | +| Disable package request forwarding | | | | | | ✓ | | ### Repository group permissions Group permissions for [repository](project/repository/index.md) features including merge requests, push rules, and deploy tokens. -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -|----------------------------------------------------------------------------------------|:-----:|:--------:|:---------:|:----------:|:-----:|-------| -| Manage [deploy tokens](project/deploy_tokens/index.md) | | | | | ✓ | | -| Manage [merge request settings](group/manage.md#group-merge-request-approval-settings) | | | | | ✓ | | -| Manage [push rules](group/access_and_permissions.md#group-push-rules) | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| -------------------------------------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| Manage [deploy tokens](project/deploy_tokens/index.md) | | | | | | ✓ | | +| Manage [merge request settings](group/manage.md#group-merge-request-approval-settings) | | | | | | ✓ | | +| Manage [push rules](group/access_and_permissions.md#group-push-rules) | | | | | | ✓ | | ### User management group permissions Group permissions for user management: -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -| ------------------------------- | :---: | :------: | :-------: | :--------: | :---: | ----- | -| View 2FA status of members | | | | | ✓ | | -| Manage group members | | | | | ✓ | | -| Manage group-level custom roles | | | | | ✓ | | -| Share (invite) groups to groups | | | | | ✓ | | -| Filter members by 2FA status | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| ------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View 2FA status of members | | | | | | ✓ | | +| Manage group members | | | | | | ✓ | | +| Manage group-level custom roles | | | | | | ✓ | | +| Share (invite) groups to groups | | | | | | ✓ | | +| Filter members by 2FA status | | | | | | ✓ | | ### Workspace group permissions Groups permissions for workspaces: -| Action | Guest | Reporter | Developer | Maintainer | Owner | Notes | -| --------------------------------------------------------- | :---: | :------: | :-------: | :--------: | :---: | ----- | -| View workspace cluster agents mapped to a group | | | | ✓ | ✓ | | -| Map or unmap workspace cluster agents to and from a group | | | | | ✓ | | +| Action | Guest | Planner | Reporter | Developer | Maintainer | Owner | Notes | +| --------------------------------------------------------- | :---: | :-----: | :------: | :-------: | :--------: | :---: | ----- | +| View workspace cluster agents mapped to a group | | | | | ✓ | ✓ | | +| Map or unmap workspace cluster agents to and from a group | | | | | | ✓ | | ## Subgroup permissions diff --git a/doc/user/profile/service_accounts.md b/doc/user/profile/service_accounts.md index 45ea6586298..a26e2ba438e 100644 --- a/doc/user/profile/service_accounts.md +++ b/doc/user/profile/service_accounts.md @@ -55,14 +55,12 @@ How you create an account differs depending on whether you are a: > - Introduced for GitLab.com in GitLab 16.3 > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163726) in GitLab 17.5 [with a feature flag](../../administration/feature_flags.md) named `allow_top_level_group_owners_to_create_service_accounts` for GitLab Self-Managed. Disabled by default. - -FLAG: -On GitLab self-managed, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `allow_top_level_group_owners_to_create_service_accounts`. On GitLab.com, this feature is available. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172502) in GitLab 17.6. Feature flag `allow_top_level_group_owners_to_create_service_accounts` removed. Prerequisites: - You must have the Owner role in a top-level group. -- For GitLab self-managed, top-level group Owners must be [allowed to create service accounts](../../administration/settings/account_and_limit_settings.md#allow-top-level-group-owners-to-create-service-accounts). +- For GitLab self-managed or GitLab Dedicated, top-level group Owners must be [allowed to create service accounts](../../administration/settings/account_and_limit_settings.md#allow-top-level-group-owners-to-create-service-accounts). 1. [Create a service account](../../api/group_service_accounts.md#create-a-service-account-user). diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index b4bcabbd1d5..99042158590 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -12,6 +12,7 @@ DETAILS: > - Milestones and iterations shown on issue cards [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25758) in GitLab 16.11. > - Ability to delete the last board in a group or project [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/499579) in GitLab 17.6. +> - Minimum role to manage issue boards [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. The issue board is a software project management tool used to plan, organize, and visualize a workflow for a feature or product release. @@ -78,7 +79,7 @@ GitLab automatically loads the last board you visited. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To create a new issue board: @@ -90,7 +91,7 @@ To create a new issue board: Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To delete the open issue board: @@ -230,7 +231,7 @@ card includes: Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. When an issue is created, the system assigns a relative order value that is greater than the maximum value of that issue's project or top-level group. This means the issue is at the bottom of any issue list that @@ -318,7 +319,7 @@ You can have a board with both label lists and assignee lists. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To add an assignee list: @@ -344,7 +345,7 @@ milestone, giving you more freedom and visibility on the issue board. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To add a milestone list: @@ -369,7 +370,7 @@ You can create lists of issues in an iteration. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To add an iteration list: @@ -398,7 +399,7 @@ For a video overview, see [Epics Swimlanes Walkthrough - 13.6](https://www.youtu Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To group issues by epic in an issue board: @@ -436,7 +437,7 @@ Examples: Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To set a WIP limit for a list, in an issue board: @@ -479,7 +480,7 @@ To open the right sidebar, select an issue card (not its title). Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. You can edit the following issue attributes in the right sidebar: @@ -537,7 +538,7 @@ list view that's removed. You can always create it again later if you need. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To remove a list from an issue board: @@ -550,7 +551,7 @@ To remove a list from an issue board: Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. If your board is scoped to one or more attributes, go to the issues you want to add and apply the same attributes as your board scope. @@ -568,7 +569,7 @@ When an issue should no longer belong to a list, you can remove it. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. The steps depend on the scope of the list: @@ -583,7 +584,7 @@ the results you want. It's similar to the filtering used in the [issue tracker]( Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. You can filter by the following: @@ -614,7 +615,7 @@ You can move issues and lists by dragging them. Prerequisites: -- You must have at least the Reporter role for a project in GitLab. +- You must have at least the Planner role for a project in GitLab. To move an issue, select the issue card and drag it to another position in its current list or into a different list. Learn about possible effects in [Dragging issues between lists](#dragging-issues-between-lists). @@ -632,7 +633,7 @@ Your issue is moved to the top of the list even if other issues are hidden by a Prerequisites: -- You must at least have the Reporter role for the project. +- You must at least have the Planner role for the project. To move an issue to the start of the list: @@ -649,7 +650,7 @@ Your issue is moved to the bottom of the list even if other issues are hidden by Prerequisites: -- You must at least have the Reporter role for the project. +- You must at least have the Planner role for the project. To move an issue to the end of the list: @@ -685,7 +686,7 @@ another list. This makes it faster to reorder many issues at once. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To select and move multiple cards: diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md index 7c135e1ae8d..09d9d5945c8 100644 --- a/doc/user/project/issues/confidential_issues.md +++ b/doc/user/project/issues/confidential_issues.md @@ -17,11 +17,13 @@ keep security vulnerabilities private or prevent surprises from leaking out. ## Make an issue confidential +> - Minimum role to make an issue confidential [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You can make an issue confidential when you create or edit an issue. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. - If the issue you want to make confidential has any child [tasks](../../tasks.md), you must first make all the child tasks confidential. A confidential issue can have only confidential children. @@ -57,7 +59,9 @@ Alternatively, you can use the `/confidential` [quick action](../quick_actions.m ## Who can see confidential issues -When an issue is made confidential, only users with at least the Reporter role +> - Minimum role to see confidential issues [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + +When an issue is made confidential, only users with at least the Planner role for the project have access to the issue. Users with Guest or [Minimal](../../permissions.md#users-with-minimal-access) roles can't access the issue even if they were actively participating before the change. diff --git a/doc/user/project/issues/csv_export.md b/doc/user/project/issues/csv_export.md index 698b1c0193d..0ae82b4a8a7 100644 --- a/doc/user/project/issues/csv_export.md +++ b/doc/user/project/issues/csv_export.md @@ -10,6 +10,8 @@ DETAILS: **Tier:** Free, Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated +> - Minimum role to export issues [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You can export issues from GitLab to a plain-text CSV ([comma-separated values](https://en.wikipedia.org/wiki/Comma-separated_values)) file. The CSV file is attached to an email, and sent to your default @@ -37,7 +39,7 @@ You can export issues from individual projects, but not groups. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Plan > Issues**. diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md index 164a3eaaca9..22565cfa051 100644 --- a/doc/user/project/issues/design_management.md +++ b/doc/user/project/issues/design_management.md @@ -102,10 +102,11 @@ To move around the image while zoomed in, drag the image. > - Ability to edit the description [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/388449) in GitLab 16.1. > - Minimum role to add a design to an issue [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147053) from Developer to Reporter in GitLab 16.11. +> - Minimum role to add a design to an issue [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. - The names of the uploaded files must be no longer than 255 characters. To add a design to an issue: @@ -137,12 +138,13 @@ To add a design to an issue: ## Add a new version of a design > - Minimum role to add a new version of a design [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147053) from Developer to Reporter in GitLab 16.11. +> - Minimum role to add a new version of a design [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. As discussion on a design continues, you might want to upload a new version of a design. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To do so, [add a design](#add-a-design-to-an-issue) with the same filename. @@ -158,6 +160,7 @@ When designs are skipped, a warning message is displayed. ## Archive a design > - Minimum role to archive a design [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147053) from Developer to Reporter in GitLab 16.11. +> - Minimum role to archive a design [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. You can archive individual designs or select a few of them to archive at once. @@ -170,7 +173,7 @@ URL. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. - You can archive only the latest version of a design. To archive a single design: @@ -233,10 +236,11 @@ so that everyone involved can participate in the discussion. ## Delete a comment from a design > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385100) in GitLab 15.9. +> Minimum role to delete comment from a design [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To delete a comment from a design: diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md index a92abe22208..86c1b07417a 100644 --- a/doc/user/project/issues/due_dates.md +++ b/doc/user/project/issues/due_dates.md @@ -10,8 +10,10 @@ DETAILS: **Tier:** Free, Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated +> - Minimum role to set due dates [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Due dates can be used in [issues](index.md) to keep track of deadlines and make sure features are -shipped on time. Users need at least the Reporter role +shipped on time. Users need at least the Planner role to be able to edit the due date. All users with permission to view the issue can view the due date. diff --git a/doc/user/project/issues/issue_weight.md b/doc/user/project/issues/issue_weight.md index a3f449dcb26..20aa911bde4 100644 --- a/doc/user/project/issues/issue_weight.md +++ b/doc/user/project/issues/issue_weight.md @@ -28,9 +28,11 @@ You can view the issue weight on: ## Set the issue weight +> - Minimum role to set issue weight [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. You can set the issue weight when you create or edit an issue. @@ -64,9 +66,11 @@ To set the issue weight when you [edit an issue from an issue board](../issue_bo ## Remove issue weight +> - Minimum role to remove issue weight [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To remove the issue weight, follow the same steps as when you [set the issue weight](#set-the-issue-weight), and select **remove weight**. diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index bf09eb7cb47..062f09d16d4 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -14,11 +14,13 @@ After you create an issue, you can start working with it. ## Edit an issue +> - Minimum role to edit an issue [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You can edit an issue's title and description. Prerequisites: -- You must have at least the Reporter role for the project, be the author of the issue, or be assigned to the issue. +- You must have at least the Planner role for the project, be the author of the issue, or be assigned to the issue. To edit an issue: @@ -64,11 +66,13 @@ the [large language model listed on the GitLab Duo page](../../gitlab_duo/index. ## Bulk edit issues from a project +> - Minimum role to bulk edit issues from a project [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You can edit multiple issues at a time when you're in a project. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To edit multiple issues at the same time: @@ -93,15 +97,13 @@ When bulk editing issues in a project, you can edit the following attributes: ### Bulk edit issues from a group -DETAILS: -**Tier:** Premium, Ultimate -**Offering:** GitLab.com, Self-managed, GitLab Dedicated +> - Minimum role to bulk edit issues from a group [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. You can edit multiple issues across multiple projects when you're in a group. Prerequisites: -- You must have at least the Reporter role for a group. +- You must have at least the Planner role for a group. To edit multiple issues at the same time: @@ -122,6 +124,8 @@ When bulk editing issues in a group, you can edit the following attributes: ## Move an issue +> - Minimum role to move an issue [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + When you move an issue, it's closed and copied to the target project. The original issue is not deleted. A [system note](../system_notes.md), which indicates where it came from and went to, is added to both issues. @@ -130,7 +134,7 @@ Be careful when moving an issue to a project with different access rules. Before Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To move an issue: @@ -159,6 +163,8 @@ DETAILS: **Tier:** Free, Premium, Ultimate **Offering:** Self-managed, GitLab Dedicated +> - Minimum role to bulk move issues [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + #### From the Issues page > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15991) in GitLab 15.6. @@ -168,7 +174,7 @@ You can't move tasks or test cases. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To move multiple issues at the same time: @@ -240,12 +246,13 @@ Any nested task list items are moved up a nested level. ### Reorder list items in the issue description > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15260) in GitLab 15.0. +> - Minimum role to reorder list items in the issue description [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. When you view an issue that has a list in the description, you can also reorder the list items. Prerequisites: -- You must have at least the Reporter role for the project, be the author of the issue, or be +- You must have at least the Planner role for the project, be the author of the issue, or be assigned to the issue. - The issue's description must have an [ordered, unordered](../../markdown.md#lists), or [task](../../markdown.md#task-lists) list. @@ -259,12 +266,14 @@ To reorder list items, when viewing an issue: ## Close an issue +> - Minimum role to close an issue [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + When you decide that an issue is resolved or no longer needed, you can close it. The issue is marked as closed but is not deleted. Prerequisites: -- You must have at least the Reporter role for the project, be the author of the issue, or be assigned to the issue. +- You must have at least the Planner role for the project, be the author of the issue, or be assigned to the issue. To close an issue, you can either: @@ -278,9 +287,11 @@ You can also use the `/close` [quick action](../quick_actions.md) in a comment o ### Reopen a closed issue +> - Minimum role to reopen a closed issue [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project, be the author of the issue, or be assigned to the issue. +- You must have at least the Planner role for the project, be the author of the issue, or be assigned to the issue. To reopen a closed issue, in the upper-right corner, select **Issue actions** (**{ellipsis_v}**) and then **Reopen issue**. A reopened issue is no different from any other open issue. @@ -414,9 +425,11 @@ of your installation. ## Change the issue type +> - Minimum role to change the issue type [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must be the issue author or have at least the Reporter role for the project, be the author of the issue, or be assigned to the issue. +- You must be the issue author or have at least the Planner role for the project, be the author of the issue, or be assigned to the issue. To change issue type: @@ -432,9 +445,11 @@ To change issue type: ## Delete an issue +> - Required role to delete an issue [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Owner to Owner or Planner in GitLab 17.7. + Prerequisites: -- You must have the Owner role for a project. +- You must have the Planner or Owner role for a project. To delete an issue: @@ -456,6 +471,8 @@ DETAILS: **Tier:** Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated +> - Minimum role to promote an issue to an epic [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You can promote an issue to an [epic](../../group/epics/index.md) in the immediate parent group. Promoting a confidential issue to an epic creates a @@ -478,9 +495,9 @@ The following issue metadata is copied to the epic: Prerequisites: - The project to which the issue belongs must be in a group. -- You must have at least the Reporter role the project's immediate parent group. +- You must have at least the Planner role the project's immediate parent group. - You must either: - - Have at least the Reporter role for the project. + - Have at least the Planner role for the project. - Be the author of the issue. - Be assigned to the issue. @@ -683,9 +700,11 @@ themselves or another project member assigns them. ### Change assignee on an issue +> - Minimum role to change assignee [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To change the assignee on an issue: @@ -722,9 +741,11 @@ Incorporate a review of issue health status into your daily stand-up, project st ### Change health status of an issue +> - Minimum role to change health status [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To edit health status of an issue: diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index 65f2985bd92..70d32585c73 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -105,9 +105,11 @@ the group's projects. ## Create a label +> - Minimum role to create a label [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project or group. +- You must have at least the Planner role for the project or group. ### Create a project label @@ -126,12 +128,14 @@ To create a project label: ### Create a project label from an issue or merge request +> - Minimum role to create a label [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You can also create a new project label from an issue or merge request. Labels you create this way belong to the same project as the issue or merge request. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To do so: @@ -164,12 +168,14 @@ DETAILS: **Tier:** Premium, Ultimate **Offering:** GitLab.com, Self-managed, GitLab Dedicated +> - Minimum role to create a group label [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You can also create a new group label from an epic. Labels you create this way belong to the same group as the epic. Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To do so: @@ -183,9 +189,11 @@ To do so: ## Edit a label +> - Minimum role to edit a label [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project or group. +- You must have at least the Planner role for the project or group. ### Edit a project label @@ -207,13 +215,15 @@ To edit a **group** label: ## Delete a label +> - Minimum role to delete a label [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + WARNING: If you delete a label, it is permanently deleted. All references to the label are removed from the system and you cannot undo the deletion. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. ### Delete a project label @@ -238,6 +248,8 @@ To delete a **group** label: ## Promote a project label to a group label +> - Minimum role to promote a label [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + You might want to make a project label available for other projects in the same group. Then, you can promote the label to a group label. @@ -250,8 +262,8 @@ Promoting a label is a permanent action and cannot be reversed. Prerequisites: -- You must have at least the Reporter role for the project. -- You must have at least the Reporter role for the project's parent group. +- You must have at least the Planner role for the project. +- You must have at least the Planner role for the project's parent group. To promote a project label to a group label: @@ -267,6 +279,8 @@ The new group label has the same ID as the previous project label. ## Promote a subgroup label to the parent group +> - Minimum role to promote a label [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + It's not possible to directly promote a group label to the parent group. To achieve this, use the following workaround. @@ -274,7 +288,7 @@ Prerequisites: - There must be a group that contains subgroups ("parent group"). - There must be a subgroup in the parent group, that has a label you want to promote. -- You must have at least the Reporter role for both groups. +- You must have at least the Planner role for both groups. To "promote" the label to the parent group: @@ -296,12 +310,14 @@ to the same issues, MRs, and epics. ## Generate default project labels +> - Minimum role to generate default labels [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + If a project or its parent group has no labels, you can generate a default set of project labels from the label list page. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. - The project must have no labels present. To add the default labels to the project: @@ -435,6 +451,8 @@ To subscribe to a label: ## Set label priority +> - Minimum role to set label priority [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. + Labels can have relative priorities, which are used when you sort issue and merge request lists by [label priority](issues/sorting_issue_lists.md#sorting-by-label-priority) and [priority](issues/sorting_issue_lists.md#sorting-by-priority). @@ -447,7 +465,7 @@ Priority sorting is based on the highest priority label only. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To prioritize a label: @@ -473,6 +491,7 @@ DETAILS: **Status:** Beta > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/408676) in GitLab 16.3 [with a flag](../../administration/feature_flags.md) named `enforce_locked_labels_on_merge`. This feature is [beta](../../policy/experiment-beta-support.md). Disabled by default. +> - Minimum role to lock labels [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. FLAG: The availability of this feature is controlled by a feature flag. @@ -486,7 +505,7 @@ When you add locked labels to issues or epics, they behave like regular labels. Prerequisites: -- You must have at least the Reporter role for the project or group. +- You must have at least the Planner role for the project or group. WARNING: After you set a label as locked, nobody can undo it or delete the label. diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md index 1fb6e7aaf00..0a05a6c62bc 100644 --- a/doc/user/project/members/index.md +++ b/doc/user/project/members/index.md @@ -184,7 +184,7 @@ If a user is: Prerequisites: - To remove direct members that have the: - - Maintainer, Developer, Reporter, or Guest role, you must have the Maintainer role. + - Maintainer, Developer, Reporter, Planner, or Guest role, you must have the Maintainer role. - Owner role, you must have the Owner role. - Optional. Unassign the member from all issues and merge requests that are assigned to them. diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index 12dcba17323..e46e0a1145a 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -123,12 +123,13 @@ The sidebar on the milestone view shows the following: ## Create a milestone > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. You can create a milestone either in a project or a group. Prerequisites: -- You must have at least the Reporter role for the project or group the milestone belongs to. +- You must have at least the Planner role for the project or group the milestone belongs to. To create a milestone: @@ -144,10 +145,11 @@ To create a milestone: ## Edit a milestone > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project or group the milestone belongs to. +- You must have at least the Planner role for the project or group the milestone belongs to. To edit a milestone: @@ -161,10 +163,11 @@ To edit a milestone: ## Close a milestone > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project or group the milestone belongs to. +- You must have at least the Planner role for the project or group the milestone belongs to. To close a milestone: @@ -177,10 +180,11 @@ To close a milestone: ## Delete a milestone > - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/343889) the minimum user role from Developer to Reporter in GitLab 15.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project or group the milestone belongs to. +- You must have at least the Planner role for the project or group the milestone belongs to. To delete a milestone: @@ -193,6 +197,8 @@ To delete a milestone: ## Promote a project milestone to a group milestone +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + If you are expanding the number of projects in a group, you might want to share the same milestones among this group's projects. You can promote project milestones to the parent group to @@ -208,7 +214,7 @@ This action cannot be reversed and the changes are permanent. Prerequisites: -- You must have at least the Reporter role for the group. +- You must have at least the Planner role for the group. To promote a project milestone: diff --git a/doc/user/project/requirements/index.md b/doc/user/project/requirements/index.md index c5edbe2173e..967d07050de 100644 --- a/doc/user/project/requirements/index.md +++ b/doc/user/project/requirements/index.md @@ -37,12 +37,14 @@ For a more in-depth walkthrough see [GitLab Requirements Traceability Walkthroug ## Create a requirement +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + A paginated list of requirements is available in each project, and there you can create a new requirement. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. To create a requirement: @@ -66,13 +68,14 @@ next to the requirement title. ## Edit a requirement -> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can edit requirements even if they don’t have the Reporter role. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can edit requirements even if they don't have the Reporter role. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. You can edit a requirement from the requirements list page. Prerequisites: -- You must have at least the Reporter role or be the author or assignee of the requirement. +- You must have at least the Planner role or be the author or assignee of the requirement. To edit a requirement: @@ -83,14 +86,15 @@ To edit a requirement: ## Archive a requirement -> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can archive requirements even if they don’t have the Reporter role. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can archive requirements even if they don't have the Reporter role. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. You can archive an open requirement while you're in the **Open** tab. Prerequisites: -- You must have at least the Reporter role or be the author or assignee of the requirement. +- You must have at least the Planner role or be the author or assignee of the requirement. To archive a requirement, select **Archive** (**{archive}**). @@ -98,13 +102,14 @@ As soon as a requirement is archived, it no longer appears in the **Open** tab. ## Reopen a requirement -> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can re-open requirements even if they don’t have the Reporter role. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/424961) in GitLab 16.11: Authors and assignees can re-open requirements even if they don't have the Reporter role. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. You can view the list of archived requirements in the **Archived** tab. Prerequisites: -- You must have at least the Reporter role or be the author or assignee of the requirement. +- You must have at least the Planner role or be the author or assignee of the requirement. ![archived requirements list](img/requirements_archived_list_view_v13_1.png) @@ -223,7 +228,9 @@ in a project, you must replace `requirements` in above configs with `requirement ## Import requirements from a CSV file -You must have at least the Reporter role. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + +You must have at least the Planner role. You can import requirements to a project by uploading a [CSV file](https://en.wikipedia.org/wiki/Comma-separated_values) with the columns `title` and `description`. @@ -284,6 +291,8 @@ For GitLab.com, it is set to 10 MB. ## Export requirements to a CSV file +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + You can export GitLab requirements to a [CSV file](https://en.wikipedia.org/wiki/Comma-separated_values) sent to your default notification email as an attachment. @@ -294,7 +303,7 @@ audit and regulatory compliance tasks. Prerequisites: -- You must have at least the Reporter role. +- You must have at least the Planner role. To export requirements: diff --git a/doc/user/project/time_tracking.md b/doc/user/project/time_tracking.md index 1d258868a2b..3efac991908 100644 --- a/doc/user/project/time_tracking.md +++ b/doc/user/project/time_tracking.md @@ -12,6 +12,7 @@ DETAILS: > - Time tracking for tasks [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/438577) in GitLab 17.0. > - Time tracking for epics [introduced](https://gitlab.com/groups/gitlab-org/-/epics/12396) in GitLab 17.5. Your administrator must have [enabled the new look for epics](../group/epics/epic_work_items.md). +> - Minimum role to add, edit, and remove estimate [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) from Reporter to Planner in GitLab 17.7. You can estimate and track the time you spend on an item, such as: @@ -50,8 +51,8 @@ You can see the estimated time remaining when you hover over the time tracking i Prerequisites: -- In issues, you must have at least the Reporter role for the project. -- In tasks, you must have at least the Reporter role for the project. +- In issues, you must have at least the Planner role for the project. +- In tasks, you must have at least the Planner role for the project. - In merge requests, you must have at least the Developer role for the project. To enter an estimate, use the `/estimate` [quick action](quick_actions.md), followed by the time. @@ -67,8 +68,8 @@ Every time you enter a new time estimate, it overwrites the previous value. Prerequisites: -- In issues, you must have at least the Reporter role for the project. -- In tasks, you must have at least the Reporter role for the project. +- In issues, you must have at least the Planner role for the project. +- In tasks, you must have at least the Planner role for the project. - In merge requests, you must have at least the Developer role for the project. To remove an estimate entirely, use the `/remove_estimate` [quick action](quick_actions.md). @@ -86,7 +87,7 @@ The total amount of time spent on an issue, task, or merge request cannot exceed Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. #### Using the user interface @@ -135,7 +136,7 @@ If you type a future date, no time is logged. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To subtract time, enter a negative value. For example, `/spend -3d` removes three days from the total time spent. You can't go below 0 minutes of time spent, @@ -160,7 +161,7 @@ To delete a timelog, either: Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To delete all the time spent at once, use the `/remove_time_spent` [quick action](quick_actions.md). diff --git a/doc/user/report_abuse.md b/doc/user/report_abuse.md index df0de351571..9263653b8f1 100644 --- a/doc/user/report_abuse.md +++ b/doc/user/report_abuse.md @@ -1,5 +1,5 @@ --- -stage: Govern +stage: Software Supply Chain Security group: Anti-Abuse info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/user/tasks.md b/doc/user/tasks.md index 7b470eb0912..e9909083c7c 100644 --- a/doc/user/tasks.md +++ b/doc/user/tasks.md @@ -50,10 +50,11 @@ the task opens in a full-page view. ## Create a task > - Ability to select which project to create the task in [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/436255) in GitLab 17.1. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project, or the project must be public. +- You must have at least the Planner role for the project, or the project must be public. To create a task: @@ -68,10 +69,11 @@ To create a task: ### From a task list item > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Plan > Issues**, then select your issue to view it. @@ -101,9 +103,11 @@ To add a task: ## Edit a task +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. + Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To edit a task: @@ -120,6 +124,7 @@ To edit a task: > - Rich text editing in the dialog view [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/363007) in GitLab 15.6 [with a flag](../administration/feature_flags.md) named `work_items_mvc`. Disabled by default. > - Rich text editing in the full page view [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104533) in GitLab 15.7. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. FLAG: On self-managed GitLab, by default the rich text feature is not available. To make it available per group, ask an @@ -131,7 +136,7 @@ Use a rich text editor to edit a task's description. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To edit the description of a task: @@ -146,10 +151,11 @@ To edit the description of a task: ## Promote a task to an issue > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412534) in GitLab 16.1. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To promote a task to an issue: @@ -192,11 +198,13 @@ To remove a task from an issue: ## Delete a task +> - [Allowed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) Planner role to delete a task in GitLab 17.7. + Prerequisites: - You must either: - Be the author of the task and have at least the Guest role for the project. - - Have the Owner role for the project. + - Have the Planner or Owner role for the project. To delete a task: @@ -209,10 +217,11 @@ To delete a task: ## Reorder tasks > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385887) in GitLab 16.0. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. By default, tasks are ordered by creation date. To reorder them, drag them around. @@ -220,6 +229,7 @@ To reorder them, drag them around. ## Assign users to a task > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334810) in GitLab 15.4. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. To show who is responsible for a task, you can assign users to it. @@ -229,7 +239,7 @@ See also [multiple assignees for issues](project/issues/multiple_assignees_for_i Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To change the assignee on a task: @@ -244,10 +254,11 @@ To change the assignee on a task: ## Assign labels to a task > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339756) in GitLab 15.5. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To add [labels](project/labels.md) to a task: @@ -262,12 +273,13 @@ To add [labels](project/labels.md) to a task: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365399) in GitLab 15.4 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/365399) in GitLab 15.5. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. You can set a [start and due date](project/issues/due_dates.md) on a task. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. You can set start and due dates on a task to show when work should begin and end. @@ -295,6 +307,7 @@ To set a start date: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) in GitLab 15.5 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) to feature flag named `work_items_mvc` in GitLab 15.7. Disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) in GitLab 15.7. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169256) the minimum user role from Reporter to Planner in GitLab 17.7. You can add a task to a [milestone](project/milestones/index.md). You can see the milestone title when you view a task. @@ -303,7 +316,7 @@ the new task inherits the milestone. Prerequisites: -- You must have at least the Reporter role for the project. +- You must have at least the Planner role for the project. To add a task to a milestone: diff --git a/lib/api/submodules.rb b/lib/api/submodules.rb index 454454da67c..dbeff31fd02 100644 --- a/lib/api/submodules.rb +++ b/lib/api/submodules.rb @@ -2,7 +2,7 @@ module API class Submodules < ::API::Base - SUBMODULE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(submodule: API::NO_SLASH_URL_PART_REGEX) # rubocop:disable Layout/LineLength -- no way to shorten this and adhere to naming convention + SUBMODULE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(submodule: API::NO_SLASH_URL_PART_REGEX) before { authenticate! } diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 7ac49209a29..422ee036411 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -12,6 +12,7 @@ module Gitlab NO_ACCESS = 0 MINIMAL_ACCESS = 5 GUEST = 10 + PLANNER = 15 REPORTER = 20 DEVELOPER = 30 MAINTAINER = 40 @@ -45,6 +46,7 @@ module Gitlab def options { "Guest" => GUEST, + "Planner" => PLANNER, "Reporter" => REPORTER, "Developer" => DEVELOPER, "Maintainer" => MAINTAINER @@ -67,6 +69,7 @@ module Gitlab { NO_ACCESS => s_('MemberRole|The None role is assigned to the invited group users of a shared project when project sharing is disabled in group setting.'), GUEST => s_('MemberRole|The Guest role is for users who need visibility into a project or group but should not have the ability to make changes, such as external stakeholders.'), + PLANNER => s_('The Planner role is suitable for team members who need to manage projects and track work items but do not need to contribute code.'), REPORTER => s_('MemberRole|The Reporter role is suitable for team members who need to stay informed about a project or group but do not actively contribute code.'), DEVELOPER => s_('MemberRole|The Developer role gives users access to contribute code while restricting sensitive administrative actions.'), MAINTAINER => s_('MemberRole|The Maintainer role is primarily used for managing code reviews, approvals, and administrative settings for projects. This role can also manage project memberships.'), @@ -77,6 +80,7 @@ module Gitlab def sym_options { guest: GUEST, + planner: PLANNER, reporter: REPORTER, developer: DEVELOPER, maintainer: MAINTAINER diff --git a/lib/gitlab/background_migration/backfill_security_policies.rb b/lib/gitlab/background_migration/backfill_security_policies.rb index eaef26415b2..7d2a4b5f549 100644 --- a/lib/gitlab/background_migration/backfill_security_policies.rb +++ b/lib/gitlab/background_migration/backfill_security_policies.rb @@ -10,4 +10,4 @@ module Gitlab end end -Gitlab::BackgroundMigration::BackfillSecurityPolicies.prepend_mod_with('Gitlab::BackgroundMigration::BackfillSecurityPolicies') # rubocop:disable Layout/LineLength -- ignore +Gitlab::BackgroundMigration::BackfillSecurityPolicies.prepend_mod diff --git a/lib/gitlab/background_migration/project_bot_user_details_bot_namespace_migration.rb b/lib/gitlab/background_migration/project_bot_user_details_bot_namespace_migration.rb index 3436df9ba0d..a2303afda9e 100644 --- a/lib/gitlab/background_migration/project_bot_user_details_bot_namespace_migration.rb +++ b/lib/gitlab/background_migration/project_bot_user_details_bot_namespace_migration.rb @@ -10,7 +10,6 @@ module Gitlab feature_category :system_access - # rubocop:disable Metrics/BlockLength -- Method is long due to formatted SQL def perform each_sub_batch do |sub_batch| connection.execute( @@ -39,7 +38,6 @@ module Gitlab ) end end - # rubocop:enable Metrics/BlockLength end end end diff --git a/lib/gitlab/ci/build/context/base.rb b/lib/gitlab/ci/build/context/base.rb index 86f4aad933e..bd19536cff7 100644 --- a/lib/gitlab/ci/build/context/base.rb +++ b/lib/gitlab/ci/build/context/base.rb @@ -19,13 +19,27 @@ module Gitlab def variables_hash strong_memoize(:variables_hash) do - variables.to_hash + if ci_optimize_memory_for_variables_enabled? + variables.to_hash + else + variables.to_hash_legacy + end end end def variables_hash_expanded strong_memoize(:variables_hash_expanded) do - variables.sort_and_expand_all.to_hash + if ci_optimize_memory_for_variables_enabled? + variables_sorted_and_expanded.to_hash + else + variables_sorted_and_expanded.to_hash_legacy + end + end + end + + def variables_sorted_and_expanded + strong_memoize(:variables_sorted_and_expanded) do + variables.sort_and_expand_all end end @@ -52,6 +66,11 @@ module Gitlab protected: pipeline.protected_ref? } end + + def ci_optimize_memory_for_variables_enabled? + ::Feature.enabled?(:ci_optimize_memory_for_variables, project) + end + strong_memoize_attr :ci_optimize_memory_for_variables_enabled? end end end diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb index 61f93ee2353..c4941e12787 100644 --- a/lib/gitlab/ci/config/external/context.rb +++ b/lib/gitlab/ci/config/external/context.rb @@ -57,13 +57,27 @@ module Gitlab def variables_hash strong_memoize(:variables_hash) do - variables.to_hash + if ci_optimize_memory_for_variables_enabled? + variables.to_hash + else + variables.to_hash_legacy + end end end def variables_hash_expanded strong_memoize(:variables_hash_expanded) do - variables.sort_and_expand_all.to_hash + if ci_optimize_memory_for_variables_enabled? + variables_sorted_and_expanded.to_hash + else + variables_sorted_and_expanded.to_hash_legacy + end + end + end + + def variables_sorted_and_expanded + strong_memoize(:variables_sorted_and_expanded) do + variables.sort_and_expand_all end end @@ -141,6 +155,11 @@ module Gitlab current_monotonic_time > execution_deadline end + + def ci_optimize_memory_for_variables_enabled? + ::Feature.enabled?(:ci_optimize_memory_for_variables, project) + end + strong_memoize_attr :ci_optimize_memory_for_variables_enabled? end end end diff --git a/lib/gitlab/ci/pipeline/chain/populate_metadata.rb b/lib/gitlab/ci/pipeline/chain/populate_metadata.rb index 4cf7afe5024..9e73d94773d 100644 --- a/lib/gitlab/ci/pipeline/chain/populate_metadata.rb +++ b/lib/gitlab/ci/pipeline/chain/populate_metadata.rb @@ -27,7 +27,7 @@ module Gitlab return if @command.yaml_processor_result.workflow_name.blank? name = @command.yaml_processor_result.workflow_name - name = ExpandVariables.expand(name, -> { global_context.variables.sort_and_expand_all }) + name = ExpandVariables.expand(name, -> { global_context.variables_sorted_and_expanded }) return if name.blank? diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb index 26418626ca9..77039cf51cd 100644 --- a/lib/gitlab/ci/pipeline/seed/build.rb +++ b/lib/gitlab/ci/pipeline/seed/build.rb @@ -142,7 +142,7 @@ module Gitlab end def variable_expansion_errors - expanded_collection = evaluate_context.variables.sort_and_expand_all + expanded_collection = evaluate_context.variables_sorted_and_expanded errors = expanded_collection.errors ["#{name}: #{errors}"] if errors end diff --git a/lib/gitlab/ci/project_config.rb b/lib/gitlab/ci/project_config.rb index bb185cf0a7d..c3645bf0181 100644 --- a/lib/gitlab/ci/project_config.rb +++ b/lib/gitlab/ci/project_config.rb @@ -11,17 +11,14 @@ module Gitlab # it would evaluate the project's YAML file instead. # - ProjectSetting takes care of CI config coming defined in a project. # This can be the project itself, remote or external. - # - EE uses PipelineExecutionPolicyForced and it must come before AutoDevops because - # it handles the empty CI config case. - # We want to run Pipeline Execution Policies instead of AutoDevops (if they are present). # - AutoDevops is used as default option if nothing else is found and if AutoDevops is enabled. # - EE uses SecurityPolicyDefault and it should come last. It is only necessary if no other source is available. + # Based on the policy configuration different source can be used. SOURCES = [ ProjectConfig::Compliance, ProjectConfig::Parameter, ProjectConfig::Bridge, ProjectConfig::ProjectSetting, - ProjectConfig::PipelineExecutionPolicyForced, ProjectConfig::AutoDevops, ProjectConfig::SecurityPolicyDefault ].freeze diff --git a/lib/gitlab/ci/project_config/pipeline_execution_policy_forced.rb b/lib/gitlab/ci/project_config/pipeline_execution_policy_forced.rb deleted file mode 100644 index 2582d816d0a..00000000000 --- a/lib/gitlab/ci/project_config/pipeline_execution_policy_forced.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - class ProjectConfig - class PipelineExecutionPolicyForced < Gitlab::Ci::ProjectConfig::Source - # rubocop:disable Gitlab/NoCodeCoverageComment -- overridden and tested in EE - # :nocov: - def content - nil - end - # :nocov: - # rubocop:enable Gitlab/NoCodeCoverageComment - - def source - :pipeline_execution_policy_forced - end - end - end - end -end - -Gitlab::Ci::ProjectConfig::PipelineExecutionPolicyForced.prepend_mod diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb index 8d4c1cc7675..7f7e1751cd7 100644 --- a/lib/gitlab/ci/variables/collection.rb +++ b/lib/gitlab/ci/variables/collection.rb @@ -73,16 +73,28 @@ module Gitlab @variables.size end + # This method should only be called via runner_variables->to_runner_variables + # because this is an expensive operation by initializing new objects in `to_runner_variable`. def to_runner_variables self.map(&:to_runner_variable) end - def to_hash + def to_hash_variables + self.map(&:to_hash_variable) + end + + def to_hash_legacy self.to_runner_variables .to_h { |env| [env.fetch(:key), env.fetch(:value)] } .with_indifferent_access end + def to_hash + self.each_with_object(ActiveSupport::HashWithIndifferentAccess.new) do |variable, result| + result[variable.key] = variable.value + end + end + def reject(&block) Collection.new(@variables.reject(&block)) end diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb index 2f5f7de4df1..e1af8eb2a97 100644 --- a/lib/gitlab/ci/variables/collection/item.rb +++ b/lib/gitlab/ci/variables/collection/item.rb @@ -60,12 +60,19 @@ module Gitlab # If `file: true` or `raw: true` has been provided we expose it, otherwise we # don't expose `file` and `raw` attributes at all (stems from what the runner expects). # + # This method should only be called via runner_variables->to_runner_variables->to_runner_variable + # because this is an expensive operation by initializing a new object. + ## def to_runner_variable @variable.reject do |hash_key, hash_value| (hash_key == :file || hash_key == :raw) && hash_value == false end end + def to_hash_variable + @variable + end + def self.fabricate(resource) case resource when Hash @@ -85,10 +92,11 @@ module Gitlab VARIABLE_REF_CHARS.any? { |symbol| value.include?(symbol) } end + # This is for debugging purposes only. def to_s - return to_runner_variable.to_s unless depends_on + return to_hash_variable.to_s unless depends_on - "#{to_runner_variable}, depends_on=#{depends_on}" + "#{to_hash_variable}, depends_on=#{depends_on}" end end end diff --git a/lib/gitlab/database/postgres_table_size.rb b/lib/gitlab/database/postgres_table_size.rb new file mode 100644 index 00000000000..359c93717b4 --- /dev/null +++ b/lib/gitlab/database/postgres_table_size.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Gitlab + module Database + class PostgresTableSize < SharedModel + SMALL = 10.gigabytes + MEDIUM = 50.gigabytes + LARGE = 100.gigabytes + + self.primary_key = 'identifier' + self.table_name = 'postgres_table_sizes' + + scope :small, -> { where(size_in_bytes: ...SMALL) } + scope :medium, -> { where(size_in_bytes: SMALL...MEDIUM) } + scope :large, -> { where(size_in_bytes: MEDIUM...LARGE) } + scope :over_limit, -> { where(size_in_bytes: LARGE...) } + end + end +end diff --git a/lib/gitlab/popen/runner.rb b/lib/gitlab/popen/runner.rb index cd9ad270cd8..1cef3b84ef3 100644 --- a/lib/gitlab/popen/runner.rb +++ b/lib/gitlab/popen/runner.rb @@ -12,7 +12,7 @@ module Gitlab def run(commands, &block) commands.each do |cmd| # yield doesn't support blocks, so we need to use a block variable - block.call(cmd) do # rubocop:disable Performance/RedundantBlockCall + block.call(cmd) do cmd_result = Gitlab::Popen.popen_with_detail(cmd) results << cmd_result diff --git a/lib/sidebars/your_work/menus/todos_menu.rb b/lib/sidebars/your_work/menus/todos_menu.rb index 5d6658c42a3..d37ffadb579 100644 --- a/lib/sidebars/your_work/menus/todos_menu.rb +++ b/lib/sidebars/your_work/menus/todos_menu.rb @@ -8,11 +8,7 @@ module Sidebars override :link def link - if Feature.enabled?(:todos_vue_application, context.current_user) - vue_dashboard_todos_path - else - dashboard_todos_path - end + dashboard_todos_path end override :title diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 461c743bc15..1adfbbd9a04 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3501,6 +3501,12 @@ msgstr "" msgid "Added %{label_references} %{label_text}." msgstr "" +msgid "Added %{target} as a linked item blocked by this %{work_item_type}." +msgstr "" + +msgid "Added %{target} as a linked item blocking this %{work_item_type}." +msgstr "" + msgid "Added %{timeAgo} by" msgstr "" @@ -32614,6 +32620,12 @@ msgstr "" msgid "Link does not exist" msgstr "" +msgid "Link items blocked by this item" +msgstr "" + +msgid "Link items blocking this item" +msgstr "" + msgid "Link text" msgstr "" @@ -33073,9 +33085,6 @@ msgstr "" msgid "Mark this issue as a duplicate of another issue" msgstr "" -msgid "Mark this issue as blocked by other issues" -msgstr "" - msgid "Mark this issue as related to another issue" msgstr "" @@ -33145,9 +33154,6 @@ msgstr "" msgid "Marked" msgstr "" -msgid "Marked %{target} as blocked by this issue." -msgstr "" - msgid "Marked For Deletion At - %{deletion_time}" msgstr "" @@ -33160,9 +33166,6 @@ msgstr "" msgid "Marked this %{noun} as ready." msgstr "" -msgid "Marked this issue as blocked by %{target}." -msgstr "" - msgid "Marked this issue as related to %{issue_ref}." msgstr "" @@ -33787,6 +33790,9 @@ msgstr "" msgid "MemberRole|The Owner role is normally assigned to the individual or team responsible for managing and maintaining the group or creating the project. This role has the highest level of administrative control, and can manage all aspects of the group or project, including managing other Owners." msgstr "" +msgid "MemberRole|The Planner role is suitable for team members who need to manage projects and track work items but do not need to contribute code, such as project managers and scrum masters." +msgstr "" + msgid "MemberRole|The Reporter role is suitable for team members who need to stay informed about a project or group but do not actively contribute code." msgstr "" @@ -41326,6 +41332,9 @@ msgstr "" msgid "Plan:" msgstr "" +msgid "Planner" +msgstr "" + msgid "PlantUML" msgstr "" @@ -51973,10 +51982,10 @@ msgstr "" msgid "Set the per-user rate limits for the requests to Organizations API." msgstr "" -msgid "Set this issue as blocked by %{target}." +msgid "Set this %{work_item_type} as blocked by %{target}." msgstr "" -msgid "Set this issue as blocking %{target}." +msgid "Set this %{work_item_type} as blocking %{target}." msgstr "" msgid "Set this number to 0 to disable the limit." @@ -53396,9 +53405,6 @@ msgstr "" msgid "Specified URL cannot be used: \"%{reason}\"" msgstr "" -msgid "Specifies that this issue blocks other issues" -msgstr "" - msgid "Specify IP ranges that are always allowed for inbound traffic, for use with group-level IP restrictions. Runner and Pages daemon internal IPs should be listed here so that they can access project artifacts." msgstr "" @@ -55301,6 +55307,9 @@ msgstr "" msgid "The Mattermost token." msgstr "" +msgid "The Planner role is suitable for team members who need to manage projects and track work items but do not need to contribute code." +msgstr "" + msgid "The Slack notifications integration is deprecated and will be removed in a future release. To continue to receive notifications from Slack, use the GitLab for Slack app instead. %{learn_more_link_start}Learn more%{link_end}." msgstr "" @@ -62429,27 +62438,9 @@ msgstr "" msgid "WikiClone|Clone Wiki repository" msgstr "" -msgid "WikiClone|Clone repository" -msgstr "" - msgid "WikiClone|Git Access" msgstr "" -msgid "WikiClone|Go to directory" -msgstr "" - -msgid "WikiClone|Install Gollum" -msgstr "" - -msgid "WikiClone|Start Gollum and edit locally" -msgstr "" - -msgid "WikiClone|Step 1: Clone repository" -msgstr "" - -msgid "WikiClone|Step 2: Install and start Gollum" -msgstr "" - msgid "WikiEdit|There is already a page with the same title in that path." msgstr "" diff --git a/qa/qa/resource/user_runners.rb b/qa/qa/resource/user_runners.rb index da1c808bb68..fb504bc725f 100644 --- a/qa/qa/resource/user_runners.rb +++ b/qa/qa/resource/user_runners.rb @@ -27,7 +27,7 @@ module QA @run_untagged = nil @name = "qa-runner-#{SecureRandom.hex(4)}" @executor = :shell - @executor_image = "#{QA::Runtime::Env.container_registry_host}/#{QA::Runtime::Env.runner_container_namespace}/#{QA::Runtime::Env.gitlab_qa_build_image}" # rubocop:disable Layout/LineLength -- image path + @executor_image = "#{QA::Runtime::Env.container_registry_host}/#{QA::Runtime::Env.runner_container_namespace}/#{QA::Runtime::Env.gitlab_qa_build_image}" end def fabricate! diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb index c9885791a63..7283a65fe85 100644 --- a/qa/qa/service/docker_run/gitlab_runner.rb +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -15,10 +15,10 @@ module QA MSG def initialize(name) - @image = "#{QA::Runtime::Env.container_registry_host}/#{QA::Runtime::Env.runner_container_namespace}/#{QA::Runtime::Env.runner_container_image}" # rubocop:disable Layout/LineLength + @image = "#{QA::Runtime::Env.container_registry_host}/#{QA::Runtime::Env.runner_container_namespace}/#{QA::Runtime::Env.runner_container_image}" @name = name || "qa-runner-#{SecureRandom.hex(4)}" @executor = :shell - @executor_image = "#{QA::Runtime::Env.container_registry_host}/#{QA::Runtime::Env.runner_container_namespace}/#{QA::Runtime::Env.gitlab_qa_build_image}" # rubocop:disable Layout/LineLength + @executor_image = "#{QA::Runtime::Env.container_registry_host}/#{QA::Runtime::Env.runner_container_namespace}/#{QA::Runtime::Env.gitlab_qa_build_image}" super() end diff --git a/qa/qa/vendor/one_password/cli.rb b/qa/qa/vendor/one_password/cli.rb index f443ba05492..191c50618de 100644 --- a/qa/qa/vendor/one_password/cli.rb +++ b/qa/qa/vendor/one_password/cli.rb @@ -51,7 +51,7 @@ module QA # But note that if we add more tests that use this class, we might need to add a mechanism to invalidate # the cache after 30 minutes or if the session_token is rejected by op CLI. def session_token - @session_token ||= `echo '#{@password}' | op account add --address #{@address} --email #{@email} --secret-key #{@secret} --signin --raw` # rubocop:disable Layout/LineLength + @session_token ||= `echo '#{@password}' | op account add --address #{@address} --email #{@email} --secret-key #{@secret} --signin --raw` end end end diff --git a/spec/components/previews/pajamas/banner_component_preview.rb b/spec/components/previews/pajamas/banner_component_preview.rb index 8bb7b82527d..e3942895cd9 100644 --- a/spec/components/previews/pajamas/banner_component_preview.rb +++ b/spec/components/previews/pajamas/banner_component_preview.rb @@ -42,7 +42,7 @@ module Pajamas def with_illustration_slot render(Pajamas::BannerComponent.new) do |c| c.with_illustration do - ''.html_safe # rubocop:disable Layout/LineLength + ''.html_safe # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective end content_tag :p, "This banner uses the illustration slot." end diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 9f5bd1a1a56..f3b922564ef 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -12,6 +12,30 @@ RSpec.describe Dashboard::TodosController, feature_category: :notifications do end describe 'GET #index' do + context 'when the `todos_vue_application` feature flag is disabled' do + before do + stub_feature_flags(todos_vue_application: false) + end + + it 'renders the legacy view' do + get :index + + expect(response).to render_template(:index) + end + end + + context 'when the `todos_vue_application` feature flag is enabled' do + before do + stub_feature_flags(todos_vue_application: true) + end + + it 'renders the legacy view' do + get :index + + expect(response).to render_template(:vue) + end + end + context 'project authorization' do it 'renders 404 when user does not have read access on given project' do unauthorized_project = create(:project, :private) @@ -43,6 +67,10 @@ RSpec.describe Dashboard::TodosController, feature_category: :notifications do end context "with render_views" do + before do + stub_feature_flags(todos_vue_application: false) + end + render_views it 'avoids N+1 queries', :request_store do @@ -89,6 +117,7 @@ RSpec.describe Dashboard::TodosController, feature_category: :notifications do end before do + stub_feature_flags(todos_vue_application: false) allow(Kaminari.config).to receive(:default_per_page).and_return(2) end @@ -158,28 +187,10 @@ RSpec.describe Dashboard::TodosController, feature_category: :notifications do end describe 'GET #vue' do - context 'with todos_vue_application on' do - before do - stub_feature_flags(todos_vue_application: true) - end + it 'redirects to #index' do + get :vue - it 'renders 200' do - get :vue - - expect(response).to have_gitlab_http_status(:ok) - end - end - - context 'with todos_vue_application off' do - before do - stub_feature_flags(todos_vue_application: false) - end - - it 'redirects to #index' do - get :vue - - expect(response).to redirect_to dashboard_todos_path - end + expect(response).to redirect_to dashboard_todos_path end end diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb index e5ae52974b9..6fa1d93265d 100644 --- a/spec/controllers/root_controller_spec.rb +++ b/spec/controllers/root_controller_spec.rb @@ -109,21 +109,10 @@ RSpec.describe RootController do user.dashboard = 'todos' end - context 'with the :todos_vue_application feature flag disabled (default)' do - it 'redirects to the classic todo list' do - stub_feature_flags(todos_vue_application: false) - get :index + it 'redirects to their todo list' do + get :index - expect(response).to redirect_to dashboard_todos_path - end - end - - context 'with the :todos_vue_application feature flag enabled (internal beta)' do - it 'redirects to the new todo list' do - get :index - - expect(response).to redirect_to vue_dashboard_todos_path - end + expect(response).to redirect_to dashboard_todos_path end end diff --git a/spec/factories/gitlab/database/postgres_table_size.rb b/spec/factories/gitlab/database/postgres_table_size.rb new file mode 100644 index 00000000000..73c855b9323 --- /dev/null +++ b/spec/factories/gitlab/database/postgres_table_size.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :postgres_table_size, class: 'Gitlab::Database::PostgresTableSize' do + sequence(:identifier) { |n| "table_#{n}" } + schema_name { 'public' } + table_name { 'foo' } + total_size { '2 GB' } + table_size { '1 GB' } + index_size { '1 GB' } + size_in_bytes { 2.gigabytes } + end +end diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 8b8a058201b..979e7ea9b32 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -11,6 +11,7 @@ FactoryBot.define do transient do # rubocop:disable Lint/EmptyBlock -- block is required by factorybot guests {} + planners {} reporters {} developers {} maintainers {} @@ -29,6 +30,7 @@ FactoryBot.define do create(:namespace_settings, namespace: group) unless group.namespace_settings group.add_members(Array.wrap(evaluator.guests), :guest) + group.add_members(Array.wrap(evaluator.planners), :planner) group.add_members(Array.wrap(evaluator.reporters), :reporter) group.add_members(Array.wrap(evaluator.developers), :developer) group.add_members(Array.wrap(evaluator.maintainers), :maintainer) diff --git a/spec/factories/member.rb b/spec/factories/member.rb index a27a48770d4..cc8add9fbe7 100644 --- a/spec/factories/member.rb +++ b/spec/factories/member.rb @@ -8,6 +8,7 @@ FactoryBot.define do user trait(:guest) { access_level { Gitlab::Access::GUEST } } + trait(:planner) { access_level { Gitlab::Access::PLANNER } } trait(:reporter) { access_level { Gitlab::Access::REPORTER } } trait(:developer) { access_level { Gitlab::Access::DEVELOPER } } trait(:maintainer) { access_level { Gitlab::Access::MAINTAINER } } diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index ae3a83ca8dc..c5a8656519b 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -76,6 +76,7 @@ FactoryBot.define do # rubocop:disable Lint/EmptyBlock -- block is required by factorybot guests {} + planners {} reporters {} developers {} maintainers {} @@ -165,6 +166,7 @@ FactoryBot.define do project.create_ci_project_mirror!(namespace_id: project.namespace_id) unless project.ci_project_mirror project.add_members(Array.wrap(evaluator.guests), :guest) + project.add_members(Array.wrap(evaluator.planners), :planner) project.add_members(Array.wrap(evaluator.reporters), :reporter) project.add_members(Array.wrap(evaluator.developers), :developer) project.add_members(Array.wrap(evaluator.maintainers), :maintainer) diff --git a/spec/factories/user_highest_roles.rb b/spec/factories/user_highest_roles.rb index ee5794b55fb..04e3e344b4c 100644 --- a/spec/factories/user_highest_roles.rb +++ b/spec/factories/user_highest_roles.rb @@ -6,6 +6,7 @@ FactoryBot.define do user trait(:guest) { highest_access_level { GroupMember::GUEST } } + trait(:planner) { highest_access_level { GroupMember::PLANNER } } trait(:reporter) { highest_access_level { GroupMember::REPORTER } } trait(:developer) { highest_access_level { GroupMember::DEVELOPER } } trait(:maintainer) { highest_access_level { GroupMember::MAINTAINER } } diff --git a/spec/factories/users.rb b/spec/factories/users.rb index ac53454ddc4..1e67cd1487c 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -207,6 +207,7 @@ FactoryBot.define do transient do # rubocop:disable Lint/EmptyBlock -- block is required by factorybot guest_of {} + planner_of {} reporter_of {} developer_of {} maintainer_of {} @@ -216,6 +217,7 @@ FactoryBot.define do after(:create) do |user, evaluator| Array.wrap(evaluator.guest_of).each { |target| target.add_guest(user) } + Array.wrap(evaluator.planner_of).each { |target| target.add_planner(user) } Array.wrap(evaluator.reporter_of).each { |target| target.add_reporter(user) } Array.wrap(evaluator.developer_of).each { |target| target.add_developer(user) } Array.wrap(evaluator.maintainer_of).each { |target| target.add_maintainer(user) } diff --git a/spec/features/dashboard/todos/accessibility_spec.rb b/spec/features/dashboard/todos/accessibility_spec.rb index 4f5c9497fd9..62ac193f58d 100644 --- a/spec/features/dashboard/todos/accessibility_spec.rb +++ b/spec/features/dashboard/todos/accessibility_spec.rb @@ -9,6 +9,10 @@ RSpec.describe 'Dashboard Todos', :js, feature_category: :team_planning do let_it_be(:project) { create(:project, :public, developers: user) } let_it_be(:issue) { create(:issue, project: project, due_date: Date.today, title: "Fix bug") } + before do + stub_feature_flags(todos_vue_application: false) + end + context 'when user does not have todos' do before do sign_in(user) diff --git a/spec/features/dashboard/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb index 15d72c3bb11..b0e3c5747e3 100644 --- a/spec/features/dashboard/todos/target_state_spec.rb +++ b/spec/features/dashboard/todos/target_state_spec.rb @@ -10,6 +10,7 @@ RSpec.describe 'Dashboard > Todo target states', feature_category: :team_plannin let_it_be(:project) { create(:project, :public, developers: user) } before do + stub_feature_flags(todos_vue_application: false) sign_in(user) end diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb index d0d83125225..43477f8e265 100644 --- a/spec/features/dashboard/todos/todos_filtering_spec.rb +++ b/spec/features/dashboard/todos/todos_filtering_spec.rb @@ -21,6 +21,8 @@ RSpec.describe 'Dashboard > User filters todos', :js, feature_category: :notific let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') } before do + stub_feature_flags(todos_vue_application: false) + create(:todo, user: user_1, author: user_2, project: project_1, target: issue1, action: 1) create(:todo, user: user_1, author: user_2, project: project_3, target: issue2, action: 1) create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2) diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb index 183c5fb804e..ef3828dfc22 100644 --- a/spec/features/dashboard/todos/todos_sorting_spec.rb +++ b/spec/features/dashboard/todos/todos_sorting_spec.rb @@ -14,6 +14,7 @@ RSpec.describe 'Dashboard > User sorts todos', feature_category: :notifications before do project.add_developer(user) + stub_feature_flags(todos_vue_application: false) end context 'sort options' do diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 678da51eadb..a0c118b0a69 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -7,6 +7,10 @@ require 'spec_helper' RSpec.describe 'Dashboard Todos (Haml version)', :js, feature_category: :notifications do include DesignManagementTestHelpers + before do + stub_feature_flags(todos_vue_application: false) + end + let_it_be(:user) { create(:user, username: 'john') } let_it_be(:user2) { create(:user, username: 'diane') } let_it_be(:user3) { create(:user) } @@ -545,18 +549,16 @@ RSpec.describe 'Dashboard Todos (Vue version)', :js, feature_category: :notifica let_it_be(:project) { create(:project, :public, developers: user) } let_it_be(:issue) { create(:issue, project: project, due_date: Date.today, title: "Fix bug") } - # FIXME: The shared example below will work as soon as we drop the /vue part of the URL - # See https://gitlab.com/gitlab-org/gitlab/-/issues/501269 - # it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :vue_dashboard_todos_path, :todos - before do sign_in user end + it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_todos_path, :todos + describe 'empty states' do context 'when user has no todos at all (neither pending nor done)' do before do - visit vue_dashboard_todos_path + visit dashboard_todos_path end it 'shows empty state for new users' do @@ -569,7 +571,7 @@ RSpec.describe 'Dashboard Todos (Vue version)', :js, feature_category: :notifica context 'when user has no pending todos (but some done todos)' do before do create_todo(state: :done) - visit vue_dashboard_todos_path + visit dashboard_todos_path end it 'shows a "well done" message on the "Pending" tab' do @@ -581,7 +583,7 @@ RSpec.describe 'Dashboard Todos (Vue version)', :js, feature_category: :notifica context 'when user has pending todos but applied filters with no matches' do before do create_todo(state: :pending) - visit vue_dashboard_todos_path(author_id: user.id) + visit dashboard_todos_path(author_id: user.id) end it 'shows a "no matches" message' do @@ -593,7 +595,7 @@ RSpec.describe 'Dashboard Todos (Vue version)', :js, feature_category: :notifica context 'when user has no done tasks' do before do create_todo(state: :pending) - visit vue_dashboard_todos_path(author_id: user.id) + visit dashboard_todos_path(author_id: user.id) click_on 'Done' end @@ -620,7 +622,7 @@ RSpec.describe 'Dashboard Todos (Vue version)', :js, feature_category: :notifica before do sign_in(user) - visit vue_dashboard_todos_path + visit dashboard_todos_path wait_for_requests end diff --git a/spec/features/groups/participants_autocomplete_spec.rb b/spec/features/groups/participants_autocomplete_spec.rb index 77423ef82a6..90ae5540a1f 100644 --- a/spec/features/groups/participants_autocomplete_spec.rb +++ b/spec/features/groups/participants_autocomplete_spec.rb @@ -37,7 +37,7 @@ RSpec.describe 'Group member autocomplete', :js, feature_category: :groups_and_p create(:group_group_link, shared_group: group, shared_with_group: private_group) end - it 'suggests member of private group as well', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/444683' do # rubocop:disable Layout/LineLength -- We prefer to keep it on a single line, for simplicity sake + it 'suggests member of private group as well', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/444683' do visit edit_group_milestone_path(group, noteable) fill_in 'Description', with: '@' diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb index de277688a98..ac5e764c302 100644 --- a/spec/features/merge_request/user_edits_mr_spec.rb +++ b/spec/features/merge_request/user_edits_mr_spec.rb @@ -94,7 +94,7 @@ RSpec.describe 'Merge request > User edits MR', feature_category: :code_review_w expect(page).to have_content( format( - _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), model_name: _('merge request'), link_to_model: _('merge request') ) diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 575de517e8d..d1ce8c22a94 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -69,6 +69,20 @@ RSpec.describe 'Branches', feature_category: :source_code_management do expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_desc, state: 'active')) end + + it 'sorts the branches by oldest updated', :js do + visit project_branches_filtered_path(project, state: 'active') + + click_button 'Updated date' + within_testid 'branches-dropdown' do + first('span', text: 'Oldest updated').click + end + + expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_asc, state: 'active')) + expect(page).to have_current_path( + [project_branches_path(project, state: 'active'), 'sort=updated_asc'].join('&') + ) + end end describe 'Stale branches page' do @@ -77,6 +91,20 @@ RSpec.describe 'Branches', feature_category: :source_code_management do expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_asc, state: 'stale')) end + + it 'sorts the branches by oldest updated', :js do + visit project_branches_filtered_path(project, state: 'stale') + + click_button 'Oldest updated' + within_testid 'branches-dropdown' do + first('span', text: 'Updated date').click + end + + expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale')) + expect(page).to have_current_path( + [project_branches_path(project, state: 'stale'), 'sort=updated_desc'].join('&') + ) + end end describe 'All branches page' do @@ -165,6 +193,9 @@ RSpec.describe 'Branches', feature_category: :source_code_management do end expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_asc)) + expect(page).to have_current_path( + [project_branches_path(project, state: 'all'), 'sort=updated_asc'].join('&') + ) end it 'avoids a N+1 query in branches index' do diff --git a/spec/frontend/branches/components/sort_dropdown_spec.js b/spec/frontend/branches/components/sort_dropdown_spec.js index 777e54f8e69..18444240027 100644 --- a/spec/frontend/branches/components/sort_dropdown_spec.js +++ b/spec/frontend/branches/components/sort_dropdown_spec.js @@ -8,12 +8,12 @@ import * as urlUtils from '~/lib/utils/url_utility'; describe('Branches Sort Dropdown', () => { let wrapper; - const createWrapper = (props = {}) => { + const createWrapper = (props = {}, state = 'all') => { return extendedWrapper( mount(SortDropdown, { provide: { mode: 'overview', - projectBranchesFilteredPath: '/root/ci-cd-project-demo/-/branches?state=all', + projectBranchesFilteredPath: `/root/ci-cd-project-demo/-/branches?state=${state}`, sortOptions: { name_asc: 'Name', updated_asc: 'Oldest updated', @@ -98,4 +98,22 @@ describe('Branches Sort Dropdown', () => { ); }); }); + + describe('when state is not "all" and search term is submitted', () => { + beforeEach(() => { + urlUtils.visitUrl = jest.fn(); + wrapper = createWrapper({}, 'active'); + }); + + it('should call visitUrl with state=all', () => { + const searchTerm = 'branch-1'; + const searchBox = findSearchBox(); + searchBox.vm.$emit('input', searchTerm); + searchBox.vm.$emit('submit'); + + expect(urlUtils.visitUrl).toHaveBeenCalledWith( + '/root/ci-cd-project-demo/-/branches?state=all&sort=updated_desc&search=branch-1', + ); + }); + }); }); diff --git a/spec/frontend/fixtures/todos.rb b/spec/frontend/fixtures/todos.rb index 58f230de546..cafebc91d3c 100644 --- a/spec/frontend/fixtures/todos.rb +++ b/spec/frontend/fixtures/todos.rb @@ -22,6 +22,7 @@ RSpec.describe 'Todos (JavaScript fixtures)' do before do sign_in(user) + stub_feature_flags(todos_vue_application: false) end it 'todos/todos.html' do diff --git a/spec/frontend/members/components/table/drawer/role_details_drawer_spec.js b/spec/frontend/members/components/table/drawer/role_details_drawer_spec.js index fc7094e3107..de7f0d6345b 100644 --- a/spec/frontend/members/components/table/drawer/role_details_drawer_spec.js +++ b/spec/frontend/members/components/table/drawer/role_details_drawer_spec.js @@ -16,7 +16,9 @@ jest.mock('~/lib/utils/dom_utils', () => ({ describe('Role details drawer', () => { const dropdownItems = roleDropdownItems(updateableMember); - const currentRole = dropdownItems.flatten[5]; + const currentRole = dropdownItems.flatten.find( + (role) => role.accessLevel === updateableMember.accessLevel.integerValue, + ); const newRole = dropdownItems.flatten[2]; const saveRoleStub = jest.fn(); let wrapper; diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js index 9666f1b22cc..e0d4d75fe32 100644 --- a/spec/frontend/members/mock_data.js +++ b/spec/frontend/members/mock_data.js @@ -53,6 +53,7 @@ export const member = { validRoles: { 'Minimal Access': 5, Guest: 10, + Planner: 15, Reporter: 20, Developer: 30, Maintainer: 40, diff --git a/spec/frontend/pages/shared/wikis/components/clone_wiki_modal_spec.js b/spec/frontend/pages/shared/wikis/components/clone_wiki_modal_spec.js index 25f58de31ee..cb821675ab5 100644 --- a/spec/frontend/pages/shared/wikis/components/clone_wiki_modal_spec.js +++ b/spec/frontend/pages/shared/wikis/components/clone_wiki_modal_spec.js @@ -37,7 +37,7 @@ describe('DeleteWikiModal', () => { const findCloneButtonTrigger = () => wrapper.findComponent(GlButton); const findCloneListTrigger = () => wrapper.findComponent(GlDisclosureDropdownItem); - it('renders a delete modal', () => { + it('renders a modal', () => { const modalId = 'clone-wiki-modal'; createComponent(); @@ -49,13 +49,7 @@ describe('DeleteWikiModal', () => { it('shows correct title', () => { createComponent(); - expect(findModalTitle().text()).toBe('Step 1: Clone repository'); - }); - - it('shows correct content', () => { - createComponent(); - - expect(findCloneModal().text()).toContain('Step 2: Install and start Gollum'); + expect(findModalTitle().text()).toBe('Clone repository'); }); describe('setting `showAsDropdownItem`', () => { diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index 014d5067d1b..9e9704ddcd8 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -96,8 +96,9 @@ RSpec.describe Resolvers::MergeRequestsResolver, feature_category: :code_review_ end it 'can batch-resolve merge requests from different projects', :request_store do - # 2 queries for project_authorizations, and 2 for merge_requests - results = batch_sync(max_queries: queries_per_project * 2) do + # 2 queries for organization_users, 2 for project_authorizations, and 2 for merge_requests + extra_auth_queries = 2 + results = batch_sync(max_queries: (queries_per_project + extra_auth_queries) * 2) do a = resolve_mr(project, iids: [iid_1]) b = resolve_mr(project, iids: [iid_2]) c = resolve_mr(other_project, iids: [other_iid]) diff --git a/spec/graphql/types/current_user_todos_type_spec.rb b/spec/graphql/types/current_user_todos_type_spec.rb index 2b33a705ae2..84bc173bf5b 100644 --- a/spec/graphql/types/current_user_todos_type_spec.rb +++ b/spec/graphql/types/current_user_todos_type_spec.rb @@ -165,11 +165,11 @@ RSpec.describe GitlabSchema.types['CurrentUserTodos'] do expect do execute_query(query_type, graphql: query_without_state_arguments) - end.not_to exceed_query_limit(control) # at present this is 3 + end.not_to exceed_query_limit(control).with_threshold(1) # at present this is 4 expect do execute_query(query_type, graphql: with_state_arguments) - end.not_to exceed_query_limit(control).with_threshold(1) + end.not_to exceed_query_limit(control).with_threshold(2) end it 'returns correct data' do diff --git a/spec/graphql/types/member_access_level_enum_spec.rb b/spec/graphql/types/member_access_level_enum_spec.rb index cb079f848e0..03bb4fb343b 100644 --- a/spec/graphql/types/member_access_level_enum_spec.rb +++ b/spec/graphql/types/member_access_level_enum_spec.rb @@ -6,6 +6,6 @@ RSpec.describe Types::MemberAccessLevelEnum, feature_category: :groups_and_proje specify { expect(described_class.graphql_name).to eq('MemberAccessLevel') } it 'exposes all the existing access levels' do - expect(described_class.values.keys).to include(*%w[GUEST REPORTER DEVELOPER MAINTAINER OWNER]) + expect(described_class.values.keys).to include(*%w[GUEST PLANNER REPORTER DEVELOPER MAINTAINER OWNER]) end end diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index 51a8625444c..6c915b288d3 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -62,6 +62,10 @@ RSpec.describe ApplicationSettingsHelper do expect(helper.visible_attributes).to include(:deactivate_dormant_users_period) end + it 'contains :can_create_organization' do + expect(helper.visible_attributes).to include(:can_create_organization) + end + it 'contains rate limit parameters' do expect(helper.visible_attributes).to include( *%i[ diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 386b5916a60..4648b71a00e 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -667,6 +667,7 @@ RSpec.describe GroupsHelper, feature_category: :groups_and_projects do expect(subject).to eq( { 'Guest' => 10, + 'Planner' => 15, 'Reporter' => 20, 'Developer' => 30 } @@ -683,6 +684,7 @@ RSpec.describe GroupsHelper, feature_category: :groups_and_projects do expect(subject).to eq( { 'Guest' => 10, + 'Planner' => 15, 'Reporter' => 20, 'Developer' => 30, 'Maintainer' => 40, @@ -714,6 +716,7 @@ RSpec.describe GroupsHelper, feature_category: :groups_and_projects do expect(helper.access_level_roles_user_can_assign(grand_parent, group.access_level_roles)).to be_empty expect(helper.access_level_roles_user_can_assign(parent, group.access_level_roles)).to eq({ 'Guest' => ::Gitlab::Access::GUEST, + 'Planner' => ::Gitlab::Access::PLANNER, 'Reporter' => ::Gitlab::Access::REPORTER, 'Developer' => ::Gitlab::Access::DEVELOPER }) diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index 016cc634699..ccab1a66965 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -187,7 +187,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do can_sign_out: helper.current_user_menu?(:sign_out), sign_out_link: destroy_user_session_path, issues_dashboard_path: issues_dashboard_path(assignee_username: user.username), - todos_dashboard_path: vue_dashboard_todos_path, + todos_dashboard_path: dashboard_todos_path, projects_path: dashboard_projects_path, groups_path: dashboard_groups_path, gitlab_com_but_not_canary: Gitlab.com_but_not_canary?, diff --git a/spec/lib/gitlab/database/postgres_table_sizes_spec.rb b/spec/lib/gitlab/database/postgres_table_sizes_spec.rb new file mode 100644 index 00000000000..27cb71be23e --- /dev/null +++ b/spec/lib/gitlab/database/postgres_table_sizes_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::PostgresTableSize, type: :model, feature_category: :database do + include Database::DatabaseHelpers + + let(:connection) { ApplicationRecord.connection } + + before do + swapout_view_for_table(:postgres_table_sizes, connection: connection) + end + + describe 'constants' do + it 'defines size thresholds' do + expect(described_class::SMALL).to eq(10.gigabytes) + expect(described_class::MEDIUM).to eq(50.gigabytes) + expect(described_class::LARGE).to eq(100.gigabytes) + end + end + + describe 'table configuration' do + it 'uses correct table name' do + expect(described_class.table_name).to eq('postgres_table_sizes') + end + + it 'uses identifier as primary key' do + expect(described_class.primary_key).to eq('identifier') + end + end + + describe 'scopes' do + let!(:small_table) { create(:postgres_table_size, size_in_bytes: 5.gigabytes) } + let!(:medium_table) { create(:postgres_table_size, size_in_bytes: 30.gigabytes) } + let!(:large_table) { create(:postgres_table_size, size_in_bytes: 70.gigabytes) } + let!(:over_limit_table) { create(:postgres_table_size, size_in_bytes: 120.gigabytes) } + + describe '.small' do + it 'returns tables smaller than SMALL threshold' do + expect(described_class.small).to include(small_table) + expect(described_class.small).not_to include(medium_table, large_table, over_limit_table) + end + end + + describe '.medium' do + it 'returns tables between SMALL AND MEDIUM thresholds' do + expect(described_class.medium).to include(medium_table) + expect(described_class.medium).not_to include(small_table, large_table, over_limit_table) + end + end + + describe '.large' do + it 'returns tables between MEDIUM and LARGE thresholds' do + expect(described_class.large).to include(large_table) + expect(described_class.large).not_to include(small_table, medium_table, over_limit_table) + end + end + + describe '.over_limit' do + it 'returns tables greater than LARGE threshold' do + expect(described_class.over_limit).to include(over_limit_table) + expect(described_class.over_limit).not_to include(small_table, medium_table, large_table) + end + end + end +end diff --git a/spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb b/spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb index fb433923db5..fd55050d4b9 100644 --- a/spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb +++ b/spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::DoorkeeperSecretStoring::Secret::Pbkdf2Sha512 do it 'generates a PBKDF2+SHA512 hashed value in the correct format' do expect(described_class.transform_secret(plaintext_secret)) - .to eq("$pbkdf2-sha512$20000$$.c0G5XJVEew1TyeJk5TrkvB0VyOaTmDzPrsdNRED9vVeZlSyuG3G90F0ow23zUCiWKAVwmNnR/ceh.nJG3MdpQ") # rubocop:disable Layout/LineLength + .to eq("$pbkdf2-sha512$20000$$.c0G5XJVEew1TyeJk5TrkvB0VyOaTmDzPrsdNRED9vVeZlSyuG3G90F0ow23zUCiWKAVwmNnR/ceh.nJG3MdpQ") end end @@ -27,7 +27,7 @@ RSpec.describe Gitlab::DoorkeeperSecretStoring::Secret::Pbkdf2Sha512 do describe '.secret_matches?' do it "match by hashing the input if the stored value is hashed" do plain_secret = 'plain_secret' - stored_value = '$pbkdf2-sha512$20000$$/BwQRdwSpL16xkQhstavh7nvA5avCP7.4n9LLKe9AupgJDeA7M5xOAvG3N3E5XbRyGWWBbbr.BsojPVWzd1Sqg' # rubocop:disable Layout/LineLength + stored_value = '$pbkdf2-sha512$20000$$/BwQRdwSpL16xkQhstavh7nvA5avCP7.4n9LLKe9AupgJDeA7M5xOAvG3N3E5XbRyGWWBbbr.BsojPVWzd1Sqg' expect(described_class.secret_matches?(plain_secret, stored_value)).to be true end end diff --git a/spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb b/spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb index e267d27ed13..8163817f62e 100644 --- a/spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb +++ b/spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512 do it 'generates a PBKDF2+SHA512 hashed value in the correct format' do expect(described_class.transform_secret(plaintext_token)) - .to eq("$pbkdf2-sha512$20000$$.c0G5XJVEew1TyeJk5TrkvB0VyOaTmDzPrsdNRED9vVeZlSyuG3G90F0ow23zUCiWKAVwmNnR/ceh.nJG3MdpQ") # rubocop:disable Layout/LineLength + .to eq("$pbkdf2-sha512$20000$$.c0G5XJVEew1TyeJk5TrkvB0VyOaTmDzPrsdNRED9vVeZlSyuG3G90F0ow23zUCiWKAVwmNnR/ceh.nJG3MdpQ") end end diff --git a/spec/lib/gitlab/merge_requests/message_generator_spec.rb b/spec/lib/gitlab/merge_requests/message_generator_spec.rb index b1b355b2f39..13af4033368 100644 --- a/spec/lib/gitlab/merge_requests/message_generator_spec.rb +++ b/spec/lib/gitlab/merge_requests/message_generator_spec.rb @@ -260,7 +260,7 @@ RSpec.describe Gitlab::MergeRequests::MessageGenerator, feature_category: :code_ context 'when project has template with CRLF newlines' do let(message_template_name) do - "Merge branch '%{source_branch}' into '%{target_branch}'\r\n\r\n%{title}\r\n\r\n%{description}\r\n\r\nSee merge request %{reference}" # rubocop: disable Layout/LineLength + "Merge branch '%{source_branch}' into '%{target_branch}'\r\n\r\n%{title}\r\n\r\n%{description}\r\n\r\nSee merge request %{reference}" end it 'converts it to LF newlines' do diff --git a/spec/lib/gitlab/search/abuse_detection_spec.rb b/spec/lib/gitlab/search/abuse_detection_spec.rb index f849b1f996b..e500faf93d5 100644 --- a/spec/lib/gitlab/search/abuse_detection_spec.rb +++ b/spec/lib/gitlab/search/abuse_detection_spec.rb @@ -83,7 +83,7 @@ RSpec.describe Gitlab::Search::AbuseDetection, feature_category: :global_search word | { query_string: ['stopword only abusive search detected'] } end - (['apples'] * (described_class::MAX_PIPE_SYNTAX_FILTERS + 1)).join('|') | { query_string: ['too many pipe syntax filters'] } # rubocop:disable Layout/LineLength + (['apples'] * (described_class::MAX_PIPE_SYNTAX_FILTERS + 1)).join('|') | { query_string: ['too many pipe syntax filters'] } (['apples'] * described_class::MAX_PIPE_SYNTAX_FILTERS).join('|') | {} 'x' | { query_string: ['abusive tiny search detected'] } 'apples|x' | { query_string: ['abusive tiny search detected'] } diff --git a/spec/migrations/db/post_migrate/20240319005754_swap_columns_for_upstream_pipeline_id_between_ci_builds_and_ci_pipelines_spec.rb b/spec/migrations/db/post_migrate/20240319005754_swap_columns_for_upstream_pipeline_id_between_ci_builds_and_ci_pipelines_spec.rb index 5ee2c311655..154900938b4 100644 --- a/spec/migrations/db/post_migrate/20240319005754_swap_columns_for_upstream_pipeline_id_between_ci_builds_and_ci_pipelines_spec.rb +++ b/spec/migrations/db/post_migrate/20240319005754_swap_columns_for_upstream_pipeline_id_between_ci_builds_and_ci_pipelines_spec.rb @@ -40,7 +40,7 @@ RSpec.describe SwapColumnsForUpstreamPipelineIdBetweenCiBuildsAndCiPipelines, fe :p_ci_builds, [:commit_id, :artifacts_expire_at, :id], name: :p_ci_builds_commit_id_artifacts_expire_at_id_convert_to_big_idx, - where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('dependency_scanning'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))" # rubocop:disable Layout/LineLength -- Just too long + where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('dependency_scanning'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))" ) end diff --git a/spec/models/concerns/encrypted_user_password_spec.rb b/spec/models/concerns/encrypted_user_password_spec.rb index b6447313967..2ee3867fcd4 100644 --- a/spec/models/concerns/encrypted_user_password_spec.rb +++ b/spec/models/concerns/encrypted_user_password_spec.rb @@ -17,7 +17,10 @@ RSpec.describe User do end context 'when password is stored in PBKDF2 format' do - let(:encrypted_password) { '$pbkdf2-sha512$20000$rKbYsScsDdk$iwWBewXmrkD2fFfaG1SDcMIvl9gvEo3fBWUAfiqyVceTlw/DYgKBByHzf45pF5Qn59R4R.NQHsFpvZB4qlsYmw' } # rubocop:disable Layout/LineLength + let(:encrypted_password) do + '$pbkdf2-sha512$20000$rKbYsScsDdk$iwWBewXmrkD2fFfaG1SDcMIvl9gvEo3fBWUAfiqyVceTlw/DYgKBByHzf45pF5Qn59R4R.NQHs' \ + 'FpvZB4qlsYmw' + end it 'uses the decoded password salt' do expect(authenticatable_salt).to eq('aca6d8b1272c0dd9') diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb index 45b05723e09..f53d1fc3ff5 100644 --- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb +++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb @@ -3,12 +3,14 @@ require 'spec_helper' RSpec.describe TokenAuthenticatableStrategies::Base, feature_category: :system_access do + include ::TokenAuthenticatableMatchers + let(:field) { 'token' } let(:digest_field) { "#{field}_digest" } let(:expires_at_field) { "#{field}_expires_at" } - let(:options) { { unique: false } } + let(:options) { { unique: false, format_with_prefix: :token_prefix } } let(:test_class) do - Struct.new(:name, :token_prefix, field, digest_field, expires_at_field) do + Struct.new(:token_prefix, field, digest_field, expires_at_field) do alias_method :read_attribute, :[] end end @@ -33,12 +35,6 @@ RSpec.describe TokenAuthenticatableStrategies::Base, feature_category: :system_a subject(:strategy) { concrete_strategy.new(test_class, field, options) } - describe '.random_bytes' do - it 'generates 16 random bytes' do - expect(described_class.random_bytes.size).to eq(16) - end - end - describe '.fabricate' do context 'when digest strategy is specified' do it 'fabricates digest strategy object' do @@ -127,13 +123,13 @@ RSpec.describe TokenAuthenticatableStrategies::Base, feature_category: :system_a describe '#ensure_token' do let(:token_prefix) { nil } let(:token_generator) { nil } - let(:token_owner_record) { test_class.new(name: 'foo', token_prefix: token_prefix) } - let(:random_bytes) { 'random-bytes' } + let(:token_owner_record) { test_class.new(token_prefix: token_prefix) } + let(:devise_token) { 'devise-token' } subject(:token) { strategy.ensure_token(token_owner_record) } before do - allow(described_class).to receive(:random_bytes).and_return(random_bytes) + allow(Devise).to receive(:friendly_token).and_return(devise_token) end describe ':format_with_prefix option' do @@ -145,12 +141,9 @@ RSpec.describe TokenAuthenticatableStrategies::Base, feature_category: :system_a context 'when set to a Symbol' do let(:token_prefix) { 'prefix-' } - let(:options) { super().merge(format_with_prefix: :token_prefix) } it 'generates a random token' do - expect(Devise).to receive(:friendly_token).and_return('devise_token') - - expect(token).to eq("#{token_prefix}devise_token") + expect(token).to eq("#{token_prefix}#{devise_token}") end end @@ -174,55 +167,73 @@ RSpec.describe TokenAuthenticatableStrategies::Base, feature_category: :system_a end describe ':routable_token option' do + let(:random_bytes) { 'a' * described_class::RANDOM_BYTES_LENGTH } let(:cell_setting) { {} } - let(:options) do - super().merge( - routable_token: { n: ->(token_owner_record) { token_owner_record.name } }, - format_with_prefix: :token_prefix - ) - end + let(:routable_token_payload) { { payload: { o: ->(_) { 'foo' } } } } - before do - allow(Settings).to receive(:cell).and_return(cell_setting) - end + shared_examples 'a routable token' do + it 'delegates to RoutableTokenGenerator#generate_token' do + generator = instance_double(TokenAuthenticatableStrategies::RoutableTokenGenerator) + expect(TokenAuthenticatableStrategies::RoutableTokenGenerator) + .to receive(:new).with( + token_owner_record, + routing_payload: routable_token_payload[:payload], + prefix: token_prefix + ).and_return(generator) + expect(generator).to receive(:generate_token) - context 'when token_owner_record does not respond to #user' do - it 'generates a non routable token' do - expect(Devise).to receive(:friendly_token).and_return('devise_token') - - expect(token).to eq('devise_token') + token end end - context 'when token_owner_record responds to #user' do - let(:user) { build(:user) } + context 'with a { payload: } hash' do + let(:options) { super().merge(routable_token: routable_token_payload) } - before do - stub_feature_flags(routable_token: user) - allow(token_owner_record).to receive(:user).and_return(user) + it_behaves_like 'a routable token' + end + + context 'with a { if:, payload: } hash when if: evaluates to true' do + let(:options) do + super().merge( + routable_token: { + if: ->(token_owner_record) { token_owner_record.respond_to?(:token_prefix) }, + **routable_token_payload + } + ) end - context 'when Settings.cells.id is not present' do - it 'generates a routable token' do - expect(Base64.urlsafe_decode64(token)).to eq("n:foo\nr:#{random_bytes}") - end + it_behaves_like 'a routable token' + end + + context 'with a { if:, payload: } hash when if: evaluates to false' do + let(:options) do + super().merge( + routable_token: { + if: ->(token_owner_record) { token_owner_record.respond_to?(:no_method) }, + **routable_token_payload + } + ) end - context 'when Settings.cells.id is present' do - let(:cell_setting) { { id: 100 } } - - it 'generates a routable token' do - expect(Base64.urlsafe_decode64(token)).to eq("c:#{cell_setting[:id].to_s(36)}\nn:foo\nr:#{random_bytes}") - end + it 'generates a random token' do + expect(token).to eq("#{token_prefix}#{devise_token}") end + end + end - context 'with a prefix set' do - let(:token_prefix) { 'prefix-' } + describe ':unique option' do + let(:token_prefix) { 'prefix-' } + let(:options) { super().merge(unique: true, format_with_prefix: :token_prefix) } - it 'generates a routable token' do - expect(token).to start_with(token_prefix) - expect(Base64.urlsafe_decode64(token.delete_prefix(token_prefix))).to eq("n:foo\nr:#{random_bytes}") - end + context 'when generated token is already in DB' do + it 'generates a different token' do + expect(strategy).to receive(:generate_token).and_return('prefix-token1') + expect(strategy).to receive(:find_token_authenticatable).with('prefix-token1', true).and_return(true) + + expect(strategy).to receive(:generate_token).and_return('prefix-token2') + expect(strategy).to receive(:find_token_authenticatable).with('prefix-token2', true).and_return(false) + + expect(token).to eq('prefix-token2') end end end diff --git a/spec/models/concerns/token_authenticatable_strategies/routable_token_generator_spec.rb b/spec/models/concerns/token_authenticatable_strategies/routable_token_generator_spec.rb new file mode 100644 index 00000000000..37d13420709 --- /dev/null +++ b/spec/models/concerns/token_authenticatable_strategies/routable_token_generator_spec.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +require_relative '../../../../app/models/concerns/token_authenticatable_strategies/routable_token_generator' +require_relative '../../../support/matchers/token_authenticatable_matchers' + +RSpec.describe TokenAuthenticatableStrategies::RoutableTokenGenerator, feature_category: :system_access do + include ::TokenAuthenticatableMatchers + + let(:test_class) { Struct.new(:id) } + let(:token_owner_record) { test_class.new(id: 1) } + let(:routing_payload) { {} } + let(:prefix) { nil } + let(:generator) do + described_class.new(token_owner_record, routing_payload: routing_payload, prefix: prefix) + end + + describe '.random_bytes' do + it 'generates random bytes' do + expect(described_class.random_bytes(42).size).to eq(42) + end + end + + describe '#initialize' do + context 'with missing required routing keys' do + let(:routing_payload) do + { p: 'foo' } + end + + it 'raises an exception' do + expect { generator }.to raise_error(described_class::MissingRequiredRoutingKeys, + "Missing required routing keys: :o. Required routing keys are: :o.") + end + end + + context 'with invalid routing keys' do + let(:routing_payload) do + { o: 'foo', q: 'bar', k: 'baz' } + end + + it 'raises an exception' do + expect { generator }.to raise_error(described_class::InvalidRoutingKeys, + "Invalid routing keys: :q, :k. Valid routing keys are: :c, :g, :o, :p, :u.") + end + end + end + + describe '#generate_token' do + let(:random_bytes) { 'a' * described_class::RANDOM_BYTES_LENGTH } + let(:cell_setting) { {} } + + subject(:token) { generator.generate_token } + + before do + allow(described_class) + .to receive(:random_bytes).with(described_class::RANDOM_BYTES_LENGTH).and_return(random_bytes) + allow(Settings).to receive(:cell).and_return(cell_setting) + end + + shared_examples 'a routable token' do + context 'when Settings.cells.id is not present' do + it 'generates a routable token' do + expect(token) + .to be_a_routable_token + .with_payload("o:#{token_owner_record.id.to_s(36)}") + end + end + + context 'when Settings.cells.id is present' do + let(:cell_setting) { { id: 100 } } + + it 'generates a routable token' do + expect(token) + .to be_a_routable_token + .with_payload("c:#{cell_setting[:id].to_s(36)}\no:#{token_owner_record.id.to_s(36)}") + end + end + + context 'with a prefix set' do + let(:prefix) { 'prefix-' } + + it 'generates a routable token' do + expect(token) + .to be_a_routable_token + .with_payload("o:#{token_owner_record.id.to_s(36)}") + .and_prefix(prefix) + end + end + end + + context 'with a routing payload hash' do + let(:routing_payload) do + { o: ->(token_owner_record) { token_owner_record.id } } + end + + it_behaves_like 'a routable token' + end + + context 'with a too big encodable routing payload' do + let(:routing_payload) do + { o: ->(_) { 'a' * 158 } } + end + + it 'raises an exception' do + expect { token }.to raise_error(described_class::PayloadTooLarge, + "Routing payload is too big: 160. " \ + "Maximum size is #{described_class::MAXIMUM_SIZE_OF_ROUTING_PAYLOAD}." + ) + end + end + + context 'with a routing payload with string value' do + let(:routing_payload) do + { o: ->(_) { 'foo' } } + end + + it 'generates a routable token' do + expect(token) + .to be_a_routable_token + .with_payload("o:foo") + end + end + + context 'with actual random bytes generated' do + let(:routing_payload) do + { o: ->(token_owner_record) { token_owner_record.id } } + end + + before do + allow(described_class).to receive(:random_bytes).with(described_class::RANDOM_BYTES_LENGTH).and_call_original + end + + it 'uses a different random_bytes value on each call' do + first_token = token + + expect(first_token) + .to be_a_routable_token + .with_prefix(prefix) + + second_token = generator.generate_token + + expect(second_token) + .to be_a_routable_token + .with_prefix(prefix) + expect(first_token) + .to have_different_random_bytes_than(second_token) + .with_prefix(prefix) + expect(second_token).not_to eq(first_token) + end + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index d1aa8f41e99..3341123a6ee 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -3197,6 +3197,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do expect(group.access_level_roles).to eq( { 'Guest' => 10, + 'Planner' => 15, 'Reporter' => 20, 'Developer' => 30, 'Maintainer' => 40, diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 70f843be0e1..891d349b6c4 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -113,6 +113,7 @@ RSpec.describe ProjectMember, feature_category: :groups_and_projects do it 'returns Gitlab::Access.options' do expect(access_levels).to eq({ "Guest" => 10, + "Planner" => 15, "Reporter" => 20, "Developer" => 30 }) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c72de28f9f6..2d45132440c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1215,7 +1215,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr describe 'delegation' do let_it_be(:project) { create(:project) } - [:add_guest, :add_reporter, :add_developer, :add_maintainer, :add_member, :add_members].each do |method| + [:add_guest, :add_planner, :add_reporter, :add_developer, :add_maintainer, :add_member, :add_members].each do |method| it { is_expected.to delegate_method(method).to(:team) } end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 728b42abe53..d7bf81869c1 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -7,6 +7,7 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do let(:maintainer) { create(:user) } let(:reporter) { create(:user) } + let(:planner) { create(:user) } let(:guest) { create(:user) } let(:nonmember) { create(:user) } @@ -15,6 +16,7 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do project.add_maintainer(maintainer) + project.add_planner(planner) project.add_reporter(reporter) project.add_guest(guest) end @@ -22,6 +24,7 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do describe 'members collection' do it { expect(project.team.maintainers).to include(maintainer) } it { expect(project.team.maintainers).not_to include(guest) } + it { expect(project.team.maintainers).not_to include(planner) } it { expect(project.team.maintainers).not_to include(reporter) } it { expect(project.team.maintainers).not_to include(nonmember) } end @@ -29,10 +32,12 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do describe 'access methods' do it { expect(project.team.maintainer?(maintainer)).to be_truthy } it { expect(project.team.maintainer?(guest)).to be_falsey } + it { expect(project.team.maintainer?(planner)).to be_falsey } it { expect(project.team.maintainer?(reporter)).to be_falsey } it { expect(project.team.maintainer?(nonmember)).to be_falsey } it { expect(project.team.member?(nonmember)).to be_falsey } it { expect(project.team.member?(guest)).to be_truthy } + it { expect(project.team.member?(planner, Gitlab::Access::PLANNER)).to be_truthy } it { expect(project.team.member?(reporter, Gitlab::Access::REPORTER)).to be_truthy } it { expect(project.team.member?(guest, Gitlab::Access::REPORTER)).to be_falsey } it { expect(project.team.member?(nonmember, Gitlab::Access::GUEST)).to be_falsey } @@ -46,6 +51,7 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do group.add_maintainer(maintainer) group.add_reporter(reporter) + group.add_planner(planner) group.add_guest(guest) # If user is a group and a project member - GitLab uses highest permission @@ -57,14 +63,17 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do describe 'members collection' do it { expect(project.team.reporters).to include(reporter) } + it { expect(project.team.planners).to include(planner) } it { expect(project.team.maintainers).to include(maintainer) } it { expect(project.team.maintainers).to include(guest) } it { expect(project.team.maintainers).not_to include(reporter) } + it { expect(project.team.maintainers).not_to include(planner) } it { expect(project.team.maintainers).not_to include(nonmember) } end describe 'access methods' do it { expect(project.team.reporter?(reporter)).to be_truthy } + it { expect(project.team.planner?(planner)).to be_truthy } it { expect(project.team.maintainer?(maintainer)).to be_truthy } it { expect(project.team.maintainer?(guest)).to be_truthy } it { expect(project.team.maintainer?(reporter)).to be_falsey } @@ -244,12 +253,14 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do project.add_maintainer(maintainer) project.add_reporter(reporter) + project.add_planner(planner) project.add_guest(guest) project.request_access(requester) end it { expect(project.team.find_member(maintainer.id)).to be_a(ProjectMember) } it { expect(project.team.find_member(reporter.id)).to be_a(ProjectMember) } + it { expect(project.team.find_member(planner.id)).to be_a(ProjectMember) } it { expect(project.team.find_member(guest.id)).to be_a(ProjectMember) } it { expect(project.team.find_member(nonmember.id)).to be_nil } it { expect(project.team.find_member(requester.id)).to be_nil } @@ -263,12 +274,14 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do group.add_maintainer(maintainer) group.add_reporter(reporter) + group.add_planner(planner) group.add_guest(guest) group.request_access(requester) end it { expect(project.team.find_member(maintainer.id)).to be_a(GroupMember) } it { expect(project.team.find_member(reporter.id)).to be_a(GroupMember) } + it { expect(project.team.find_member(planner.id)).to be_a(GroupMember) } it { expect(project.team.find_member(guest.id)).to be_a(GroupMember) } it { expect(project.team.find_member(nonmember.id)).to be_nil } it { expect(project.team.find_member(requester.id)).to be_nil } @@ -411,11 +424,13 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do project.add_maintainer(maintainer) project.add_reporter(reporter) + project.add_planner(planner) project.add_guest(guest) end it { expect(project.team.contributor?(maintainer.id)).to be false } it { expect(project.team.contributor?(reporter.id)).to be false } + it { expect(project.team.contributor?(planner.id)).to be false } it { expect(project.team.contributor?(guest.id)).to be false } end @@ -445,12 +460,14 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do project.add_maintainer(maintainer) project.add_reporter(reporter) + project.add_planner(planner) project.add_guest(guest) project.request_access(requester) end it { expect(project.team.max_member_access(maintainer.id)).to eq(Gitlab::Access::MAINTAINER) } it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_member_access(planner.id)).to eq(Gitlab::Access::PLANNER) } it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) } it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) } it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) } @@ -465,10 +482,12 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do group.add_maintainer(maintainer) group.add_reporter(reporter) + group.add_planner(planner) end it { expect(project.team.max_member_access(maintainer.id)).to eq(Gitlab::Access::DEVELOPER) } it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_member_access(planner.id)).to eq(Gitlab::Access::PLANNER) } it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) } it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) } @@ -479,6 +498,7 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do it { expect(project.team.max_member_access(maintainer.id)).to eq(Gitlab::Access::NO_ACCESS) } it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::NO_ACCESS) } + it { expect(project.team.max_member_access(planner.id)).to eq(Gitlab::Access::NO_ACCESS) } end end end @@ -492,12 +512,14 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do group.add_maintainer(maintainer) group.add_reporter(reporter) + group.add_planner(planner) group.add_guest(guest) group.request_access(requester) end it { expect(project.team.max_member_access(maintainer.id)).to eq(Gitlab::Access::MAINTAINER) } it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) } + it { expect(project.team.max_member_access(planner.id)).to eq(Gitlab::Access::PLANNER) } it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) } it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) } it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) } @@ -644,6 +666,7 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do let(:maintainer) { create(:user) } let(:reporter) { create(:user) } + let(:planner) { create(:user) } let(:guest) { create(:user) } let(:promoted_guest) { create(:user) } @@ -655,13 +678,17 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do let(:second_user_without_access) { create(:user) } let(:users) do - [maintainer, reporter, promoted_guest, guest, group_developer, second_developer, user_without_access].map(&:id) + [ + maintainer, reporter, planner, promoted_guest, guest, + group_developer, second_developer, user_without_access + ].map(&:id) end let(:expected) do { maintainer.id => Gitlab::Access::MAINTAINER, reporter.id => Gitlab::Access::REPORTER, + planner.id => Gitlab::Access::PLANNER, promoted_guest.id => Gitlab::Access::DEVELOPER, guest.id => Gitlab::Access::GUEST, group_developer.id => Gitlab::Access::DEVELOPER, @@ -673,6 +700,7 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do before do project.add_maintainer(maintainer) project.add_reporter(reporter) + project.add_planner(planner) project.add_guest(promoted_guest) project.add_guest(guest) diff --git a/spec/models/user_highest_role_spec.rb b/spec/models/user_highest_role_spec.rb index cf86a996ba5..56a7e22871a 100644 --- a/spec/models/user_highest_role_spec.rb +++ b/spec/models/user_highest_role_spec.rb @@ -31,6 +31,7 @@ RSpec.describe UserHighestRole, feature_category: :plan_provisioning do let(:expected_allowed_values) do [ Gitlab::Access::GUEST, + Gitlab::Access::PLANNER, Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d3b8dec0c3a..e18bd8ab033 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -3973,6 +3973,7 @@ RSpec.describe User, feature_category: :user_profile do end [ + Gitlab::Access::PLANNER, Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, @@ -5297,18 +5298,22 @@ RSpec.describe User, feature_category: :user_profile do describe '#projects_where_can_admin_issues' do let(:user) { create(:user) } - it 'includes projects for which the user access level is above or equal to reporter' do + it 'includes projects for which the user access level is above or equal to planner' do + planner_project = create(:project) { |p| p.add_planner(user) } reporter_project = create(:project) { |p| p.add_reporter(user) } developer_project = create(:project) { |p| p.add_developer(user) } maintainer_project = create(:project) { |p| p.add_maintainer(user) } - expect(user.projects_where_can_admin_issues.to_a).to match_array([maintainer_project, developer_project, reporter_project]) + expect(user.projects_where_can_admin_issues.to_a).to match_array( + [maintainer_project, developer_project, reporter_project, planner_project] + ) expect(user.can?(:admin_issue, maintainer_project)).to eq(true) expect(user.can?(:admin_issue, developer_project)).to eq(true) expect(user.can?(:admin_issue, reporter_project)).to eq(true) + expect(user.can?(:admin_issue, planner_project)).to eq(true) end - it 'does not include for which the user access level is below reporter' do + it 'does not include for which the user access level is below planner' do project = create(:project) guest_project = create(:project) { |p| p.add_guest(user) } @@ -6906,11 +6911,13 @@ RSpec.describe User, feature_category: :user_profile do let(:maintainer_project) { create(:project) } let(:reporter_project) { create(:project) } let(:developer_project) { create(:project) } + let(:planner_project) { create(:project) } let(:guest_project) { create(:project) } let(:no_access_project) { create(:project) } let(:projects) do - [owner_project, maintainer_project, reporter_project, developer_project, guest_project, no_access_project].map(&:id) + [owner_project, maintainer_project, reporter_project, + developer_project, planner_project, guest_project, no_access_project].map(&:id) end let(:expected) do @@ -6920,6 +6927,7 @@ RSpec.describe User, feature_category: :user_profile do reporter_project.id => Gitlab::Access::REPORTER, developer_project.id => Gitlab::Access::DEVELOPER, guest_project.id => Gitlab::Access::GUEST, + planner_project.id => Gitlab::Access::PLANNER, no_access_project.id => Gitlab::Access::NO_ACCESS } end @@ -6929,6 +6937,7 @@ RSpec.describe User, feature_category: :user_profile do maintainer_project.add_maintainer(user) reporter_project.add_reporter(user) developer_project.add_developer(user) + planner_project.add_planner(user) guest_project.add_guest(user) end @@ -6987,11 +6996,13 @@ RSpec.describe User, feature_category: :user_profile do let(:maintainer_group) { create(:group) } let(:reporter_group) { create(:group) } let(:developer_group) { create(:group) } + let(:planner_group) { create(:group) } let(:guest_group) { create(:group) } let(:no_access_group) { create(:group) } let(:groups) do - [owner_group, maintainer_group, reporter_group, developer_group, guest_group, no_access_group].map(&:id) + [owner_group, maintainer_group, reporter_group, developer_group, + planner_group, guest_group, no_access_group, planner_group].map(&:id) end let(:expected) do @@ -7000,6 +7011,7 @@ RSpec.describe User, feature_category: :user_profile do maintainer_group.id => Gitlab::Access::MAINTAINER, reporter_group.id => Gitlab::Access::REPORTER, developer_group.id => Gitlab::Access::DEVELOPER, + planner_group.id => Gitlab::Access::PLANNER, guest_group.id => Gitlab::Access::GUEST, no_access_group.id => Gitlab::Access::NO_ACCESS } @@ -7010,6 +7022,7 @@ RSpec.describe User, feature_category: :user_profile do maintainer_group.add_maintainer(user) reporter_group.add_reporter(user) developer_group.add_developer(user) + planner_group.add_planner(user) guest_group.add_guest(user) end @@ -8092,6 +8105,7 @@ RSpec.describe User, feature_category: :user_profile do context 'when memberships exist' do it 'returns the highest access level for non requested memberships' do create(:group_member, :reporter, user_id: user.id) + create(:project_member, :planner, user_id: user.id) create(:project_member, :guest, user_id: user.id) create(:project_member, :maintainer, user_id: user.id, requested_at: Time.current) diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb index 3786fb22266..fcb452cf169 100644 --- a/spec/policies/board_policy_spec.rb +++ b/spec/policies/board_policy_spec.rb @@ -2,12 +2,12 @@ require 'spec_helper' -RSpec.describe BoardPolicy do - let(:user) { create(:user) } - let(:project) { create(:project, :private) } - let(:group) { create(:group, :private) } - let(:group_board) { create(:board, group: group) } - let(:project_board) { create(:board, project: project) } +RSpec.describe BoardPolicy, feature_category: :portfolio_management do + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:project) { create(:project, :private) } + let_it_be_with_reload(:group) { create(:group, :private) } + let_it_be(:group_board) { create(:board, group: group) } + let_it_be(:project_board) { create(:board, project: project) } let(:board_permissions) do [ @@ -21,7 +21,7 @@ RSpec.describe BoardPolicy do subject { described_class.new(user, group_board) } context 'user has access' do - before do + before_all do group.add_developer(user) end @@ -41,7 +41,7 @@ RSpec.describe BoardPolicy do subject { described_class.new(user, project_board) } context 'user has access' do - before do + before_all do project.add_developer(user) end @@ -83,7 +83,7 @@ RSpec.describe BoardPolicy do context 'when user can admin project issues' do it 'allows to add non backlog issues from issue board' do - project.add_reporter(current_user) + project.add_planner(current_user) expect_allowed(:create_non_backlog_issues) end @@ -99,29 +99,34 @@ RSpec.describe BoardPolicy do end context 'for group boards' do - let!(:current_user) { create(:user) } - let!(:project_1) { create(:project, namespace: group) } - let!(:project_2) { create(:project, namespace: group) } - let!(:group_board) { create(:board, group: group) } + let_it_be(:guest) { create(:user) } + let_it_be(:planner) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:group_board) { create(:board, group: group) } + let_it_be(:project_2) do + create(:project, namespace: group, guests: guest, planners: planner, reporters: reporter) + end + + let(:current_user) { nil } subject { described_class.new(current_user, group_board) } it_behaves_like 'with admin' - context 'when user is at least reporter in one of the child projects' do - it 'allows to add non backlog issues from issue board' do - project_2.add_reporter(current_user) + context 'with planner or reporter role in a child project' do + where(role: %w[planner reporter]) - expect_allowed(:create_non_backlog_issues) + with_them do + let(:current_user) { public_send(role) } + + it { expect_allowed(:create_non_backlog_issues) } end end - context 'when user is not a reporter from any child projects' do - it 'does not allow to add non backlog issues from issue board' do - project_2.add_guest(current_user) + context 'when user is not at least a planner from any child projects' do + let(:current_user) { guest } - expect_disallowed(:create_non_backlog_issues) - end + it { expect_disallowed(:create_non_backlog_issues) } end end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index b2dbe59241e..544cd7176bc 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -14,6 +14,8 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_disallowed(:upload_file) + expect_disallowed(*(guest_permissions - public_permissions)) + expect_disallowed(*(planner_permissions - guest_permissions)) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -29,6 +31,8 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_disallowed(:upload_file) + expect_disallowed(*(guest_permissions - public_permissions)) + expect_disallowed(*(planner_permissions - guest_permissions)) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -47,6 +51,8 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_disallowed(*public_permissions) + expect_disallowed(*guest_permissions) + expect_disallowed(*planner_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*owner_permissions) end @@ -62,6 +68,8 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_disallowed(*public_permissions) + expect_disallowed(*guest_permissions) + expect_disallowed(*planner_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*owner_permissions) end @@ -96,6 +104,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_disallowed(*public_permissions) expect_disallowed(*guest_permissions) + expect_disallowed(*planner_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -109,6 +118,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_disallowed(*(planner_permissions - guest_permissions)) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -120,12 +130,31 @@ RSpec.describe GroupPolicy, feature_category: :system_access do end end + context 'planners' do + let(:current_user) { planner } + + specify do + expect_allowed(*public_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) + expect_disallowed(*(reporter_permissions - planner_permissions)) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*(owner_permissions - [:destroy_issue])) + end + + it_behaves_like 'deploy token does not get confused with user' do + let(:user_id) { planner.id } + end + end + context 'reporter' do let(:current_user) { reporter } specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - [:destroy_issue])) expect_allowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -143,6 +172,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - [:destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -165,6 +195,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it 'allows permissions from lower roles' do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - [:destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) end @@ -179,6 +210,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it 'allows every maintainer permission' do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions - [:destroy_issue]) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) expect_allowed(*maintainer_permissions) @@ -197,6 +229,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) expect_allowed(*maintainer_permissions) @@ -214,6 +247,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_disallowed(*public_permissions) expect_disallowed(*guest_permissions) + expect_disallowed(*planner_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -224,6 +258,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) expect_allowed(*maintainer_permissions) @@ -247,6 +282,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) expect_allowed(*maintainer_permissions) @@ -274,6 +310,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it :aggregate_failures do expect_allowed(:read_resource_access_tokens, :destroy_resource_access_tokens) expect_disallowed(*guest_permissions) + expect_disallowed(*planner_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -290,6 +327,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it :aggregate_failures do expect_disallowed(:read_resource_access_tokens, :destroy_resource_access_tokens) expect_disallowed(*guest_permissions) + expect_disallowed(*planner_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -323,6 +361,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_disallowed(*public_permissions) expect_disallowed(*guest_permissions) + expect_disallowed(*planner_permissions) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -336,6 +375,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_disallowed(*(planner_permissions - guest_permissions)) expect_disallowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -343,12 +383,27 @@ RSpec.describe GroupPolicy, feature_category: :system_access do end end + context 'planners' do + let(:current_user) { planner } + + specify do + expect_allowed(*public_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) + expect_disallowed(*(reporter_permissions - planner_permissions)) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*(owner_permissions - [:destroy_issue])) + end + end + context 'reporter' do let(:current_user) { reporter } specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - [:destroy_issue])) expect_allowed(*reporter_permissions) expect_disallowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -362,6 +417,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - [:destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) expect_disallowed(*maintainer_permissions) @@ -375,6 +431,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - [:destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) end @@ -391,6 +448,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify do expect_allowed(*public_permissions) expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) expect_allowed(*reporter_permissions) expect_allowed(*developer_permissions) expect_allowed(*maintainer_permissions) @@ -578,26 +636,31 @@ RSpec.describe GroupPolicy, feature_category: :system_access do context 'create_projects' do context 'without visibility levels restricted' do where(:project_creation_level, :current_user, :create_projects_allowed?) do + nil | lazy { planner } | false nil | lazy { reporter } | false nil | lazy { developer } | true nil | lazy { maintainer } | true nil | lazy { owner } | true nil | lazy { admin } | true + ::Gitlab::Access::NO_ONE_PROJECT_ACCESS | lazy { planner } | false ::Gitlab::Access::NO_ONE_PROJECT_ACCESS | lazy { reporter } | false ::Gitlab::Access::NO_ONE_PROJECT_ACCESS | lazy { developer } | false ::Gitlab::Access::NO_ONE_PROJECT_ACCESS | lazy { maintainer } | false ::Gitlab::Access::NO_ONE_PROJECT_ACCESS | lazy { owner } | false ::Gitlab::Access::NO_ONE_PROJECT_ACCESS | lazy { admin } | false + ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS | lazy { planner } | false ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS | lazy { reporter } | false ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS | lazy { developer } | false ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS | lazy { maintainer } | true ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS | lazy { owner } | true ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS | lazy { admin } | true + ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS | lazy { planner } | false ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS | lazy { reporter } | false ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS | lazy { developer } | true ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS | lazy { maintainer } | true ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS | lazy { owner } | true ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS | lazy { admin } | true + ::Gitlab::Access::ADMINISTRATOR_PROJECT_ACCESS | lazy { planner } | false ::Gitlab::Access::ADMINISTRATOR_PROJECT_ACCESS | lazy { reporter } | false ::Gitlab::Access::ADMINISTRATOR_PROJECT_ACCESS | lazy { developer } | false ::Gitlab::Access::ADMINISTRATOR_PROJECT_ACCESS | lazy { maintainer } | false @@ -678,6 +741,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do context 'when group has no project creation level set' do let(:project_creation_level) { nil } + context 'planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:import_projects) } + end + context 'reporter' do let(:current_user) { reporter } @@ -706,6 +775,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do context 'when group has project creation level set to no one' do let(:project_creation_level) { ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } + context 'planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:import_projects) } + end + context 'reporter' do let(:current_user) { reporter } @@ -734,6 +809,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do context 'when group has project creation level set to maintainer only' do let(:project_creation_level) { ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } + context 'planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:import_projects) } + end + context 'reporter' do let(:current_user) { reporter } @@ -762,6 +843,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do context 'when group has project creation level set to developers + maintainer' do let(:project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + context 'planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:import_projects) } + end + context 'reporter' do let(:current_user) { reporter } @@ -794,6 +881,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do group.update!(subgroup_creation_level: ::Gitlab::Access::OWNER_SUBGROUP_ACCESS) end + context 'planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:create_subgroup) } + end + context 'reporter' do let(:current_user) { reporter } @@ -824,6 +917,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do group.update!(subgroup_creation_level: ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS) end + context 'planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:create_subgroup) } + end + context 'reporter' do let(:current_user) { reporter } @@ -878,7 +977,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do end end - %w[guest reporter developer maintainer owner].each do |role| + %w[guest planner reporter developer maintainer owner].each do |role| context role do let(:current_user) { send(role) } @@ -976,6 +1075,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it { is_expected.to be_disallowed(:create_jira_connect_subscription) } end + context 'with planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:create_jira_connect_subscription) } + end + context 'with guest' do let(:current_user) { guest } @@ -1026,6 +1131,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_package) } end + context 'with planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:read_package) } + end + context 'with guest' do let(:current_user) { guest } @@ -1278,6 +1389,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it { is_expected.to be_disallowed(:update_runners_registration_token) } end + context 'with planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + context 'with guest' do let(:current_user) { guest } @@ -1408,6 +1525,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it { is_expected.to be_disallowed(:register_group_runners) } end + context 'with planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:register_group_runners) } + end + context 'with guest' do let(:current_user) { guest } @@ -1508,6 +1631,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do it { is_expected.to be_disallowed(:create_runner) } end + context 'with planner' do + let(:current_user) { planner } + + it { is_expected.to be_disallowed(:create_runner) } + end + context 'with guest' do let(:current_user) { guest } @@ -1564,6 +1693,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify { is_expected.to be_disallowed(:read_group_all_available_runners) } end + context 'with planner' do + let(:current_user) { planner } + + specify { is_expected.to be_disallowed(:read_group_all_available_runners) } + end + context 'with guest' do let(:current_user) { guest } @@ -1633,6 +1768,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do :maintainer | nil | false :developer | nil | false :reporter | nil | false + :planner | nil | false :guest | nil | false end diff --git a/spec/policies/issuable_policy_spec.rb b/spec/policies/issuable_policy_spec.rb index 71cf341b584..f6767ac88e3 100644 --- a/spec/policies/issuable_policy_spec.rb +++ b/spec/policies/issuable_policy_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe IssuablePolicy, :models do let_it_be(:user) { create(:user) } let_it_be(:guest) { create(:user) } + let_it_be(:planner) { create(:user) } let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } let_it_be(:project) { create(:project, :public) } @@ -15,6 +16,7 @@ RSpec.describe IssuablePolicy, :models do before do project.add_developer(developer) project.add_guest(guest) + project.add_planner(planner) project.add_reporter(reporter) end @@ -46,6 +48,10 @@ RSpec.describe IssuablePolicy, :models do expect(permissions(guest, issue)).to be_allowed(:read_incident_management_timeline_event) end + it 'disallows planners from managing timeline events' do + expect(permissions(planner, issue)).to be_disallowed(:admin_incident_management_timeline_event) + end + it 'disallows reporters from managing timeline events' do expect(permissions(reporter, issue)).to be_disallowed(:admin_incident_management_timeline_event) end @@ -79,6 +85,10 @@ RSpec.describe IssuablePolicy, :models do expect(permissions(guest, issue)).to be_allowed(:read_incident_management_timeline_event) end + it 'disallows planners from managing timeline events' do + expect(permissions(planner, issue)).to be_disallowed(:admin_incident_management_timeline_event) + end + it 'disallows reporters from managing timeline events' do expect(permissions(reporter, issue)).to be_disallowed(:admin_incident_management_timeline_event) end @@ -169,6 +179,16 @@ RSpec.describe IssuablePolicy, :models do end end + context 'when user is at planner of the project' do + it 'allows timelogs creation' do + expect(permissions(planner, issue)).to be_allowed(:create_timelog) + end + + it 'allows reading internal notes' do + expect(permissions(planner, issue)).to be_allowed(:read_internal_note) + end + end + context 'when user is at least reporter of the project' do it 'allows timelogs creation' do expect(permissions(reporter, issue)).to be_allowed(:create_timelog) diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index e82ad79f2a4..442dc9de565 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -11,6 +11,7 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do let_it_be(:group) { create(:group, :public) } let_it_be(:admin) { create(:user, :admin) } let_it_be(:guest) { create(:user) } + let_it_be(:planner) { create(:user) } let_it_be(:author) { create(:user) } let_it_be(:assignee) { create(:user) } let_it_be(:reporter) { create(:user) } @@ -80,6 +81,7 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do project.add_guest(guest) project.add_guest(author) project.add_guest(assignee) + project.add_planner(planner) project.add_reporter(reporter) group.add_reporter(reporter_from_group_link) @@ -101,6 +103,12 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do expect(permissions(guest, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality) end + it 'allows planners to read, update, admin and create confidential notes' do + expect(permissions(planner, issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) + expect(permissions(planner, issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) + expect(permissions(planner, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality, :mark_note_as_internal, :admin_issue_relation) + end + it 'allows reporters to read, update, admin and create confidential notes' do expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) @@ -156,6 +164,11 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji, :admin_issue_link) end + it 'allows planners to read, update, and admin confidential issues' do + expect(permissions(planner, confidential_issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji) + expect(permissions(planner, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji) + end + it 'allows reporters to read, update, and admin confidential issues' do expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji) expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji) @@ -205,6 +218,7 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do before_all do project.add_guest(guest) + project.add_planner(planner) project.add_reporter(reporter) project.add_maintainer(maintainer) project.add_owner(owner) @@ -246,6 +260,14 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do expect(permissions(guest, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) end + it 'allows planners to read, update, reopen, and admin issues' do + expect(permissions(planner, issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) + expect(permissions(planner, issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) + expect(permissions(planner, issue_locked)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) + expect(permissions(planner, issue_locked)).to be_disallowed(:reopen_issue) + expect(permissions(planner, new_issue)).to be_allowed(:create_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) + end + it 'allows reporters to read, update, reopen, and admin issues' do expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation) @@ -402,6 +424,11 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality) end + it 'allows planners to read, update, and admin confidential issues' do + expect(permissions(planner, confidential_issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation) + expect(permissions(planner, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality) + end + it 'allows reporters to read, update, and admin confidential issues' do expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation) expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_note, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality) @@ -463,6 +490,7 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do # with notes widget enabled, even guests can access notes expect(permissions(guest, issue)).to be_allowed(:create_note, :read_note) expect(permissions(guest, issue)).to be_disallowed(:read_internal_note, :mark_note_as_internal, :set_note_created_at, :admin_note) + expect(permissions(planner, issue)).to be_allowed(:create_note, :read_note, :read_internal_note, :mark_note_as_internal) expect(permissions(reporter, issue)).to be_allowed(:create_note, :read_note, :read_internal_note, :mark_note_as_internal) expect(permissions(reporter, issue)).to be_disallowed(:admin_note) expect(permissions(maintainer, issue)).to be_allowed(:create_note, :read_note, :read_internal_note, :mark_note_as_internal, :admin_note) @@ -483,10 +511,12 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do let_it_be(:non_member_user) { create(:user) } let_it_be(:guest) { create(:user, guest_of: [private_project, public_project]) } + let_it_be(:planner) { create(:user, planner_of: [private_project, public_project]) } let_it_be(:guest_author) { create(:user, guest_of: [private_project, public_project]) } let_it_be(:reporter) { create(:user, reporter_of: [private_project, public_project]) } let_it_be(:group_guest) { create(:user, guest_of: [private_group, public_group]) } + let_it_be(:group_planner) { create(:user, planner_of: [private_group, public_group]) } let_it_be(:group_guest_author) { create(:user, guest_of: [private_group, public_group]) } let_it_be(:group_reporter) { create(:user, reporter_of: [private_group, public_group]) } @@ -538,79 +568,81 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do let(:issue) { create(:issue, project: project) } let(:policies) { described_class.new(user, issue) } - context 'when project reporter' do - it 'is disallowed' do - project.add_reporter(user) - - expect(policies).to be_disallowed(:read_crm_contacts) - expect(policies).to be_disallowed(:set_issue_crm_contacts) - end - end - - context 'when subgroup reporter' do - it 'is allowed' do - subgroup.add_reporter(user) - - expect(policies).to be_disallowed(:read_crm_contacts) - expect(policies).to be_disallowed(:set_issue_crm_contacts) - end - end - - context 'when root group reporter' do - it 'is allowed' do - subgroup.parent.add_reporter(user) - - expect(policies).to be_allowed(:read_crm_contacts) - expect(policies).to be_allowed(:set_issue_crm_contacts) - end - end - - context 'when crm disabled on subgroup' do - let(:subgroup) { create(:group, :crm_disabled, parent: create(:group)) } - - it 'is disallowed' do - subgroup.parent.add_reporter(user) - - expect(policies).to be_disallowed(:read_crm_contacts) - expect(policies).to be_disallowed(:set_issue_crm_contacts) - end - end - - context 'when personal namespace' do - let(:project) { create(:project) } - - it 'is disallowed' do - project.add_reporter(user) - - expect(policies).to be_disallowed(:read_crm_contacts) - expect(policies).to be_disallowed(:set_issue_crm_contacts) - end - end - - context 'when custom crm_group configured' do - let_it_be(:crm_settings) { create(:crm_settings, source_group: create(:group)) } - let_it_be(:subgroup) { create(:group, parent: create(:group), crm_settings: crm_settings) } - let_it_be(:project) { create(:project, group: subgroup) } - - context 'when custom crm_group guest' do + %i[planner reporter].each do |role| + context "when project #{role}" do it 'is disallowed' do - subgroup.parent.add_reporter(user) - crm_settings.source_group.add_guest(user) + project.try(:"add_#{role}", user) expect(policies).to be_disallowed(:read_crm_contacts) expect(policies).to be_disallowed(:set_issue_crm_contacts) end end - context 'when custom crm_group reporter' do + context "when subgroup #{role}" do it 'is allowed' do - subgroup.parent.add_reporter(user) - crm_settings.source_group.add_reporter(user) + subgroup.try(:"add_#{role}", user) + + expect(policies).to be_disallowed(:read_crm_contacts) + expect(policies).to be_disallowed(:set_issue_crm_contacts) + end + end + + context "when root group #{role}" do + it 'is allowed' do + subgroup.parent.try(:"add_#{role}", user) expect(policies).to be_allowed(:read_crm_contacts) expect(policies).to be_allowed(:set_issue_crm_contacts) end end + + context 'when crm disabled on subgroup' do + let(:subgroup) { create(:group, :crm_disabled, parent: create(:group)) } + + it 'is disallowed' do + subgroup.parent.try(:"add_#{role}", user) + + expect(policies).to be_disallowed(:read_crm_contacts) + expect(policies).to be_disallowed(:set_issue_crm_contacts) + end + end + + context 'when personal namespace' do + let(:project) { create(:project) } + + it 'is disallowed' do + project.try(:"add_#{role}", user) + + expect(policies).to be_disallowed(:read_crm_contacts) + expect(policies).to be_disallowed(:set_issue_crm_contacts) + end + end + + context 'when custom crm_group configured' do + let_it_be(:crm_settings) { create(:crm_settings, source_group: create(:group)) } + let_it_be(:subgroup) { create(:group, parent: create(:group), crm_settings: crm_settings) } + let_it_be(:project) { create(:project, group: subgroup) } + + context 'when custom crm_group guest' do + it 'is disallowed' do + subgroup.parent.try(:"add_#{role}", user) + crm_settings.source_group.add_guest(user) + + expect(policies).to be_disallowed(:read_crm_contacts) + expect(policies).to be_disallowed(:set_issue_crm_contacts) + end + end + + context "when custom crm_group #{role}" do + it 'is allowed' do + subgroup.parent.try(:"add_#{role}", user) + crm_settings.source_group.try(:"add_#{role}", user) + + expect(policies).to be_allowed(:read_crm_contacts) + expect(policies).to be_allowed(:set_issue_crm_contacts) + end + end + end end end diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb index 12b38fe1d93..a5ad504c87d 100644 --- a/spec/policies/merge_request_policy_spec.rb +++ b/spec/policies/merge_request_policy_spec.rb @@ -4,9 +4,11 @@ require 'spec_helper' RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do include ExternalAuthorizationServiceHelpers + using RSpec::Parameterized::TableSyntax let_it_be(:guest) { create(:user) } let_it_be(:author) { create(:user) } + let_it_be(:planner) { create(:user) } let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } let_it_be(:non_team_member) { create(:user) } @@ -16,6 +18,34 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do described_class.new(user, merge_request) end + # :policy, :is_allowed + def permission_table_for_guest + :read_merge_request | true + :create_todo | true + :create_note | true + :update_subscription | true + :create_merge_request_in | true + :create_merge_request_from | false + :approve_merge_request | false + :update_merge_request | false + :reset_merge_request_approvals | false + :mark_note_as_internal | false + end + + # :policy, :is_allowed + def permission_table_for_reporter + :read_merge_request | true + :create_todo | true + :create_note | true + :update_subscription | true + :create_merge_request_in | true + :create_merge_request_from | false + :approve_merge_request | false + :update_merge_request | false + :reset_merge_request_approvals | false + :mark_note_as_internal | true + end + mr_perms = %i[create_merge_request_in create_merge_request_from read_merge_request @@ -36,19 +66,9 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do end end - shared_examples_for 'a user with reporter access' do - using RSpec::Parameterized::TableSyntax - + shared_examples_for 'a user with limited access' do where(:policy, :is_allowed) do - :create_merge_request_in | true - :read_merge_request | true - :create_todo | true - :create_note | true - :update_subscription | true - :create_merge_request_from | false - :approve_merge_request | false - :update_merge_request | false - :mark_note_as_internal | true + permission_table end with_them do @@ -74,6 +94,7 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do before do project.add_guest(guest) project.add_guest(author) + project.add_planner(planner) project.add_developer(developer) project.add_developer(bot) end @@ -105,6 +126,30 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do end end + context 'and the user is a planner' do + let(:user) { planner } + + it do + is_expected.to be_allowed(:update_merge_request) + end + + it do + is_expected.to be_allowed(:reopen_merge_request) + end + + it do + is_expected.to be_allowed(:approve_merge_request) + end + + it do + is_expected.to be_allowed(:mark_note_as_internal) + end + + it do + is_expected.to be_disallowed(:reset_merge_request_approvals) + end + end + context 'and the user is a bot' do let(:user) { bot } @@ -113,6 +158,46 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do end end end + + context 'and user is not author' do + let(:merge_request) do + create(:merge_request, source_project: project, target_project: project, author: author) + end + + describe 'a guest' do + let(:permission_table) { permission_table_for_guest } + + subject { permissions(guest, merge_request) } + + it_behaves_like 'a user with limited access' + end + + describe 'a planner' do + let(:permission_table) { permission_table_for_reporter } # same as reporter because MR is public + + subject { permissions(planner, merge_request) } + + it_behaves_like 'a user with limited access' + end + end + + context 'with private project' do + let_it_be(:project) { create(:project, :private) } + + describe 'a guest' do + subject { guest } + + it_behaves_like 'a denied user' + end + + describe 'a planner' do + let(:permission_table) { permission_table_for_reporter } + + subject { permissions(planner, merge_request) } + + it_behaves_like 'a user with limited access' + end + end end context 'when merge requests have been disabled' do @@ -134,6 +219,12 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do it_behaves_like 'a denied user' end + describe 'a planner' do + subject { planner } + + it_behaves_like 'a denied user' + end + describe 'a developer' do subject { developer } @@ -163,6 +254,12 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do it_behaves_like 'a denied user' end + describe 'a planner' do + subject { planner } + + it_behaves_like 'a denied user' + end + describe 'a developer' do subject { developer } @@ -189,6 +286,10 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do expect(permissions(developer, merge_request)).to be_allowed(:reopen_merge_request) end + it 'prevents planner from reopening merge request' do + expect(permissions(planner, merge_request)).to be_disallowed(:reopen_merge_request) + end + it 'prevents guest from reopening merge request' do expect(permissions(guest, merge_request)).to be_disallowed(:reopen_merge_request) end @@ -205,6 +306,10 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do expect(permissions(developer, merge_request_locked)).to be_disallowed(:reopen_merge_request) end + it 'prevents planners from reopening merge request' do + expect(permissions(planner, merge_request_locked)).to be_disallowed(:reopen_merge_request) + end + it 'prevents guests from reopening merge request' do expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request) end @@ -244,6 +349,7 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do before_all do group.add_guest(guest) group.add_guest(author) + group.add_planner(planner) group.add_reporter(reporter) group.add_developer(developer) group.add_developer(bot) @@ -276,6 +382,22 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do end end + describe 'a planner' do + let(:permission_table) { permission_table_for_reporter } + + subject { permissions(planner, merge_request) } + + it_behaves_like 'a user with limited access' + end + + describe 'a reporter' do + let(:permission_table) { permission_table_for_reporter } + + subject { permissions(reporter, merge_request) } + + it_behaves_like 'a user with limited access' + end + context 'and merge requests are private' do before do project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) @@ -288,10 +410,18 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do it_behaves_like 'a denied user' end + describe 'a planner' do + subject { planner } + + it_behaves_like 'a denied user' + end + describe 'a reporter' do + let(:permission_table) { permission_table_for_reporter } + subject { permissions(reporter, merge_request) } - it_behaves_like 'a user with reporter access' + it_behaves_like 'a user with limited access' end describe 'a developer' do @@ -319,10 +449,20 @@ RSpec.describe MergeRequestPolicy, feature_category: :code_review_workflow do it_behaves_like 'a denied user' end + describe 'a planner' do + let(:permission_table) { permission_table_for_reporter } + + subject { permissions(planner, merge_request) } + + it_behaves_like 'a user with limited access' + end + describe 'a reporter' do + let(:permission_table) { permission_table_for_reporter } + subject { permissions(reporter, merge_request) } - it_behaves_like 'a user with reporter access' + it_behaves_like 'a user with limited access' end describe 'a developer' do diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index dde138a862d..4b61a53b424 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -17,6 +17,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do before_all do project_with_runner_registration_token.add_guest(guest) + project_with_runner_registration_token.add_planner(planner) project_with_runner_registration_token.add_reporter(reporter) project_with_runner_registration_token.add_developer(developer) project_with_runner_registration_token.add_maintainer(maintainer) @@ -105,13 +106,18 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do expect_disallowed(*mr_permissions) end - context 'for a guest in a private project' do + context "for a guest in a private project" do let(:current_user) { guest } let(:project) { private_project } - it 'disallows the guest from all merge request permissions' do - expect_disallowed(*mr_permissions) - end + it { expect_disallowed(*mr_permissions) } + end + + context "for a planner in a private project" do + let(:current_user) { planner } + let(:project) { private_project } + + it { expect_disallowed(*(mr_permissions - [:read_merge_request, :create_merge_request_in])) } end end @@ -178,32 +184,42 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do context 'when project is public' do let(:project) { public_project } - context 'when the current_user is guest' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "when the current_user is #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:create_merge_request_in) } + it { is_expected.to be_allowed(:create_merge_request_in) } + end end end context 'when project is internal' do let(:project) { internal_project } - context 'when the current_user is guest' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "when the current_user is #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:create_merge_request_in) } + it { is_expected.to be_allowed(:create_merge_request_in) } + end end end context 'when project is private' do let(:project) { private_project } - context 'when the current_user is guest' do + context "when the current_user is guest" do let(:current_user) { guest } it { is_expected.not_to be_allowed(:create_merge_request_in) } end + context 'when the current_user is planner' do + let(:current_user) { planner } + + it { is_expected.to be_allowed(:create_merge_request_in) } + end + context 'when the current_user is reporter or above' do let(:current_user) { reporter } @@ -221,30 +237,36 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do context 'when project is public' do let(:project) { public_project } - context 'when the current_user is guest' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "when the current_user is #{role}" do + let(:current_user) { send(role) } - it { is_expected.not_to be_allowed(:create_merge_request_in) } + it { is_expected.not_to be_allowed(:create_merge_request_in) } + end end end context 'when project is internal' do let(:project) { internal_project } - context 'when the current_user is guest' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "when the current_user is #{role}" do + let(:current_user) { send(role) } - it { is_expected.not_to be_allowed(:create_merge_request_in) } + it { is_expected.not_to be_allowed(:create_merge_request_in) } + end end end context 'when project is private' do let(:project) { private_project } - context 'when the current_user is guest' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "when the current_user is #{role}" do + let(:current_user) { send(role) } - it { is_expected.not_to be_allowed(:create_merge_request_in) } + it { is_expected.not_to be_allowed(:create_merge_request_in) } + end end context 'when the current_user is reporter or above' do @@ -446,6 +468,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it_behaves_like 'project policies as anonymous' it_behaves_like 'project policies as guest' + it_behaves_like 'project policies as planner' it_behaves_like 'project policies as reporter' it_behaves_like 'project policies as developer' it_behaves_like 'project policies as maintainer' @@ -499,7 +522,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %w[guest reporter developer anonymous].each do |role| + %w[guest planner reporter developer anonymous].each do |role| context "with #{role}" do let(:current_user) { send(role) } @@ -521,7 +544,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end context 'importing work items' do - %w[reporter developer maintainer owner].each do |role| + %w[reporter planner developer maintainer owner].each do |role| context "with #{role}" do let(:current_user) { send(role) } @@ -559,7 +582,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %w[guest reporter developer anonymous].each do |role| + %w[guest planner reporter developer anonymous].each do |role| context "with #{role}" do let(:current_user) { send(role) } @@ -597,6 +620,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do before_all do project.add_guest(guest) + project.add_planner(planner) project.add_reporter(reporter) project.add_developer(developer) project.add_maintainer(maintainer) @@ -607,6 +631,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do expect(described_class.new(owner_of_different_thing, project)).to be_disallowed(:owner_access) expect(described_class.new(non_member, project)).to be_disallowed(:owner_access) expect(described_class.new(guest, project)).to be_disallowed(:owner_access) + expect(described_class.new(planner, project)).to be_disallowed(:owner_access) expect(described_class.new(reporter, project)).to be_disallowed(:owner_access) expect(described_class.new(developer, project)).to be_disallowed(:owner_access) expect(described_class.new(maintainer, project)).to be_disallowed(:owner_access) @@ -622,6 +647,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do context 'group members' do before_all do group.add_guest(guest) + group.add_planner(planner) group.add_reporter(reporter) group.add_developer(developer) group.add_maintainer(maintainer) @@ -633,6 +659,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do expect(described_class.new(owner_of_different_thing, project)).to be_disallowed(:owner_access) expect(described_class.new(non_member, project)).to be_disallowed(:owner_access) expect(described_class.new(guest, project)).to be_disallowed(:owner_access) + expect(described_class.new(planner, project)).to be_disallowed(:owner_access) expect(described_class.new(reporter, project)).to be_disallowed(:owner_access) expect(described_class.new(developer, project)).to be_disallowed(:owner_access) expect(described_class.new(maintainer, project)).to be_disallowed(:owner_access) @@ -648,6 +675,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do expect(described_class.new(owner, project)).to be_allowed(:read_incident_management_timeline_event_tag) expect(described_class.new(developer, project)).to be_allowed(:read_incident_management_timeline_event_tag) expect(described_class.new(guest, project)).to be_allowed(:read_incident_management_timeline_event_tag) + expect(described_class.new(planner, project)).to be_allowed(:read_incident_management_timeline_event_tag) expect(described_class.new(admin, project)).to be_allowed(:read_incident_management_timeline_event_tag) end end @@ -664,16 +692,18 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - context 'when user is a developer/guest/reporter' do + context 'when user is a developer/guest/planner/reporter' do it 'disallows creation' do expect(described_class.new(developer, project)).to be_disallowed(:admin_incident_management_timeline_event_tag) expect(described_class.new(guest, project)).to be_disallowed(:admin_incident_management_timeline_event_tag) + expect(described_class.new(planner, project)).to be_disallowed(:admin_incident_management_timeline_event_tag) expect(described_class.new(reporter, project)).to be_disallowed(:admin_incident_management_timeline_event_tag) end it 'disallows reading the import error' do expect(described_class.new(developer, project)).to be_disallowed(:read_import_error) expect(described_class.new(guest, project)).to be_disallowed(:read_import_error) + expect(described_class.new(planner, project)).to be_disallowed(:read_import_error) expect(described_class.new(reporter, project)).to be_disallowed(:read_import_error) end end @@ -764,10 +794,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do context 'project member' do let(:project) { private_project } - context 'guest' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context role do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:fork_project) } + it { is_expected.to be_disallowed(:fork_project) } + end end %w[reporter developer maintainer].each do |role| @@ -796,12 +828,15 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :role, :allowed) do :public | :anonymous | false :public | :guest | false + :public | :planner | false :public | :reporter | true :internal | :anonymous | false :internal | :guest | true + :internal | :planner | true :internal | :reporter | true :private | :anonymous | false :private | :guest | true + :private | :planner | true :private | :reporter | true end @@ -829,12 +864,15 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :role, :allowed) do :public | :anonymous | false :public | :guest | false + :public | :planner | false :public | :reporter | true :internal | :anonymous | false :internal | :guest | false + :internal | :planner | false :internal | :reporter | true :private | :anonymous | false :private | :guest | false + :private | :planner | false :private | :reporter | true end @@ -869,7 +907,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %w[guest reporter developer maintainer owner].each do |role| + %w[guest planner reporter developer maintainer owner].each do |role| context role do let(:current_user) { send(role) } @@ -897,7 +935,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %w[guest reporter developer maintainer owner].each do |role| + %w[guest planner reporter developer maintainer owner].each do |role| context role do let(:current_user) { send(role) } @@ -923,6 +961,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:user_role, :minimum_role, :allowed) do :guest | :developer | false + :planner | :developer | false :reporter | :developer | false :developer | :developer | false :maintainer | :developer | true @@ -961,48 +1000,56 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :maintainer | :no_one_allowed | true | false :owner | :no_one_allowed | true | false :guest | :no_one_allowed | true | false + :planner | :no_one_allowed | true | false :reporter | :no_one_allowed | true | false :anonymous | :no_one_allowed | true | false :developer | :developer | true | true :maintainer | :developer | true | true :owner | :developer | true | true :guest | :developer | true | false + :planner | :developer | true | false :reporter | :developer | true | false :anonymous | :developer | true | false :developer | :maintainer | true | false :maintainer | :maintainer | true | true :owner | :maintainer | true | true :guest | :maintainer | true | false + :planner | :maintainer | true | false :reporter | :maintainer | true | false :anonymous | :maintainer | true | false :developer | :owner | true | false :maintainer | :owner | true | false :owner | :owner | true | true :guest | :owner | true | false + :planner | :owner | true | false :reporter | :owner | true | false :anonymous | :owner | true | false :developer | :no_one_allowed | false | true :maintainer | :no_one_allowed | false | true :owner | :no_one_allowed | false | true :guest | :no_one_allowed | false | true + :planner | :no_one_allowed | false | true :reporter | :no_one_allowed | false | true :anonymous | :no_one_allowed | false | true :developer | :developer | false | true :maintainer | :developer | false | true :owner | :developer | false | true :guest | :developer | false | true + :planner | :developer | false | true :reporter | :developer | false | true :anonymous | :developer | false | true :developer | :maintainer | false | true :maintainer | :maintainer | false | true :owner | :maintainer | false | true :guest | :maintainer | false | true + :planner | :maintainer | false | true :reporter | :maintainer | false | true :anonymous | :maintainer | false | true :developer | :owner | false | true :maintainer | :owner | false | true :owner | :owner | false | true :guest | :owner | false | true + :planner | :owner | false | true :reporter | :owner | false | true :anonymous | :owner | false | true end @@ -1152,16 +1199,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_deployment) } end - context 'with guest' do - let(:current_user) { guest } + %w[anonymous guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:metrics_dashboard) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:metrics_dashboard) } + end end end @@ -1178,20 +1221,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_deployment) } end - context 'with guest' do - let(:current_user) { guest } + %w[anonymous guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } - it { is_expected.to be_allowed(:read_deployment) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } - it { is_expected.to be_allowed(:read_deployment) } + it { is_expected.to be_allowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_allowed(:read_deployment) } + end end end end @@ -1208,18 +1245,13 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_deployment) } end - context 'with guest' do - let(:current_user) { guest } + %w[anonymous guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } + end end end @@ -1236,12 +1268,14 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_deployment) } end - context 'with guest' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } - it { is_expected.to be_allowed(:read_deployment) } + it { is_expected.to be_allowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_allowed(:read_deployment) } + end end context 'with anonymous' do @@ -1265,18 +1299,13 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_deployment) } end - context 'with guest' do - let(:current_user) { guest } + %w[anonymous guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } + end end end @@ -1289,18 +1318,13 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_deployment) } end - context 'with guest' do - let(:current_user) { guest } + %w[anonymous guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_disallowed(:metrics_dashboard) } - it { is_expected.to be_disallowed(:read_prometheus) } + it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:read_prometheus) } + end end end end @@ -1310,22 +1334,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::DISABLED) end - context 'with reporter' do - let(:current_user) { reporter } + %w[anonymous guest planner reporter].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:metrics_dashboard) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_disallowed(:metrics_dashboard) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_disallowed(:metrics_dashboard) } + it { is_expected.to be_disallowed(:metrics_dashboard) } + end end end end @@ -1507,60 +1521,42 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:create_web_ide_terminal) } end - context 'with developer' do - let(:current_user) { developer } + %w[anonymous non_member guest planner reporter developer].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:create_web_ide_terminal) } - end - - context 'with reporter' do - let(:current_user) { reporter } - - it { is_expected.to be_disallowed(:create_web_ide_terminal) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_disallowed(:create_web_ide_terminal) } - end - - context 'with non member' do - let(:current_user) { non_member } - - it { is_expected.to be_disallowed(:create_web_ide_terminal) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_disallowed(:create_web_ide_terminal) } + it { is_expected.to be_disallowed(:create_web_ide_terminal) } + end end end describe 'read_repository_graphs' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - before do - allow(subject).to receive(:allowed?).with(:read_repository_graphs).and_call_original - allow(subject).to receive(:allowed?).with(:download_code).and_return(can_download_code) - end + before do + allow(subject).to receive(:allowed?).with(:read_repository_graphs).and_call_original + allow(subject).to receive(:allowed?).with(:download_code).and_return(can_download_code) + end - context 'when user can download_code' do - let(:can_download_code) { true } + context 'when user can download_code' do + let(:can_download_code) { true } - it { is_expected.to be_allowed(:read_repository_graphs) } - end + it { is_expected.to be_allowed(:read_repository_graphs) } + end - context 'when user cannot download_code' do - let(:can_download_code) { false } + context 'when user cannot download_code' do + let(:can_download_code) { false } - it { is_expected.to be_disallowed(:read_repository_graphs) } + it { is_expected.to be_disallowed(:read_repository_graphs) } + end + end end end context 'security configuration feature' do - %w[guest reporter].each do |role| + %w[guest planner reporter].each do |role| context role do let(:current_user) { send(role) } @@ -1582,7 +1578,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end context 'infrastructure google cloud feature' do - %w[guest reporter developer].each do |role| + %w[guest planner reporter developer].each do |role| context role do let(:current_user) { send(role) } @@ -1604,7 +1600,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end context 'infrastructure aws feature' do - %w[guest reporter developer].each do |role| + %w[guest planner reporter developer].each do |role| context role do let(:current_user) { send(role) } @@ -1631,8 +1627,8 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { reporter } let(:guest_design_abilities) { %i[read_design read_design_activity] } - let(:reporter_design_abilities) { %i[create_design destroy_design move_design update_design] } - let(:design_abilities) { guest_design_abilities + reporter_design_abilities } + let(:reporter_and_planner_design_abilities) { %i[create_design destroy_design move_design update_design] } + let(:design_abilities) { guest_design_abilities + reporter_and_planner_design_abilities } context 'when design management is not available' do before do @@ -1649,11 +1645,19 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(*design_abilities) } - context 'when user has below reporter access' do + %w[planner reporter].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } + + it { is_expected.to be_allowed(*design_abilities) } + end + end + + context 'when user is a guest' do let(:current_user) { guest } it { is_expected.to be_allowed(*guest_design_abilities) } - it { is_expected.not_to be_allowed(*reporter_design_abilities) } + it { is_expected.not_to be_allowed(*reporter_and_planner_design_abilities) } end end end @@ -1705,46 +1709,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it_behaves_like 'package access with repository disabled' end - context 'with owner' do - let(:current_user) { owner } + %w[anonymous non_member guest planner reporter developer maintainer owner].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:read_package) } - end - - context 'with maintainer' do - let(:current_user) { maintainer } - - it { is_expected.to be_allowed(:read_package) } - end - - context 'with developer' do - let(:current_user) { developer } - - it { is_expected.to be_allowed(:read_package) } - end - - context 'with reporter' do - let(:current_user) { reporter } - - it { is_expected.to be_allowed(:read_package) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_allowed(:read_package) } - end - - context 'with non member' do - let(:current_user) { non_member } - - it { is_expected.to be_allowed(:read_package) } - end - - context 'with anonymous' do - let(:current_user) { anonymous } - - it { is_expected.to be_allowed(:read_package) } + it { is_expected.to be_allowed(:read_package) } + end end end @@ -1769,7 +1739,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %i[developer reporter guest non_member anonymous].each do |role| + %i[developer reporter planner guest non_member anonymous].each do |role| context "with #{role}" do let(:current_user) { public_send(role) } @@ -1809,7 +1779,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %i[developer reporter guest non_member anonymous].each do |role| + %i[developer reporter planner guest non_member anonymous].each do |role| context "with #{role}" do let(:current_user) { public_send(role) } @@ -1835,7 +1805,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %i[owner maintainer developer reporter guest non_member anonymous].each do |role| + %i[owner maintainer developer reporter planner guest non_member anonymous].each do |role| context "with #{role}" do let(:current_user) { public_send(role) } @@ -1875,7 +1845,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %i[developer reporter guest non_member anonymous].each do |role| + %i[developer reporter planner guest non_member anonymous].each do |role| context "with #{role}" do let(:current_user) { public_send(role) } @@ -1901,7 +1871,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %i[owner maintainer developer reporter guest non_member anonymous].each do |role| + %i[owner maintainer developer reporter planner guest non_member anonymous].each do |role| context "with #{role}" do let(:current_user) { public_send(role) } @@ -1929,7 +1899,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %i[owner maintainer developer reporter guest non_member anonymous].each do |role| + %i[owner maintainer developer reporter planner guest non_member anonymous].each do |role| context "with #{role}" do let(:current_user) { public_send(role) } @@ -1996,6 +1966,10 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do project_with_analytics_private.add_guest(guest) project_with_analytics_enabled.add_guest(guest) + project_with_analytics_disabled.add_guest(planner) + project_with_analytics_private.add_guest(planner) + project_with_analytics_enabled.add_guest(planner) + project_with_analytics_disabled.add_reporter(reporter) project_with_analytics_private.add_reporter(reporter) project_with_analytics_enabled.add_reporter(reporter) @@ -2008,93 +1982,67 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do context 'when analytics is disabled for the project' do let(:project) { project_with_analytics_disabled } - context 'for guest user' do - let(:current_user) { guest } + %w[guest planner reporter developer].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:read_cycle_analytics) } - it { is_expected.to be_disallowed(:read_insights) } - it { is_expected.to be_disallowed(:read_repository_graphs) } - it { is_expected.to be_disallowed(:read_ci_cd_analytics) } - end - - context 'for reporter user' do - let(:current_user) { reporter } - - it { is_expected.to be_disallowed(:read_cycle_analytics) } - it { is_expected.to be_disallowed(:read_insights) } - it { is_expected.to be_disallowed(:read_repository_graphs) } - it { is_expected.to be_disallowed(:read_ci_cd_analytics) } - end - - context 'for developer' do - let(:current_user) { developer } - - it { is_expected.to be_disallowed(:read_cycle_analytics) } - it { is_expected.to be_disallowed(:read_insights) } - it { is_expected.to be_disallowed(:read_repository_graphs) } - it { is_expected.to be_disallowed(:read_ci_cd_analytics) } + it { is_expected.to be_disallowed(:read_cycle_analytics) } + it { is_expected.to be_disallowed(:read_insights) } + it { is_expected.to be_disallowed(:read_repository_graphs) } + it { is_expected.to be_disallowed(:read_ci_cd_analytics) } + end end end context 'when analytics is private for the project' do let(:project) { project_with_analytics_private } - context 'for guest user' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:read_cycle_analytics) } - it { is_expected.to be_allowed(:read_insights) } - it { is_expected.to be_disallowed(:read_repository_graphs) } - it { is_expected.to be_disallowed(:read_ci_cd_analytics) } + it { is_expected.to be_allowed(:read_cycle_analytics) } + it { is_expected.to be_allowed(:read_insights) } + it { is_expected.to be_disallowed(:read_repository_graphs) } + it { is_expected.to be_disallowed(:read_ci_cd_analytics) } + end end - context 'for reporter user' do - let(:current_user) { reporter } + %w[reporter developer].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:read_cycle_analytics) } - it { is_expected.to be_allowed(:read_insights) } - it { is_expected.to be_allowed(:read_repository_graphs) } - it { is_expected.to be_allowed(:read_ci_cd_analytics) } - end - - context 'for developer' do - let(:current_user) { developer } - - it { is_expected.to be_allowed(:read_cycle_analytics) } - it { is_expected.to be_allowed(:read_insights) } - it { is_expected.to be_allowed(:read_repository_graphs) } - it { is_expected.to be_allowed(:read_ci_cd_analytics) } + it { is_expected.to be_allowed(:read_cycle_analytics) } + it { is_expected.to be_allowed(:read_insights) } + it { is_expected.to be_allowed(:read_repository_graphs) } + it { is_expected.to be_allowed(:read_ci_cd_analytics) } + end end end context 'when analytics is enabled for the project' do let(:project) { project_with_analytics_enabled } - context 'for guest user' do - let(:current_user) { guest } + %w[guest planner].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:read_cycle_analytics) } - it { is_expected.to be_allowed(:read_insights) } - it { is_expected.to be_disallowed(:read_repository_graphs) } - it { is_expected.to be_disallowed(:read_ci_cd_analytics) } + it { is_expected.to be_allowed(:read_cycle_analytics) } + it { is_expected.to be_allowed(:read_insights) } + it { is_expected.to be_disallowed(:read_repository_graphs) } + it { is_expected.to be_disallowed(:read_ci_cd_analytics) } + end end - context 'for reporter user' do - let(:current_user) { reporter } + %w[reporter developer].each do |role| + context "for #{role} user" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:read_cycle_analytics) } - it { is_expected.to be_allowed(:read_insights) } - it { is_expected.to be_allowed(:read_repository_graphs) } - it { is_expected.to be_allowed(:read_ci_cd_analytics) } - end - - context 'for developer' do - let(:current_user) { developer } - - it { is_expected.to be_allowed(:read_cycle_analytics) } - it { is_expected.to be_allowed(:read_insights) } - it { is_expected.to be_allowed(:read_repository_graphs) } - it { is_expected.to be_allowed(:read_ci_cd_analytics) } + it { is_expected.to be_allowed(:read_cycle_analytics) } + it { is_expected.to be_allowed(:read_insights) } + it { is_expected.to be_allowed(:read_repository_graphs) } + it { is_expected.to be_allowed(:read_ci_cd_analytics) } + end end end end @@ -2102,7 +2050,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do context 'project member' do let(:project) { private_project } - %w[guest reporter developer maintainer].each do |role| + %w[guest planner reporter developer maintainer].each do |role| context role do let(:current_user) { send(role) } @@ -2131,13 +2079,13 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end context 'project member' do - %w[guest reporter developer maintainer].each do |role| + %w[guest planner reporter developer maintainer].each do |role| context role do before do project.add_member(current_user, role.to_sym) end - if role == 'guest' + if role == 'guest' || role == 'planner' it { is_expected.to be_disallowed(:read_ci_cd_analytics) } else it { is_expected.to be_allowed(:read_ci_cd_analytics) } @@ -2165,7 +2113,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end context 'project member' do - %w[guest reporter developer maintainer].each do |role| + %w[guest planner reporter developer maintainer].each do |role| context role do before do project.add_member(current_user, role.to_sym) @@ -2195,13 +2143,13 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do let(:current_user) { create(:user) } context 'project member' do - %w[guest reporter developer maintainer].each do |role| + %w[guest planner reporter developer maintainer].each do |role| context role do before do project.add_member(current_user, role.to_sym) end - if role == 'guest' + if role == 'guest' || role == 'planner' it { is_expected.to be_disallowed(:read_ci_cd_analytics) } else it { is_expected.to be_allowed(:read_ci_cd_analytics) } @@ -2244,38 +2192,47 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :access_level, :role, :allowed) do :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true + :public | ProjectFeature::ENABLED | :planner | true :public | ProjectFeature::ENABLED | :guest | true :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true + :public | ProjectFeature::PRIVATE | :planner | false :public | ProjectFeature::PRIVATE | :guest | false :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false + :public | ProjectFeature::DISABLED | :planner | false :public | ProjectFeature::DISABLED | :guest | false :public | ProjectFeature::DISABLED | :anonymous | false :internal | ProjectFeature::ENABLED | :maintainer | true :internal | ProjectFeature::ENABLED | :developer | true + :internal | ProjectFeature::ENABLED | :planner | true :internal | ProjectFeature::ENABLED | :guest | true :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true + :internal | ProjectFeature::PRIVATE | :planner | false :internal | ProjectFeature::PRIVATE | :guest | false :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false + :internal | ProjectFeature::DISABLED | :planner | false :internal | ProjectFeature::DISABLED | :guest | false :internal | ProjectFeature::DISABLED | :anonymous | false :private | ProjectFeature::ENABLED | :maintainer | true :private | ProjectFeature::ENABLED | :developer | true + :private | ProjectFeature::ENABLED | :planner | false :private | ProjectFeature::ENABLED | :guest | false :private | ProjectFeature::ENABLED | :anonymous | false :private | ProjectFeature::PRIVATE | :maintainer | true :private | ProjectFeature::PRIVATE | :developer | true + :private | ProjectFeature::PRIVATE | :planner | false :private | ProjectFeature::PRIVATE | :guest | false :private | ProjectFeature::PRIVATE | :anonymous | false :private | ProjectFeature::DISABLED | :maintainer | false :private | ProjectFeature::DISABLED | :developer | false + :private | ProjectFeature::DISABLED | :planner | false :private | ProjectFeature::DISABLED | :guest | false :private | ProjectFeature::DISABLED | :anonymous | false end @@ -2313,38 +2270,47 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :access_level, :role, :allowed) do :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true + :public | ProjectFeature::ENABLED | :planne | true :public | ProjectFeature::ENABLED | :guest | true :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true + :public | ProjectFeature::PRIVATE | :planne | true :public | ProjectFeature::PRIVATE | :guest | true :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false + :public | ProjectFeature::DISABLED | :planner | false :public | ProjectFeature::DISABLED | :guest | false :public | ProjectFeature::DISABLED | :anonymous | false :internal | ProjectFeature::ENABLED | :maintainer | true :internal | ProjectFeature::ENABLED | :developer | true + :internal | ProjectFeature::ENABLED | :planner | true :internal | ProjectFeature::ENABLED | :guest | true :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true + :internal | ProjectFeature::PRIVATE | :planner | true :internal | ProjectFeature::PRIVATE | :guest | true :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false + :internal | ProjectFeature::DISABLED | :planner | false :internal | ProjectFeature::DISABLED | :guest | false :internal | ProjectFeature::DISABLED | :anonymous | false :private | ProjectFeature::ENABLED | :maintainer | true :private | ProjectFeature::ENABLED | :developer | true + :private | ProjectFeature::ENABLED | :planner | false :private | ProjectFeature::ENABLED | :guest | false :private | ProjectFeature::ENABLED | :anonymous | false :private | ProjectFeature::PRIVATE | :maintainer | true :private | ProjectFeature::PRIVATE | :developer | true + :private | ProjectFeature::PRIVATE | :planner | false :private | ProjectFeature::PRIVATE | :guest | false :private | ProjectFeature::PRIVATE | :anonymous | false :private | ProjectFeature::DISABLED | :maintainer | false :private | ProjectFeature::DISABLED | :developer | false + :private | ProjectFeature::DISABLED | :planner | false :private | ProjectFeature::DISABLED | :guest | false :private | ProjectFeature::DISABLED | :anonymous | false end @@ -2384,38 +2350,47 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :access_level, :role, :allowed) do :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true + :public | ProjectFeature::ENABLED | :planner | true :public | ProjectFeature::ENABLED | :guest | true :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true + :public | ProjectFeature::PRIVATE | :planner | true :public | ProjectFeature::PRIVATE | :guest | true :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false + :public | ProjectFeature::DISABLED | :planner | false :public | ProjectFeature::DISABLED | :guest | false :public | ProjectFeature::DISABLED | :anonymous | false :internal | ProjectFeature::ENABLED | :maintainer | true :internal | ProjectFeature::ENABLED | :developer | true + :internal | ProjectFeature::ENABLED | :planner | true :internal | ProjectFeature::ENABLED | :guest | true :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true + :internal | ProjectFeature::PRIVATE | :planner | true :internal | ProjectFeature::PRIVATE | :guest | true :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false + :internal | ProjectFeature::DISABLED | :planner | false :internal | ProjectFeature::DISABLED | :guest | false :internal | ProjectFeature::DISABLED | :anonymous | false :private | ProjectFeature::ENABLED | :maintainer | true :private | ProjectFeature::ENABLED | :developer | true + :private | ProjectFeature::ENABLED | :planner | false :private | ProjectFeature::ENABLED | :guest | false :private | ProjectFeature::ENABLED | :anonymous | false :private | ProjectFeature::PRIVATE | :maintainer | true :private | ProjectFeature::PRIVATE | :developer | true + :private | ProjectFeature::PRIVATE | :planner | false :private | ProjectFeature::PRIVATE | :guest | false :private | ProjectFeature::PRIVATE | :anonymous | false :private | ProjectFeature::DISABLED | :maintainer | false :private | ProjectFeature::DISABLED | :developer | false + :private | ProjectFeature::DISABLED | :planner | false :private | ProjectFeature::DISABLED | :guest | false :private | ProjectFeature::DISABLED | :anonymous | false end @@ -2452,38 +2427,47 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :access_level, :role, :allowed) do :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true + :public | ProjectFeature::ENABLED | :planner | true :public | ProjectFeature::ENABLED | :guest | true :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true + :public | ProjectFeature::PRIVATE | :planner | true :public | ProjectFeature::PRIVATE | :guest | true :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false + :public | ProjectFeature::DISABLED | :planner | false :public | ProjectFeature::DISABLED | :guest | false :public | ProjectFeature::DISABLED | :anonymous | false :internal | ProjectFeature::ENABLED | :maintainer | true :internal | ProjectFeature::ENABLED | :developer | true + :internal | ProjectFeature::ENABLED | :planner | true :internal | ProjectFeature::ENABLED | :guest | true :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true + :internal | ProjectFeature::PRIVATE | :planner | true :internal | ProjectFeature::PRIVATE | :guest | true :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false + :internal | ProjectFeature::DISABLED | :planner | false :internal | ProjectFeature::DISABLED | :guest | false :internal | ProjectFeature::DISABLED | :anonymous | false :private | ProjectFeature::ENABLED | :maintainer | true :private | ProjectFeature::ENABLED | :developer | true + :private | ProjectFeature::ENABLED | :planner | true :private | ProjectFeature::ENABLED | :guest | true :private | ProjectFeature::ENABLED | :anonymous | false :private | ProjectFeature::PRIVATE | :maintainer | true :private | ProjectFeature::PRIVATE | :developer | true + :private | ProjectFeature::PRIVATE | :planner | true :private | ProjectFeature::PRIVATE | :guest | true :private | ProjectFeature::PRIVATE | :anonymous | false :private | ProjectFeature::DISABLED | :maintainer | false :private | ProjectFeature::DISABLED | :developer | false + :private | ProjectFeature::DISABLED | :planner | false :private | ProjectFeature::DISABLED | :guest | false :private | ProjectFeature::DISABLED | :anonymous | false end @@ -2512,6 +2496,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :maintainer | true :developer | true :reporter | false + :planner | false :guest | false end @@ -2547,38 +2532,47 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :access_level, :role, :allowed) do :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true + :public | ProjectFeature::ENABLED | :planner | true :public | ProjectFeature::ENABLED | :guest | true :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true + :public | ProjectFeature::PRIVATE | :planner | true :public | ProjectFeature::PRIVATE | :guest | true :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false + :public | ProjectFeature::DISABLED | :planner | false :public | ProjectFeature::DISABLED | :guest | false :public | ProjectFeature::DISABLED | :anonymous | false :internal | ProjectFeature::ENABLED | :maintainer | true :internal | ProjectFeature::ENABLED | :developer | true + :internal | ProjectFeature::ENABLED | :planner | true :internal | ProjectFeature::ENABLED | :guest | true :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true + :internal | ProjectFeature::PRIVATE | :planner | true :internal | ProjectFeature::PRIVATE | :guest | true :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false + :internal | ProjectFeature::DISABLED | :planner | false :internal | ProjectFeature::DISABLED | :guest | false :internal | ProjectFeature::DISABLED | :anonymous | false :private | ProjectFeature::ENABLED | :maintainer | true :private | ProjectFeature::ENABLED | :developer | true + :private | ProjectFeature::ENABLED | :planner | true :private | ProjectFeature::ENABLED | :guest | true :private | ProjectFeature::ENABLED | :anonymous | false :private | ProjectFeature::PRIVATE | :maintainer | true :private | ProjectFeature::PRIVATE | :developer | true + :private | ProjectFeature::PRIVATE | :planner | true :private | ProjectFeature::PRIVATE | :guest | true :private | ProjectFeature::PRIVATE | :anonymous | false :private | ProjectFeature::DISABLED | :maintainer | false :private | ProjectFeature::DISABLED | :developer | false + :private | ProjectFeature::DISABLED | :planner | false :private | ProjectFeature::DISABLED | :guest | false :private | ProjectFeature::DISABLED | :anonymous | false end @@ -2655,7 +2649,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %w[reporter guest].each do |role| + %w[reporter planner guest].each do |role| context "when the role is #{role}" do let(:current_user) { public_send(role) } @@ -2681,7 +2675,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do project.project_feature.update!(security_and_compliance_access_level: Featurable::DISABLED) end - %w[owner maintainer developer reporter guest].each do |role| + %w[owner maintainer developer reporter planner guest].each do |role| context "when the role is #{role}" do let(:current_user) { public_send(role) } @@ -2762,6 +2756,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :reporter | false | :different | true | false :reporter | true | :different | true | false :reporter | false | :different | false | true + :planner | false | :same | true | true + :planner | true | :same | true | true + :planner | false | :same | false | true + :planner | false | :different | true | false + :planner | true | :different | true | false + :planner | false | :different | false | true :guest | false | :same | true | true :guest | true | :same | true | true :guest | false | :same | false | true @@ -2821,72 +2821,84 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:project_visibility, :external_user, :token_scope_enabled, :role, :allowed) do :private | false | false | :anonymous | false + :private | false | false | :planner | true :private | false | false | :guest | true :private | false | false | :reporter | true :private | false | false | :developer | true :private | false | false | :maintainer | true :private | false | false | :owner | true :public | false | false | :anonymous | false + :public | false | false | :planner | true :public | false | false | :guest | true :public | false | false | :reporter | true :public | false | false | :developer | true :public | false | false | :maintainer | true :public | false | false | :owner | true :internal | false | false | :anonymous | false + :internal | false | false | :planner | true :internal | false | false | :guest | true :internal | false | false | :reporter | true :internal | false | false | :developer | true :internal | false | false | :maintainer | true :internal | false | false | :owner | true :private | true | false | :anonymous | false + :private | true | false | :planner | false :private | true | false | :guest | false :private | true | false | :reporter | false :private | true | false | :developer | false :private | true | false | :maintainer | false :private | true | false | :owner | false :public | true | false | :anonymous | false + :public | true | false | :planner | false :public | true | false | :guest | false :public | true | false | :reporter | false :public | true | false | :developer | false :public | true | false | :maintainer | false :public | true | false | :owner | false :internal | true | false | :anonymous | false + :internal | true | false | :planner | false :internal | true | false | :guest | false :internal | true | false | :reporter | false :internal | true | false | :developer | false :internal | true | false | :maintainer | false :internal | true | false | :owner | false :private | false | true | :anonymous | false + :private | false | true | :planner | true :private | false | true | :guest | true :private | false | true | :reporter | true :private | false | true | :developer | true :private | false | true | :maintainer | true :private | false | true | :owner | true :public | false | true | :anonymous | false + :public | false | true | :planner | true :public | false | true | :guest | true :public | false | true | :reporter | true :public | false | true | :developer | true :public | false | true | :maintainer | true :public | false | true | :owner | true :internal | false | true | :anonymous | false + :internal | false | true | :planner | true :internal | false | true | :guest | true :internal | false | true | :reporter | true :internal | false | true | :developer | true :internal | false | true | :maintainer | true :internal | false | true | :owner | true :private | true | true | :anonymous | false + :private | true | true | :planner | false :private | true | true | :guest | false :private | true | true | :reporter | false :private | true | true | :developer | false :private | true | true | :maintainer | false :private | true | true | :owner | false :public | true | true | :anonymous | false + :public | true | true | :planner | false :public | true | true | :guest | false :public | true | true | :reporter | false :public | true | true | :developer | false :public | true | true | :maintainer | false :public | true | true | :owner | false :internal | true | true | :anonymous | false + :internal | true | true | :planner | false :internal | true | true | :guest | false :internal | true | true | :reporter | false :internal | true | true | :developer | false @@ -2956,6 +2968,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true :public | ProjectFeature::ENABLED | :reporter | true + :public | ProjectFeature::ENABLED | :planner | true :public | ProjectFeature::ENABLED | :guest | true :public | ProjectFeature::ENABLED | :anonymous | true :public | ProjectFeature::PRIVATE | :admin | true @@ -2963,6 +2976,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true :public | ProjectFeature::PRIVATE | :reporter | true + :public | ProjectFeature::PRIVATE | :planner | false :public | ProjectFeature::PRIVATE | :guest | false :public | ProjectFeature::PRIVATE | :anonymous | false :public | ProjectFeature::DISABLED | :admin | false @@ -2970,6 +2984,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false :public | ProjectFeature::DISABLED | :reporter | false + :public | ProjectFeature::DISABLED | :planner | false :public | ProjectFeature::DISABLED | :guest | false :public | ProjectFeature::DISABLED | :anonymous | false :internal | ProjectFeature::ENABLED | :admin | true @@ -2977,6 +2992,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :internal | ProjectFeature::ENABLED | :maintainer | true :internal | ProjectFeature::ENABLED | :developer | true :internal | ProjectFeature::ENABLED | :reporter | true + :internal | ProjectFeature::ENABLED | :planner | true :internal | ProjectFeature::ENABLED | :guest | true :internal | ProjectFeature::ENABLED | :anonymous | false :internal | ProjectFeature::PRIVATE | :admin | true @@ -2984,6 +3000,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true :internal | ProjectFeature::PRIVATE | :reporter | true + :internal | ProjectFeature::PRIVATE | :planner | false :internal | ProjectFeature::PRIVATE | :guest | false :internal | ProjectFeature::PRIVATE | :anonymous | false :internal | ProjectFeature::DISABLED | :admin | false @@ -2991,6 +3008,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false :internal | ProjectFeature::DISABLED | :reporter | false + :internal | ProjectFeature::DISABLED | :planner | false :internal | ProjectFeature::DISABLED | :guest | false :internal | ProjectFeature::DISABLED | :anonymous | false :private | ProjectFeature::ENABLED | :admin | true @@ -2998,6 +3016,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :private | ProjectFeature::ENABLED | :maintainer | true :private | ProjectFeature::ENABLED | :developer | true :private | ProjectFeature::ENABLED | :reporter | true + :private | ProjectFeature::ENABLED | :planner | false :private | ProjectFeature::ENABLED | :guest | false :private | ProjectFeature::ENABLED | :anonymous | false :private | ProjectFeature::PRIVATE | :admin | true @@ -3005,6 +3024,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :private | ProjectFeature::PRIVATE | :maintainer | true :private | ProjectFeature::PRIVATE | :developer | true :private | ProjectFeature::PRIVATE | :reporter | true + :private | ProjectFeature::PRIVATE | :planner | false :private | ProjectFeature::PRIVATE | :guest | false :private | ProjectFeature::PRIVATE | :anonymous | false :private | ProjectFeature::DISABLED | :admin | false @@ -3012,6 +3032,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :private | ProjectFeature::DISABLED | :maintainer | false :private | ProjectFeature::DISABLED | :developer | false :private | ProjectFeature::DISABLED | :reporter | false + :private | ProjectFeature::DISABLED | :planner | false :private | ProjectFeature::DISABLED | :guest | false :private | ProjectFeature::DISABLED | :anonymous | false end @@ -3022,6 +3043,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do before do enable_admin_mode!(admin) if role == :admin + allow(current_user).to receive(:external?).and_return(false) project.project_feature.update!(container_registry_access_level: access_level) end @@ -3043,23 +3065,35 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - context 'with external guest users' do - where(:project_visibility, :access_level, :allowed) do - :public | ProjectFeature::ENABLED | true - :public | ProjectFeature::PRIVATE | false - :public | ProjectFeature::DISABLED | false + context 'with external guest and planner users' do + where(:project_visibility, :access_level, :role, :allowed) do + :public | ProjectFeature::ENABLED | :guest | true + :public | ProjectFeature::PRIVATE | :guest | false + :public | ProjectFeature::DISABLED | :guest | false - :internal | ProjectFeature::ENABLED | true - :internal | ProjectFeature::PRIVATE | false - :internal | ProjectFeature::DISABLED | false + :internal | ProjectFeature::ENABLED | :guest | true + :internal | ProjectFeature::PRIVATE | :guest | false + :internal | ProjectFeature::DISABLED | :guest | false - :private | ProjectFeature::ENABLED | false - :private | ProjectFeature::PRIVATE | false - :private | ProjectFeature::DISABLED | false + :private | ProjectFeature::ENABLED | :guest | false + :private | ProjectFeature::PRIVATE | :guest | false + :private | ProjectFeature::DISABLED | :guest | false + + :public | ProjectFeature::ENABLED | :planner | true + :public | ProjectFeature::PRIVATE | :planner | false + :public | ProjectFeature::DISABLED | :planner | false + + :internal | ProjectFeature::ENABLED | :planner | true + :internal | ProjectFeature::PRIVATE | :planner | false + :internal | ProjectFeature::DISABLED | :planner | false + + :private | ProjectFeature::ENABLED | :planner | false + :private | ProjectFeature::PRIVATE | :planner | false + :private | ProjectFeature::DISABLED | :planner | false end with_them do - let(:current_user) { guest } + let(:current_user) { send(role) } let(:project) { send("#{project_visibility}_project") } before do @@ -3091,7 +3125,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do maintainer_operations_permissions when :developer developer_operations_permissions - when :reporter, :guest + when :reporter, :guest, :planner guest_operations_permissions when :anonymous anonymous_operations_permissions @@ -3134,7 +3168,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - %w[guest reporter developer].each do |role| + %w[guest planner reporter developer].each do |role| context role do let(:current_user) { send(role) } @@ -3240,28 +3274,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end end - context 'with reporter' do - let(:current_user) { reporter } + %w[anonymous non_member guest planner reporter developer].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:register_project_runners) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_disallowed(:register_project_runners) } - end - - context 'with non member' do - let(:current_user) { create(:user) } - - it { is_expected.to be_disallowed(:register_project_runners) } - end - - context 'with anonymous' do - let(:current_user) { nil } - - it { is_expected.to be_disallowed(:register_project_runners) } + it { is_expected.to be_disallowed(:register_project_runners) } + end end end @@ -3322,28 +3340,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:create_runner) } end - context 'with reporter' do - let(:current_user) { reporter } + %w[anonymous guest planner reporter developer].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:create_runner) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_disallowed(:create_runner) } - end - - context 'with developer' do - let(:current_user) { developer } - - it { is_expected.to be_disallowed(:create_runner) } - end - - context 'with anonymous' do - let(:current_user) { nil } - - it { is_expected.to be_disallowed(:create_runner) } + it { is_expected.to be_disallowed(:create_runner) } + end end end @@ -3372,28 +3374,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:create_runner) } end - context 'with reporter' do - let(:current_user) { reporter } + %w[anonymous guest planner reporter developer].each do |role| + context "with #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_disallowed(:create_runner) } - end - - context 'with guest' do - let(:current_user) { guest } - - it { is_expected.to be_disallowed(:create_runner) } - end - - context 'with developer' do - let(:current_user) { developer } - - it { is_expected.to be_disallowed(:create_runner) } - end - - context 'with anonymous' do - let(:current_user) { nil } - - it { is_expected.to be_disallowed(:create_runner) } + it { is_expected.to be_disallowed(:create_runner) } + end end end @@ -3412,16 +3398,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do it { is_expected.to be_allowed(:read_project_runners) } end - context 'with reporter' do - let(:user) { reporter } + %w[non_member guest planner reporter].each do |role| + context "with #{role}" do + let(:user) { send(role) } - it { is_expected.to be_disallowed(:read_project_runners) } - end - - context 'when the user is not part of the project' do - let(:user) { non_member } - - it { is_expected.to be_disallowed(:read_project_runners) } + it { is_expected.to be_disallowed(:read_project_runners) } + end end end @@ -3433,6 +3415,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :maintainer | true :developer | true :reporter | false + :planner | false :guest | false end @@ -3459,22 +3442,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do end context 'when user is an inherited member from the group' do - context 'and user is a guest' do - let(:current_user) { inherited_guest } + %w[guest planner reporter developer].each do |role| + context "and user is a #{role}" do + let(:current_user) { send(role) } - it { is_expected.to be_allowed(:read_milestone) } - end - - context 'and user is a reporter' do - let(:current_user) { inherited_reporter } - - it { is_expected.to be_allowed(:read_milestone) } - end - - context 'and user is a developer' do - let(:current_user) { inherited_developer } - - it { is_expected.to be_allowed(:read_milestone) } + it { is_expected.to be_allowed(:read_milestone) } + end end end end @@ -3495,6 +3468,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :maintainer | true :developer | true :reporter | true + :planner | true :guest | true with_them do @@ -3513,6 +3487,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :maintainer | true :developer | true :reporter | true + :planner | true :guest | false end @@ -3562,6 +3537,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :maintainer | false :developer | false :reporter | false + :planner | false :guest | false end @@ -3578,21 +3554,25 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do where(:ability, :current_user, :access_level, :allowed) do :admin_pages | ref(:maintainer) | Featurable::ENABLED | true :admin_pages | ref(:reporter) | Featurable::ENABLED | false + :admin_pages | ref(:planner) | Featurable::ENABLED | false :admin_pages | ref(:guest) | Featurable::ENABLED | false :admin_pages | ref(:non_member) | Featurable::ENABLED | false :update_pages | ref(:maintainer) | Featurable::ENABLED | true :update_pages | ref(:reporter) | Featurable::ENABLED | false + :update_pages | ref(:planner) | Featurable::ENABLED | false :update_pages | ref(:guest) | Featurable::ENABLED | false :update_pages | ref(:non_member) | Featurable::ENABLED | false :remove_pages | ref(:maintainer) | Featurable::ENABLED | true :remove_pages | ref(:reporter) | Featurable::ENABLED | false + :remove_pages | ref(:planner) | Featurable::ENABLED | false :remove_pages | ref(:guest) | Featurable::ENABLED | false :remove_pages | ref(:non_member) | Featurable::ENABLED | false :read_pages | ref(:maintainer) | Featurable::ENABLED | true :read_pages | ref(:reporter) | Featurable::ENABLED | false + :read_pages | ref(:planner) | Featurable::ENABLED | false :read_pages | ref(:guest) | Featurable::ENABLED | false :read_pages | ref(:non_member) | Featurable::ENABLED | false @@ -3600,6 +3580,9 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :read_pages_content | ref(:reporter) | Featurable::ENABLED | true :read_pages_content | ref(:reporter) | Featurable::PRIVATE | true :read_pages_content | ref(:reporter) | Featurable::DISABLED | false + :read_pages_content | ref(:planner) | Featurable::ENABLED | true + :read_pages_content | ref(:planner) | Featurable::PRIVATE | true + :read_pages_content | ref(:planner) | Featurable::DISABLED | false :read_pages_content | ref(:guest) | Featurable::ENABLED | true :read_pages_content | ref(:guest) | Featurable::PRIVATE | true :read_pages_content | ref(:guest) | Featurable::DISABLED | false @@ -3628,6 +3611,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::DISABLED | ref(:anonymous) | false Featurable::DISABLED | ref(:non_member) | false Featurable::DISABLED | ref(:guest) | false + Featurable::DISABLED | ref(:planner) | false Featurable::DISABLED | ref(:reporter) | false Featurable::DISABLED | ref(:developer) | false Featurable::DISABLED | ref(:maintainer) | false @@ -3635,6 +3619,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::ENABLED | ref(:anonymous) | true Featurable::ENABLED | ref(:non_member) | true Featurable::ENABLED | ref(:guest) | true + Featurable::ENABLED | ref(:planner) | true Featurable::ENABLED | ref(:reporter) | true Featurable::ENABLED | ref(:developer) | true Featurable::ENABLED | ref(:maintainer) | true @@ -3642,6 +3627,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::PRIVATE | ref(:anonymous) | false Featurable::PRIVATE | ref(:non_member) | false Featurable::PRIVATE | ref(:guest) | true + Featurable::PRIVATE | ref(:planner) | true Featurable::PRIVATE | ref(:reporter) | true Featurable::PRIVATE | ref(:developer) | true Featurable::PRIVATE | ref(:maintainer) | true @@ -3669,6 +3655,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::DISABLED | ref(:anonymous) | false Featurable::DISABLED | ref(:non_member) | false Featurable::DISABLED | ref(:guest) | false + Featurable::DISABLED | ref(:planner) | false Featurable::DISABLED | ref(:reporter) | false Featurable::DISABLED | ref(:developer) | false Featurable::DISABLED | ref(:maintainer) | false @@ -3676,6 +3663,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::ENABLED | ref(:anonymous) | false Featurable::ENABLED | ref(:non_member) | false Featurable::ENABLED | ref(:guest) | true + Featurable::ENABLED | ref(:planner) | true Featurable::ENABLED | ref(:reporter) | true Featurable::ENABLED | ref(:developer) | true Featurable::ENABLED | ref(:maintainer) | true @@ -3683,6 +3671,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::PRIVATE | ref(:anonymous) | false Featurable::PRIVATE | ref(:non_member) | false Featurable::PRIVATE | ref(:guest) | true + Featurable::PRIVATE | ref(:planner) | true Featurable::PRIVATE | ref(:reporter) | true Featurable::PRIVATE | ref(:developer) | true Featurable::PRIVATE | ref(:maintainer) | true @@ -3710,6 +3699,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::DISABLED | ref(:anonymous) | false Featurable::DISABLED | ref(:non_member) | false Featurable::DISABLED | ref(:guest) | false + Featurable::DISABLED | ref(:planner) | false Featurable::DISABLED | ref(:reporter) | false Featurable::DISABLED | ref(:developer) | false Featurable::DISABLED | ref(:maintainer) | false @@ -3717,6 +3707,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::ENABLED | ref(:anonymous) | false Featurable::ENABLED | ref(:non_member) | false Featurable::ENABLED | ref(:guest) | true + Featurable::ENABLED | ref(:planner) | true Featurable::ENABLED | ref(:reporter) | true Featurable::ENABLED | ref(:developer) | true Featurable::ENABLED | ref(:maintainer) | true @@ -3724,6 +3715,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::PRIVATE | ref(:anonymous) | false Featurable::PRIVATE | ref(:non_member) | false Featurable::PRIVATE | ref(:guest) | true + Featurable::PRIVATE | ref(:planner) | true Featurable::PRIVATE | ref(:reporter) | true Featurable::PRIVATE | ref(:developer) | true Featurable::PRIVATE | ref(:maintainer) | true @@ -3756,6 +3748,9 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do ref(:guest) | Featurable::ENABLED | false ref(:guest) | Featurable::PRIVATE | false ref(:guest) | Featurable::DISABLED | false + ref(:planner) | Featurable::ENABLED | false + ref(:planner) | Featurable::PRIVATE | false + ref(:planner) | Featurable::DISABLED | false ref(:reporter) | Featurable::ENABLED | false ref(:reporter) | Featurable::PRIVATE | false ref(:reporter) | Featurable::DISABLED | false @@ -3790,6 +3785,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::DISABLED | ref(:anonymous) | false Featurable::DISABLED | ref(:non_member) | false Featurable::DISABLED | ref(:guest) | false + Featurable::DISABLED | ref(:planner) | false Featurable::DISABLED | ref(:reporter) | false Featurable::DISABLED | ref(:developer) | false Featurable::DISABLED | ref(:maintainer) | false @@ -3797,6 +3793,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::ENABLED | ref(:anonymous) | true Featurable::ENABLED | ref(:non_member) | true Featurable::ENABLED | ref(:guest) | true + Featurable::ENABLED | ref(:planner) | true Featurable::ENABLED | ref(:reporter) | true Featurable::ENABLED | ref(:developer) | true Featurable::ENABLED | ref(:maintainer) | true @@ -3804,6 +3801,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::PRIVATE | ref(:anonymous) | false Featurable::PRIVATE | ref(:non_member) | false Featurable::PRIVATE | ref(:guest) | true + Featurable::PRIVATE | ref(:planner) | true Featurable::PRIVATE | ref(:reporter) | true Featurable::PRIVATE | ref(:developer) | true Featurable::PRIVATE | ref(:maintainer) | true @@ -3831,6 +3829,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::DISABLED | ref(:anonymous) | false Featurable::DISABLED | ref(:non_member) | false Featurable::DISABLED | ref(:guest) | false + Featurable::DISABLED | ref(:planner) | false Featurable::DISABLED | ref(:reporter) | false Featurable::DISABLED | ref(:developer) | false Featurable::DISABLED | ref(:maintainer) | false @@ -3838,6 +3837,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::ENABLED | ref(:anonymous) | false Featurable::ENABLED | ref(:non_member) | false Featurable::ENABLED | ref(:guest) | true + Featurable::ENABLED | ref(:planner) | true Featurable::ENABLED | ref(:reporter) | true Featurable::ENABLED | ref(:developer) | true Featurable::ENABLED | ref(:maintainer) | true @@ -3845,6 +3845,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::PRIVATE | ref(:anonymous) | false Featurable::PRIVATE | ref(:non_member) | false Featurable::PRIVATE | ref(:guest) | true + Featurable::PRIVATE | ref(:planner) | true Featurable::PRIVATE | ref(:reporter) | true Featurable::PRIVATE | ref(:developer) | true Featurable::PRIVATE | ref(:maintainer) | true @@ -3872,6 +3873,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::DISABLED | ref(:anonymous) | false Featurable::DISABLED | ref(:non_member) | false Featurable::DISABLED | ref(:guest) | false + Featurable::DISABLED | ref(:planner) | false Featurable::DISABLED | ref(:reporter) | false Featurable::DISABLED | ref(:developer) | false Featurable::DISABLED | ref(:maintainer) | false @@ -3879,6 +3881,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::ENABLED | ref(:anonymous) | false Featurable::ENABLED | ref(:non_member) | false Featurable::ENABLED | ref(:guest) | true + Featurable::ENABLED | ref(:planner) | true Featurable::ENABLED | ref(:reporter) | true Featurable::ENABLED | ref(:developer) | true Featurable::ENABLED | ref(:maintainer) | true @@ -3886,6 +3889,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do Featurable::PRIVATE | ref(:anonymous) | false Featurable::PRIVATE | ref(:non_member) | false Featurable::PRIVATE | ref(:guest) | true + Featurable::PRIVATE | ref(:planner) | true Featurable::PRIVATE | ref(:reporter) | true Featurable::PRIVATE | ref(:developer) | true Featurable::PRIVATE | ref(:maintainer) | true @@ -3919,6 +3923,9 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do true | ref(:guest) | Featurable::ENABLED | false true | ref(:guest) | Featurable::PRIVATE | false true | ref(:guest) | Featurable::DISABLED | false + true | ref(:planner) | Featurable::ENABLED | false + true | ref(:planner) | Featurable::PRIVATE | false + true | ref(:planner) | Featurable::DISABLED | false true | ref(:reporter) | Featurable::ENABLED | false true | ref(:reporter) | Featurable::PRIVATE | false true | ref(:reporter) | Featurable::DISABLED | false @@ -3997,6 +4004,9 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do :maintainer | :private | true | true | true | false :developer | :public | true | true | true | false :reporter | :public | true | true | false | false + :planner | :public | true | true | false | false + :planner | :private | true | true | false | false + :planner | :internal | true | true | false | false :guest | :public | true | true | false | false :guest | :private | true | true | false | false :guest | :internal | true | true | false | false @@ -4025,6 +4035,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do stub_feature_flags(allow_push_repository_for_job_token: false) if ff_disabled project.add_guest(guest) + project.add_planner(planner) project.add_reporter(reporter) project.add_developer(developer) project.add_maintainer(maintainer) @@ -4077,6 +4088,8 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do developer when :guest guest + when :planner + planner when :anonymous anonymous end diff --git a/spec/policies/work_item_policy_spec.rb b/spec/policies/work_item_policy_spec.rb index a634105e0be..d251c688b43 100644 --- a/spec/policies/work_item_policy_spec.rb +++ b/spec/policies/work_item_policy_spec.rb @@ -14,9 +14,11 @@ RSpec.describe WorkItemPolicy, :aggregate_failures, feature_category: :team_plan let_it_be(:guest) { create(:user, guest_of: [private_project, public_project]) } let_it_be(:guest_author) { create(:user, guest_of: [private_project, public_project]) } + let_it_be(:planner) { create(:user, planner_of: [private_project, public_project]) } let_it_be(:reporter) { create(:user, reporter_of: [private_project, public_project]) } let_it_be(:group_guest) { create(:user, guest_of: [private_group, public_group]) } + let_it_be(:group_planner) { create(:user, planner_of: [private_group, public_group]) } let_it_be(:group_guest_author) { create(:user, guest_of: [private_group, public_group]) } let_it_be(:group_reporter) { create(:user, reporter_of: [private_group, public_group]) } diff --git a/spec/presenters/member_presenter_spec.rb b/spec/presenters/member_presenter_spec.rb index c0ed8fdb3a3..ebf2e847a08 100644 --- a/spec/presenters/member_presenter_spec.rb +++ b/spec/presenters/member_presenter_spec.rb @@ -48,6 +48,7 @@ RSpec.describe MemberPresenter, feature_category: :groups_and_projects do it 'returns all roles for the root group' do expect(described_class.new(root_member).valid_level_roles).to eq( 'Guest' => Gitlab::Access::GUEST, + 'Planner' => Gitlab::Access::PLANNER, 'Reporter' => Gitlab::Access::REPORTER, 'Developer' => Gitlab::Access::DEVELOPER, 'Maintainer' => Gitlab::Access::MAINTAINER, diff --git a/spec/requests/api/alert_management_alerts_spec.rb b/spec/requests/api/alert_management_alerts_spec.rb index 8dd0c46eab6..bd08bed02f6 100644 --- a/spec/requests/api/alert_management_alerts_spec.rb +++ b/spec/requests/api/alert_management_alerts_spec.rb @@ -303,7 +303,9 @@ RSpec.describe API::AlertManagementAlerts, feature_category: :incident_managemen context 'and metric image not found' do subject do - put api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user) # rubocop: disable Layout/LineLength + put api( + "/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user + ) end it 'returns an error' do @@ -378,7 +380,9 @@ RSpec.describe API::AlertManagementAlerts, feature_category: :incident_managemen context 'when metric image not found' do subject do - delete api("/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user) # rubocop: disable Layout/LineLength + delete api( + "/projects/#{project.id}/alert_management_alerts/#{alert.iid}/metric_images/#{non_existing_record_id}", user + ) end it 'returns an error' do diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb index e48736fb91d..6388ef049d7 100644 --- a/spec/requests/api/ci/jobs_spec.rb +++ b/spec/requests/api/ci/jobs_spec.rb @@ -269,7 +269,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do expect(json_response.dig('project', 'groups')).to match_array([{ 'id' => group.id }]) expect(json_response.dig('user', 'id')).to eq(api_user.id) expect(json_response.dig('user', 'username')).to eq(api_user.username) - expect(json_response.dig('user', 'roles_in_project')).to match_array %w[guest reporter developer] + expect(json_response.dig('user', 'roles_in_project')).to match_array %w[guest planner reporter developer] expect(json_response).not_to include('environment') end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 48d8bd904e4..87b9055c919 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -909,7 +909,7 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do it 'returns 400 when access level is not valid' do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", maintainer), - params: { access_level: 15 } + params: { access_level: 25 } expect(response).to have_gitlab_http_status(:bad_request) end diff --git a/spec/requests/projects/releases_controller_spec.rb b/spec/requests/projects/releases_controller_spec.rb index bb12f80f0fa..8cf1c83e13d 100644 --- a/spec/requests/projects/releases_controller_spec.rb +++ b/spec/requests/projects/releases_controller_spec.rb @@ -107,7 +107,7 @@ RSpec.describe 'Projects::ReleasesController', feature_category: :release_orches context 'when private project' do let_it_be(:private_project) { create(:project, :repository, :private) } - it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do # rubocop:disable Layout/LineLength -- We prefer to keep it on a single line, for simplicity sake + it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do let(:url) { project_releases_url(private_project, format: :atom) } before do diff --git a/spec/scripts/setup/tests-metadata_spec.rb b/spec/scripts/setup/tests-metadata_spec.rb index 29b7df39e8d..0ca58448331 100644 --- a/spec/scripts/setup/tests-metadata_spec.rb +++ b/spec/scripts/setup/tests-metadata_spec.rb @@ -6,7 +6,7 @@ require_relative '../../../scripts/setup/tests-metadata' # rubocop:disable Gitlab/Json, Lint/MissingCopEnableDirective -- It's not intended to have extra dependency -RSpec.describe TestsMetadata, feature_category: :tooling do # rubocop:disable Rails/FilePath,RSpec/SpecFilePathFormat -- We use dashes in scripts +RSpec.describe TestsMetadata, feature_category: :tooling do # rubocop:disable RSpec/SpecFilePathFormat -- We use dashes in scripts subject(:metadata) do described_class.new( mode: mode, diff --git a/spec/services/ci/create_pipeline_service/logger_spec.rb b/spec/services/ci/create_pipeline_service/logger_spec.rb index aee2f9b6370..d7027f624c5 100644 --- a/spec/services/ci/create_pipeline_service/logger_spec.rb +++ b/spec/services/ci/create_pipeline_service/logger_spec.rb @@ -2,8 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, # rubocop: disable RSpec/SpecFilePathForm - feature_category: :continuous_integration do +RSpec.describe Ci::CreatePipelineService, feature_category: :continuous_integration do describe 'pipeline logger' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/groups/open_issues_count_service_spec.rb b/spec/services/groups/open_issues_count_service_spec.rb index b336f48ebf9..362d5e8c723 100644 --- a/spec/services/groups/open_issues_count_service_spec.rb +++ b/spec/services/groups/open_issues_count_service_spec.rb @@ -39,7 +39,7 @@ RSpec.describe Groups::OpenIssuesCountService, :use_clean_rails_memory_store_cac context 'when user is provided' do context 'when user can read confidential issues' do before do - group.add_reporter(user) + group.add_planner(user) end it 'returns the right count with confidential issues' do diff --git a/spec/services/packages/conan/create_package_service_spec.rb b/spec/services/packages/conan/create_package_service_spec.rb index 602179a1224..c056dd5f420 100644 --- a/spec/services/packages/conan/create_package_service_spec.rb +++ b/spec/services/packages/conan/create_package_service_spec.rb @@ -169,15 +169,12 @@ RSpec.describe Packages::Conan::CreatePackageService, feature_category: :package stub_feature_flags(packages_protected_packages_conan: false) end - # rubocop:disable Layout/LineLength -- Avoid formatting to keep one-line table syntax where(:package_name_pattern, :minimum_access_level_for_push, :user) do ref(:package_name) | :maintainer | ref(:project_developer) ref(:package_name) | :admin | ref(:project_owner) ref(:package_name_pattern_no_match) | :maintainer | ref(:project_developer) ref(:package_name_pattern_no_match) | :admin | ref(:project_owner) end - # rubocop:enable Layout/LineLength - with_them do it_behaves_like 'a service response for valid package' end diff --git a/spec/services/packages/pypi/create_package_service_spec.rb b/spec/services/packages/pypi/create_package_service_spec.rb index 954c25083f5..28a636967a8 100644 --- a/spec/services/packages/pypi/create_package_service_spec.rb +++ b/spec/services/packages/pypi/create_package_service_spec.rb @@ -278,7 +278,7 @@ RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures, featur minimum_access_level_for_push: minimum_access_level_for_push) end - # rubocop:disable Layout/LineLength -- Avoid formatting to keep one-line table syntax + # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective -- Avoid formatting to keep one-line table syntax where(:package_name_pattern, :minimum_access_level_for_push, :user, :shared_examples_name) do ref(:package_name) | :maintainer | ref(:project_developer) | 'an error service response for protected package' ref(:package_name) | :maintainer | ref(:project_maintainer) | 'a service response for valid package' @@ -297,7 +297,7 @@ RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures, featur ref(:package_name) | :maintainer | nil | 'an error service response for unauthorized' ref(:package_name) | :admin | ref(:unauthorized_deploy_token) | 'an error service response for unauthorized' end - # rubocop:enable Layout/LineLength + # rubocop:enable Layout/LineLength, Lint/RedundantCopDisableDirective with_them do it_behaves_like params[:shared_examples_name] @@ -308,15 +308,12 @@ RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures, featur stub_feature_flags(packages_protected_packages_pypi: false) end - # rubocop:disable Layout/LineLength -- Avoid formatting to keep one-line table syntax where(:package_name_pattern, :minimum_access_level_for_push, :user) do ref(:package_name) | :maintainer | ref(:project_developer) ref(:package_name) | :admin | ref(:project_owner) ref(:package_name_pattern_no_match) | :maintainer | ref(:project_developer) ref(:package_name_pattern_no_match) | :admin | ref(:project_owner) end - # rubocop:enable Layout/LineLength - with_them do it_behaves_like 'a service response for valid package' end diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb index 46601d07ed9..2eab23cae5d 100644 --- a/spec/services/pages_domains/create_acme_order_service_spec.rb +++ b/spec/services/pages_domains/create_acme_order_service_spec.rb @@ -82,7 +82,7 @@ RSpec.describe PagesDomains::CreateAcmeOrderService, feature_category: :pages do end where(:lets_encrypt_client_error, :lets_encrypt_client_error_message) do - Acme::Client::Error::RejectedIdentifier | 'Invalid identifiers requested :: Cannot issue for "local": Domain name needs at least one dot' # rubocop:disable Layout/LineLength -- Ensuring one line table syntax + Acme::Client::Error::RejectedIdentifier | 'Invalid identifiers requested :: Cannot issue for "local": Domain name needs at least one dot' # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective -- Ensuring one line table syntax Acme::Client::Error::BadCSR | 'Error finalizing order :: signature algorithm not supported' Acme::Client::Error | 'Other acme client error' Acme::Client::Error | nil diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb index a8835312913..bbcd607d464 100644 --- a/spec/services/projects/open_issues_count_service_spec.rb +++ b/spec/services/projects/open_issues_count_service_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_c context 'when user can read confidential issues' do before do - project.add_reporter(user) + project.add_planner(user) end it 'returns the right count with confidential issues' do diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb index 1ced2eda799..80d83ef6e67 100644 --- a/spec/services/todos/destroy/entity_leave_service_spec.rb +++ b/spec/services/todos/destroy/entity_leave_service_spec.rb @@ -66,8 +66,8 @@ RSpec.describe Todos::Destroy::EntityLeaveService, feature_category: :team_plann case access_name when :developer object.add_developer(user) - when :reporter - object.add_reporter(user) + when :planner + object.add_planner(user) when :guest object.add_guest(user) end @@ -91,11 +91,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService, feature_category: :team_plann context 'access permissions' do where(:group_access, :project_access, :method_name) do [ - [nil, :reporter, :does_not_remove_any_todos], + [nil, :planner, :does_not_remove_any_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos], - [:reporter, nil, :does_not_remove_any_todos], + [:planner, nil, :does_not_remove_any_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_and_merge_request_todos], - [:guest, :reporter, :does_not_remove_any_todos], + [:guest, :planner, :does_not_remove_any_todos], [:guest, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos] ] end @@ -123,11 +123,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService, feature_category: :team_plann context 'access permissions' do where(:group_access, :project_access, :method_name) do [ - [nil, :reporter, :does_not_remove_any_todos], + [nil, :planner, :does_not_remove_any_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos], - [:reporter, nil, :does_not_remove_any_todos], + [:planner, nil, :does_not_remove_any_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_and_merge_request_todos], - [:guest, :reporter, :does_not_remove_any_todos], + [:guest, :planner, :does_not_remove_any_todos], [:guest, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos] ] end @@ -176,11 +176,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService, feature_category: :team_plann context 'access permissions' do where(:group_access, :project_access, :method_name) do [ - [nil, :reporter, :does_not_remove_any_todos], + [nil, :planner, :does_not_remove_any_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_todos], - [:reporter, nil, :does_not_remove_any_todos], + [:planner, nil, :does_not_remove_any_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_todos], - [:guest, :reporter, :does_not_remove_any_todos], + [:guest, :planner, :does_not_remove_any_todos], [:guest, :guest, :removes_confidential_issues_and_internal_notes_todos] ] end @@ -221,11 +221,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService, feature_category: :team_plann context 'access permissions' do where(:group_access, :project_access, :method_name) do [ - [nil, :reporter, :does_not_remove_any_todos], + [nil, :planner, :does_not_remove_any_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos], - [:reporter, nil, :does_not_remove_any_todos], + [:planner, nil, :does_not_remove_any_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_and_merge_request_todos], - [:guest, :reporter, :does_not_remove_any_todos], + [:guest, :planner, :does_not_remove_any_todos], [:guest, :guest, :removes_confidential_issues_and_internal_notes_and_merge_request_todos] ] end @@ -336,11 +336,11 @@ RSpec.describe Todos::Destroy::EntityLeaveService, feature_category: :team_plann where(:group_access, :project_access, :method_name) do [ [nil, nil, :removes_confidential_issues_and_internal_notes_todos], - [nil, :reporter, :does_not_remove_any_todos], + [nil, :planner, :does_not_remove_any_todos], [nil, :guest, :removes_confidential_issues_and_internal_notes_todos], - [:reporter, nil, :does_not_remove_any_todos], + [:planner, nil, :does_not_remove_any_todos], [:guest, nil, :removes_confidential_issues_and_internal_notes_todos], - [:guest, :reporter, :does_not_remove_any_todos], + [:guest, :planner, :does_not_remove_any_todos], [:guest, :guest, :removes_confidential_issues_and_internal_notes_todos] ] end diff --git a/spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb b/spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb index 1f8505978f5..4be78f690c3 100644 --- a/spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb +++ b/spec/support/helpers/migrations_helpers/vulnerabilities_findings_helper.rb @@ -20,7 +20,7 @@ module MigrationHelpers "description" => "The cipher does not provide data integrity update 1", "message" => "The cipher does not provide data integrity", "cve" => "818bf5dacb291e15d9e6dc3c5ac32178:CIPHER", - "solution" => "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.", # rubocop:disable Layout/LineLength + "solution" => "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.", "location" => { "file" => "maven/src/main/java/com/gitlab/security_products/tests/App.java", "start_line" => 29, diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb index b7a3b77a694..ab9cca7c6b0 100644 --- a/spec/support/helpers/wait_for_requests.rb +++ b/spec/support/helpers/wait_for_requests.rb @@ -72,6 +72,9 @@ module WaitForRequests end def finished_all_ajax_requests? - Capybara.page.evaluate_script('window.pendingRequests || window.pendingApolloRequests || window.pendingRailsUJSRequests || 0').zero? # rubocop:disable Style/NumericPredicate + Capybara + .page + .evaluate_script('window.pendingRequests || window.pendingApolloRequests || window.pendingRailsUJSRequests || 0') + .zero? end end diff --git a/spec/support/matchers/token_authenticatable_matchers.rb b/spec/support/matchers/token_authenticatable_matchers.rb new file mode 100644 index 00000000000..e3c7f2c5751 --- /dev/null +++ b/spec/support/matchers/token_authenticatable_matchers.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +module TokenAuthenticatableMatchers + extend RSpec::Matchers::DSL + + ROUTING_PAYLOAD_REGEX = + %r{ + (c:\w+\n)? + (g:\w+\n)? + o:\w+ + (\np:\w+)? + (\nu:\w+)? + }mx + + ROUTABLE_TOKEN_REGEX = + %r{ + \A + ([\w\-]+){0,20}? # prefix + [\w+\-]{27,300} # base64 payload + \. + [0-9a-z]{2} # base64 payload length holder + [0-9a-z]{7} # crc + \z + }mx + BASE64_PAYLOAD_LENGTH_HOLDER_BYTES = + TokenAuthenticatableStrategies::RoutableTokenGenerator::BASE64_PAYLOAD_LENGTH_HOLDER_BYTES + CRC_BYTES = TokenAuthenticatableStrategies::RoutableTokenGenerator::CRC_BYTES + + def generate_routable_token(routing_payload, random_bytes:, prefix: '') + base64_payload = Base64.urlsafe_encode64( + "#{routing_payload}#{random_bytes}#{[random_bytes.size].pack('C')}", padding: false + ) + base64_payload_length = base64_payload.size.to_s(36) + .rjust(BASE64_PAYLOAD_LENGTH_HOLDER_BYTES, '0') + checksummable_payload = "#{prefix}#{base64_payload}.#{base64_payload_length}" + crc = Zlib.crc32(checksummable_payload).to_s(36).rjust(CRC_BYTES, '0') + + "#{checksummable_payload}#{crc}" + end + + def decoded_payload(token) + base64_payload_length = base64_payload_length(token) + offset = BASE64_PAYLOAD_LENGTH_HOLDER_BYTES + CRC_BYTES + 1 + + Base64.urlsafe_decode64(token[-offset - base64_payload_length, base64_payload_length]) + end + + def base64_payload_length(token) + offset = BASE64_PAYLOAD_LENGTH_HOLDER_BYTES + CRC_BYTES + + token[-offset, BASE64_PAYLOAD_LENGTH_HOLDER_BYTES].to_i(36) + end + + def decoded_random_bytes(token) + payload = decoded_payload(token) + random_bytes_length = random_bytes_length(payload) + offset = random_bytes_length + 1 + + payload[-offset, random_bytes_length] + end + + def random_bytes_length(payload) + payload[-1].unpack1("C") + end + + def decoded_routing_payload(token) + payload = decoded_payload(token) + + payload[0, routing_payload_length(payload)] + end + + def routing_payload_length(payload) + payload.size - random_bytes_length(payload) - 1 + end + + def token_crc(token) + token[-CRC_BYTES, CRC_BYTES].to_i(36) + end + + def full_token(payload, prefix: '') + "#{prefix}#{payload}" + end + + def digested_token(token) + Gitlab::CryptoHelper.sha256(token) + end + + matcher :be_a_token do + match do |actual_token| + @expected_token = + if @routing_payload + generate_routable_token(@routing_payload, random_bytes: @random_bytes.to_s, prefix: @prefix.to_s) + else + full_token(@payload, prefix: @prefix.to_s) + end + + actual_token == expected_token + end + + chain :with_payload do |payload| + @payload = payload + end + + chain :with_routing_payload do |routing_payload| + @routing_payload = routing_payload + end + + chain :and_random_bytes do |random_bytes| + @random_bytes = random_bytes + end + + chain :and_prefix do |prefix| + @prefix = prefix + end + + failure_message do |actual_token| + expectation_msg = "Expected token:\n#{@expected_token}\n" + expectation_msg << if @routing_payload + "Expected routing payload\n#{@routing_payload}\n" \ + "got\n#{decoded_routing_payload(actual_token)}" + else + "Expected payload\n#{@routing_payload}\n" \ + "got\n#{actual_token.delete_prefix(@prefix.to_s)}" + end + end + end + + matcher :be_a_digested_token do + match do |actual_token_digest| + @expected_token = + if @routing_payload + generate_routable_token(@routing_payload, random_bytes: @random_bytes.to_s, prefix: @prefix.to_s) + else + full_token(@payload, prefix: @prefix.to_s) + end + + actual_token_digest == digested_token(@expected_token) + end + + chain :with_payload do |payload| + @payload = payload + end + + chain :with_routing_payload do |routing_payload| + @routing_payload = routing_payload + end + + chain :and_random_bytes do |random_bytes| + @random_bytes = random_bytes + end + + chain :and_prefix do |prefix| + @prefix = prefix + end + + failure_message do |actual_token_digest| + "Expected token digest:\n#{digested_token(@expected_token)}\ngot\n#{actual_token_digest}." + end + end + + matcher :be_a_routable_token do + match do |actual_token| + result = actual_token.match?(ROUTABLE_TOKEN_REGEX) + result &&= + if @payload + decoded_routing_payload(actual_token) == @payload + else + decoded_routing_payload(actual_token).match?(ROUTING_PAYLOAD_REGEX) + end + + @expected_crc = Zlib.crc32(actual_token[...-CRC_BYTES]) + + result && token_crc(actual_token) == @expected_crc + end + + chain :with_payload do |payload| + @payload = payload + end + + chain :with_prefix do |prefix| + @prefix = prefix + end + + chain :and_prefix do |prefix| + @prefix = prefix + end + + failure_message do |actual_token| + expectation_msg = + "Expected\n#{actual_token}\nto match\n#{ROUTABLE_TOKEN_REGEX}\n" \ + "and #{token_crc(actual_token)} to equal #{@expected_crc}\n" + expectation_msg << if @payload # rubocop:disable Cop/LineBreakAroundConditionalBlock: -- doesn't make sense here + "and\n#{decoded_routing_payload(actual_token)}\nto equal\n#{@payload}" + else + expectation_msg << "and\n#{decoded_routing_payload(actual_token)}\n" \ + "to match\n#{ROUTING_PAYLOAD_REGEX}" + end + end + end + + matcher :have_different_random_bytes_than do |second_token| + match do |first_token| + decoded_random_bytes(first_token) != decoded_random_bytes(second_token) + end + + chain :with_prefix do |prefix| + @prefix = prefix + end + + failure_message do |first_token| + "Expected\n#{decoded_random_bytes(first_token)}\n" \ + "not to equal\n#{decoded_random_bytes(second_token)}" + end + end +end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index fceda6349eb..eeba57d2151 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -2192,7 +2192,6 @@ - './ee/spec/services/personal_access_tokens/instance/update_lifetime_service_spec.rb' - './ee/spec/services/personal_access_tokens/revoke_invalid_tokens_spec.rb' - './ee/spec/services/personal_access_tokens/revoke_service_audit_log_spec.rb' -- './ee/spec/services/personal_access_tokens/rotation_verifier_service_spec.rb' - './ee/spec/services/projects/alerting/notify_service_spec.rb' - './ee/spec/services/projects/cleanup_service_spec.rb' - './ee/spec/services/projects/create_from_template_service_spec.rb' diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index e223e2f82e4..d97dd19cfe5 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -8,6 +8,7 @@ RSpec.shared_context 'GroupPolicy context' do end let_it_be(:guest) { create(:user, guest_of: group) } + let_it_be(:planner) { create(:user, planner_of: group) } let_it_be(:reporter) { create(:user, reporter_of: group) } let_it_be(:developer) { create(:user, developer_of: group) } let_it_be(:maintainer) { create(:user, maintainer_of: group) } @@ -22,6 +23,8 @@ RSpec.shared_context 'GroupPolicy context' do %i[ read_group read_counts read_issue read_namespace read_label read_issue_board_list read_milestone read_issue_board + read_group_activity read_group_issues read_group_boards read_group_labels + read_group_milestones read_group_merge_requests ] end @@ -33,6 +36,14 @@ RSpec.shared_context 'GroupPolicy context' do ] end + let(:planner_permissions) do + guest_permissions + %i[ + admin_label admin_milestone admin_issue_board admin_issue_board_list + admin_issue update_issue destroy_issue read_confidential_issues read_internal_note + read_crm_contact read_crm_organization + ] + end + let(:reporter_permissions) do %i[ admin_label diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 8a6b75eb449..bd4c26cb23a 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -3,10 +3,12 @@ RSpec.shared_context 'ProjectPolicy context' do let_it_be(:anonymous) { nil } let_it_be(:guest) { create(:user) } + let_it_be(:planner) { create(:user) } let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } let_it_be(:maintainer) { create(:user) } let_it_be(:inherited_guest) { create(:user) } + let_it_be(:inherited_planner) { create(:user) } let_it_be(:inherited_reporter) { create(:user) } let_it_be(:inherited_developer) { create(:user) } let_it_be(:inherited_maintainer) { create(:user) } @@ -32,6 +34,15 @@ RSpec.shared_context 'ProjectPolicy context' do ] end + let(:base_planner_permissions) do + base_guest_permissions + + %i[ + admin_issue admin_issue_board admin_issue_board_list admin_label admin_milestone + read_confidential_issues update_issue reopen_issue destroy_issue read_internal_note + download_wiki_code create_wiki admin_wiki export_work_items + ] + end + let(:base_reporter_permissions) do %i[ admin_issue admin_label admin_milestone admin_issue_board_list @@ -107,23 +118,27 @@ RSpec.shared_context 'ProjectPolicy context' do # Used in EE specs let(:additional_guest_permissions) { [] } + let(:additional_planner_permissions) { [] } let(:additional_reporter_permissions) { [] } let(:additional_maintainer_permissions) { [] } let(:additional_owner_permissions) { [] } let(:guest_permissions) { base_guest_permissions + additional_guest_permissions } + let(:planner_permissions) { base_planner_permissions + additional_planner_permissions } let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions } let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions } let(:owner_permissions) { base_owner_permissions + additional_owner_permissions } before_all do group.add_guest(inherited_guest) + group.add_planner(inherited_planner) group.add_reporter(inherited_reporter) group.add_developer(inherited_developer) group.add_maintainer(inherited_maintainer) [private_project, internal_project, public_project, public_project_in_group].each do |project| project.add_guest(guest) + project.add_planner(planner) project.add_reporter(reporter) project.add_developer(developer) project.add_maintainer(maintainer) diff --git a/spec/support/shared_examples/features/milestone_editing_shared_examples.rb b/spec/support/shared_examples/features/milestone_editing_shared_examples.rb index 53498a1bb39..062e8fc3644 100644 --- a/spec/support/shared_examples/features/milestone_editing_shared_examples.rb +++ b/spec/support/shared_examples/features/milestone_editing_shared_examples.rb @@ -14,7 +14,7 @@ RSpec.shared_examples 'milestone handling version conflicts' do expect(page).to have_content( format( - _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), model_name: _('milestone'), link_to_model: _('milestone') ) diff --git a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb index f2052f4202d..5bad260d48b 100644 --- a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb +++ b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb @@ -172,7 +172,7 @@ RSpec.shared_examples 'a redacted search results' do context 'with :with_api_entity_associations' do let(:unredacted_results) { ar_relation(MergeRequest.with_api_entity_associations, readable, unreadable) } - it_behaves_like "redaction limits N+1 queries", limit: 8 + it_behaves_like "redaction limits N+1 queries", limit: 10 end end diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_shared_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_shared_examples.rb index 6676fbf070e..e001a96acb5 100644 --- a/spec/support/shared_examples/models/concerns/protected_ref_access_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/protected_ref_access_shared_examples.rb @@ -131,7 +131,7 @@ RSpec.shared_examples 'protected ref access' do context 'when current_user cannot push_code to project' do before do - allow(current_user).to receive(:can?).with(:push_code, project).and_return(false) # rubocop:disable CodeReuse/ActiveRecord -- false positive flag on `with` + allow(current_user).to receive(:can?).with(:push_code, project).and_return(false) end it { is_expected.to eq false } diff --git a/spec/support/shared_examples/models/concerns/token_authenticatable_shared_examples.rb b/spec/support/shared_examples/models/concerns/token_authenticatable_shared_examples.rb index 4242b1c4a73..61f5f5489a6 100644 --- a/spec/support/shared_examples/models/concerns/token_authenticatable_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/token_authenticatable_shared_examples.rb @@ -21,3 +21,34 @@ RSpec.shared_examples 'TokenAuthenticatable' do end end end + +# Expects `expected_token_prefix`, `expected_token_payload`, `expected_token`, `expected_token_digest` variables +RSpec.shared_examples 'a digested token' do + it 'generates a digested token' do + expect(expected_token) + .to be_a_token + .with_payload(expected_token_payload) + .and_prefix(expected_token_prefix) + expect(expected_token_digest) + .to be_a_digested_token + .with_payload(expected_token_payload) + .and_prefix(expected_token_prefix) + end +end + +# Expects `expected_token`, `expected_routing_payload`, `expected_random_bytes`, `expected_token_prefix`, +# `expected_token_digest` variables +RSpec.shared_examples 'a digested routable token' do + it 'generates a digested routable token' do + expect(expected_token) + .to be_a_token + .with_routing_payload(expected_routing_payload) + .and_random_bytes(expected_random_bytes) + .and_prefix(expected_token_prefix) + expect(expected_token_digest) + .to be_a_digested_token + .with_routing_payload(expected_routing_payload) + .and_random_bytes(expected_random_bytes) + .and_prefix(expected_token_prefix) + end +end diff --git a/spec/support/shared_examples/policies/group_level_work_items_shared_examples.rb b/spec/support/shared_examples/policies/group_level_work_items_shared_examples.rb index fcb13b04f8c..b8d6bbc8016 100644 --- a/spec/support/shared_examples/policies/group_level_work_items_shared_examples.rb +++ b/spec/support/shared_examples/policies/group_level_work_items_shared_examples.rb @@ -34,6 +34,18 @@ RSpec.shared_examples 'abilities without group level work items license' do ) end + it 'checks project planner abilities' do + # disallowed + expect(permissions(planner, work_item)).to be_disallowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :delete_work_item, + :admin_parent_link, :set_work_item_metadata, :admin_work_item_link, :create_note + ) + expect(permissions(planner, confidential_work_item)).to be_disallowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :delete_work_item, + :admin_parent_link, :set_work_item_metadata, :admin_work_item_link, :create_note + ) + end + it 'checks project reporter abilities' do # disallowed expect(permissions(reporter, work_item)).to be_disallowed( @@ -66,6 +78,18 @@ RSpec.shared_examples 'abilities without group level work items license' do ) end + it 'checks group planner abilities' do + # disallowed + expect(permissions(group_planner, work_item)).to be_disallowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :delete_work_item, + :admin_parent_link, :set_work_item_metadata, :admin_work_item_link, :create_note + ) + expect(permissions(group_planner, confidential_work_item)).to be_disallowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :delete_work_item, + :admin_parent_link, :set_work_item_metadata, :admin_work_item_link, :create_note + ) + end + it 'checks group reporter abilities' do # disallowed expect(permissions(group_reporter, work_item)).to be_disallowed( @@ -108,6 +132,23 @@ RSpec.shared_examples 'abilities with group level work items license' do ) end + it 'checks project planner abilities' do + # allowed + expect(permissions(planner, work_item)).to be_allowed( + :read_work_item, :read_issue, :read_note, :create_note + ) + + # disallowed + expect(permissions(planner, work_item)).to be_disallowed( + :admin_work_item, :update_work_item, :delete_work_item, :admin_parent_link, :set_work_item_metadata, + :admin_work_item_link + ) + expect(permissions(planner, confidential_work_item)).to be_disallowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :admin_parent_link, + :set_work_item_metadata, :admin_work_item_link, :create_note + ) + end + it 'checks project reporter abilities' do # allowed expect(permissions(reporter, work_item)).to be_allowed( @@ -159,6 +200,22 @@ RSpec.shared_examples 'abilities with group level work items license' do ) end + it 'checks group planner abilities' do + # allowed + expect(permissions(group_planner, work_item)).to be_allowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :admin_parent_link, + :set_work_item_metadata, :admin_work_item_link, :create_note + ) + expect(permissions(group_planner, confidential_work_item)).to be_allowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :admin_parent_link, + :set_work_item_metadata, :admin_work_item_link, :create_note + ) + + # disallowed + expect(permissions(group_planner, work_item)).to be_allowed(:delete_work_item) + expect(permissions(group_planner, confidential_work_item)).to be_allowed(:delete_work_item) + end + it 'checks group reporter abilities' do # allowed expect(permissions(group_reporter, work_item)).to be_allowed( diff --git a/spec/support/shared_examples/policies/project_level_work_items_shared_examples.rb b/spec/support/shared_examples/policies/project_level_work_items_shared_examples.rb index daaea4a88e5..00a45c8a5c8 100644 --- a/spec/support/shared_examples/policies/project_level_work_items_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_level_work_items_shared_examples.rb @@ -35,6 +35,38 @@ RSpec.shared_examples 'checks abilities for project level work items' do ) end + it 'checks planner abilities' do + # allowed + expect(permissions(planner, project_work_item)).to be_allowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :admin_parent_link, + :set_work_item_metadata, :admin_work_item_link, :create_note + ) + expect(permissions(planner, project_confidential_work_item)).to be_allowed(:read_work_item, :read_issue, + :read_note, :admin_work_item, :update_work_item, :admin_parent_link, :set_work_item_metadata, + :admin_work_item_link, :create_note + ) + + # disallowed + expect(permissions(planner, project_work_item)).to be_allowed(:delete_work_item) + expect(permissions(planner, project_confidential_work_item)).to be_allowed(:delete_work_item) + end + + it 'checks group planner abilities' do + # allowed + expect(permissions(group_planner, project_work_item)).to be_allowed( + :read_work_item, :read_issue, :read_note, :admin_work_item, :update_work_item, :admin_parent_link, + :set_work_item_metadata, :admin_work_item_link, :create_note + ) + expect(permissions(group_planner, project_confidential_work_item)).to be_allowed(:read_work_item, + :read_issue, :read_note, :admin_work_item, :update_work_item, :admin_parent_link, :set_work_item_metadata, + :admin_work_item_link, :create_note + ) + + # disallowed + expect(permissions(group_planner, project_work_item)).to be_allowed(:delete_work_item) + expect(permissions(group_planner, project_confidential_work_item)).to be_allowed(:delete_work_item) + end + it 'checks reporter abilities' do # allowed expect(permissions(reporter, project_work_item)).to be_allowed( diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index 953a036c0e5..820328d0154 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -195,6 +195,109 @@ RSpec.shared_examples 'project policies as guest' do end end +RSpec.shared_examples 'project policies as planner' do + let(:disallowed_reporter_public_permissions) do + %i[ + create_snippet create_incident daily_statistics metrics_dashboard read_harbor_registry + read_prometheus read_sentry_issue read_external_emails + ] + end + + let(:disallowed_reporter_permissions) do + disallowed_reporter_public_permissions + + %i[fork_project read_commit_status read_container_image read_deployment read_environment] + end + + context 'as a direct project member' do + context 'abilities for public projects' do + let(:project) { public_project } + let(:current_user) { planner } + + specify do + expect_allowed(*public_permissions) + expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) + expect_allowed(*(base_reporter_permissions - disallowed_reporter_public_permissions)) + expect_disallowed(*disallowed_reporter_public_permissions) + expect_disallowed(*(developer_permissions - [:create_wiki])) + expect_disallowed(*(maintainer_permissions - [:admin_wiki])) + expect_disallowed(*(owner_permissions - [:destroy_issue])) + end + end + + context 'abilities for non-public projects' do + let(:project) { private_project } + let(:current_user) { planner } + + specify do + expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) + expect_allowed(*(base_reporter_permissions - disallowed_reporter_permissions)) + expect_disallowed(*disallowed_reporter_permissions) + expect_disallowed(*(developer_permissions - [:create_wiki])) + expect_disallowed(*(maintainer_permissions - [:admin_wiki])) + expect_disallowed(*(owner_permissions - [:destroy_issue])) + end + + it_behaves_like 'deploy token does not get confused with user' do + let(:user_id) { planner.id } + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { planner_permissions } + end + + context 'public builds enabled' do + specify do + expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) + expect_allowed(:read_build, :read_pipeline) + end + end + + context 'when public builds disabled' do + before do + project.update!(public_builds: false) + end + + specify do + expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) + expect_disallowed(:read_build, :read_pipeline) + end + end + + context 'when builds are disabled' do + before do + project.project_feature.update!(builds_access_level: ProjectFeature::DISABLED) + end + + specify do + expect_disallowed(:read_build) + expect_allowed(:read_pipeline) + end + end + end + end + + context 'as an inherited member from the group' do + context 'abilities for private projects' do + let(:project) { private_project_in_group } + let(:current_user) { inherited_planner } + + specify do + expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) + expect_allowed(*(base_reporter_permissions - disallowed_reporter_permissions)) + expect_disallowed(*disallowed_reporter_permissions) + expect_disallowed(*(developer_permissions - [:create_wiki])) + expect_disallowed(*(maintainer_permissions - [:admin_wiki])) + expect_disallowed(*(owner_permissions - [:destroy_issue])) + end + end + end +end + RSpec.shared_examples 'project policies as reporter' do context 'abilities for non-public projects' do let(:project) { private_project } @@ -202,6 +305,7 @@ RSpec.shared_examples 'project policies as reporter' do specify do expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - %i[create_wiki admin_wiki destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) expect_disallowed(*developer_permissions) @@ -225,6 +329,7 @@ RSpec.shared_examples 'project policies as reporter' do specify do expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - %i[create_wiki admin_wiki destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) expect_disallowed(*developer_permissions) @@ -242,6 +347,7 @@ RSpec.shared_examples 'project policies as developer' do specify do expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - %i[admin_wiki destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) expect_allowed(*developer_permissions) @@ -265,6 +371,7 @@ RSpec.shared_examples 'project policies as developer' do specify do expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - %i[admin_wiki destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) expect_allowed(*developer_permissions) @@ -282,6 +389,7 @@ RSpec.shared_examples 'project policies as maintainer' do it do expect_allowed(*guest_permissions) + expect_allowed(*(planner_permissions - [:destroy_issue])) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) expect_allowed(*developer_permissions) @@ -306,6 +414,7 @@ RSpec.shared_examples 'project policies as owner' do it do expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) expect_allowed(*developer_permissions) @@ -330,6 +439,7 @@ RSpec.shared_examples 'project policies as organization owner' do it do expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) expect_allowed(*reporter_permissions) expect_disallowed(*team_member_reporter_permissions) expect_allowed(*developer_permissions) @@ -361,6 +471,7 @@ RSpec.shared_examples 'project policies as admin with admin mode' do it do expect_allowed(*guest_permissions) + expect_allowed(*planner_permissions) expect_allowed(*reporter_permissions) expect_disallowed(*team_member_reporter_permissions) expect_allowed(*developer_permissions) diff --git a/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb index b9d4709efd5..7efd0ebcada 100644 --- a/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb +++ b/spec/support/shared_examples/policies/wiki_policies_shared_examples.rb @@ -15,6 +15,7 @@ RSpec.shared_examples 'model with wiki policies' do permissions[:reporter] = permissions[:guest] + %i[download_wiki_code] permissions[:developer] = permissions[:reporter] + %i[create_wiki] permissions[:maintainer] = permissions[:developer] + %i[admin_wiki] + permissions[:planner] = permissions[:maintainer] permissions[:all] = permissions[:maintainer] end end @@ -26,6 +27,7 @@ RSpec.shared_examples 'model with wiki policies' do :public | :enabled | :maintainer | :maintainer :public | :enabled | :developer | :developer :public | :enabled | :reporter | :reporter + :public | :enabled | :planner | :planner :public | :enabled | :guest | :guest :public | :enabled | :non_member | :guest :public | :enabled | :anonymous | :guest @@ -34,6 +36,7 @@ RSpec.shared_examples 'model with wiki policies' do :public | :private | :maintainer | :maintainer :public | :private | :developer | :developer :public | :private | :reporter | :reporter + :public | :private | :planner | :planner :public | :private | :guest | :guest :public | :private | :non_member | nil :public | :private | :anonymous | nil @@ -42,6 +45,7 @@ RSpec.shared_examples 'model with wiki policies' do :public | :disabled | :maintainer | nil :public | :disabled | :developer | nil :public | :disabled | :reporter | nil + :public | :disabled | :planner | nil :public | :disabled | :guest | nil :public | :disabled | :non_member | nil :public | :disabled | :anonymous | nil @@ -50,6 +54,7 @@ RSpec.shared_examples 'model with wiki policies' do :internal | :enabled | :maintainer | :maintainer :internal | :enabled | :developer | :developer :internal | :enabled | :reporter | :reporter + :internal | :enabled | :planner | :planner :internal | :enabled | :guest | :guest :internal | :enabled | :non_member | :guest :internal | :enabled | :anonymous | nil @@ -58,6 +63,7 @@ RSpec.shared_examples 'model with wiki policies' do :internal | :private | :maintainer | :maintainer :internal | :private | :developer | :developer :internal | :private | :reporter | :reporter + :internal | :private | :planner | :planner :internal | :private | :guest | :guest :internal | :private | :non_member | nil :internal | :private | :anonymous | nil @@ -66,6 +72,7 @@ RSpec.shared_examples 'model with wiki policies' do :internal | :disabled | :maintainer | nil :internal | :disabled | :developer | nil :internal | :disabled | :reporter | nil + :internal | :disabled | :planner | nil :internal | :disabled | :guest | nil :internal | :disabled | :non_member | nil :internal | :disabled | :anonymous | nil @@ -74,6 +81,7 @@ RSpec.shared_examples 'model with wiki policies' do :private | :private | :maintainer | :maintainer :private | :private | :developer | :developer :private | :private | :reporter | :reporter + :private | :private | :planner | :planner :private | :private | :guest | :guest :private | :private | :non_member | nil :private | :private | :anonymous | nil @@ -82,6 +90,7 @@ RSpec.shared_examples 'model with wiki policies' do :private | :disabled | :maintainer | nil :private | :disabled | :developer | nil :private | :disabled | :reporter | nil + :private | :disabled | :planner | nil :private | :disabled | :guest | nil :private | :disabled | :non_member | nil :private | :disabled | :anonymous | nil diff --git a/tooling/danger/sidekiq_queues.rb b/tooling/danger/sidekiq_queues.rb index bd6480fcba6..ae32b128682 100644 --- a/tooling/danger/sidekiq_queues.rb +++ b/tooling/danger/sidekiq_queues.rb @@ -14,7 +14,7 @@ module Tooling def changed_queue_names @changed_queue_names ||= (new_queues.values_at(*old_queues.keys) - old_queues.values) - .compact.map { |queue| queue[:name] } # rubocop:disable Rails/Pluck + .compact.map { |queue| queue[:name] } end private diff --git a/tooling/danger/stable_branch.rb b/tooling/danger/stable_branch.rb index abeed0be88a..13e867b8432 100644 --- a/tooling/danger/stable_branch.rb +++ b/tooling/danger/stable_branch.rb @@ -182,7 +182,7 @@ module Tooling raise VersionApiError unless response.success? - version_list = response.parsed_response.map { |v| v['version'] } # rubocop:disable Rails/Pluck + version_list = response.parsed_response.map { |v| v['version'] } version_list.sort_by { |v| Gem::Version.new(v) }.reverse end