From a0134987192c42adb626e48fe7eca936bc92967f Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 21 May 2025 12:11:45 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- Gemfile.checksum | 6 +- Gemfile.lock | 6 +- Gemfile.next.checksum | 6 +- Gemfile.next.lock | 6 +- .../mount_pipeline_schedules_form_app.js | 2 + .../graphql_shared/possible_types.json | 5 + .../javascripts/lib/utils/secret_detection.js | 2 +- .../components/interval_pattern_input.vue | 12 ++ .../javascripts/persistent_user_callouts.js | 3 +- .../components/work_item_type_icon.vue | 4 +- .../stylesheets/framework/variables.scss | 2 + .../page_bundles/merge_requests.scss | 10 +- app/controllers/concerns/bizible_csp.rb | 3 +- app/finders/work_items/work_items_finder.rb | 18 ++- .../work_items/shared_filter_arguments.rb | 17 +++ app/graphql/types/namespace_type.rb | 7 ++ .../namespaces/user_level_permissions.rb | 44 +++++++ ...p_namespace_user_level_permissions_type.rb | 24 ++++ ...t_namespace_user_level_permissions_type.rb | 20 ++++ ...r_namespace_user_level_permissions_type.rb | 14 +++ app/helpers/ci/pipeline_schedules_helper.rb | 3 +- app/models/deploy_token.rb | 5 + app/models/work_item.rb | 5 + config/initializers/active_context.rb | 6 + .../dedicated/configure_instance/_index.md | 10 +- doc/administration/dedicated/encryption.md | 12 +- doc/api/graphql/reference/_index.md | 84 ++++++++++++++ doc/api/graphql/removed_items.md | 56 ++++----- doc/ci/jobs/ci_job_token.md | 2 +- .../site_architecture/automation.md | 2 +- .../documentation/styleguide/_index.md | 67 ++++++----- doc/development/export_csv.md | 16 +-- doc/development/fe_guide/haml.md | 46 ++++---- doc/development/metrics.md | 12 +- doc/development/migration_style_guide.md | 10 +- doc/development/routing.md | 10 +- doc/development/secure_coding_guidelines.md | 36 +++--- .../uploads/working_with_uploads.md | 18 +-- doc/user/analytics/value_streams_dashboard.md | 94 +++++++-------- .../tutorial_generate_sbom.md | 12 +- doc/user/project/badges.md | 24 ++-- .../lib/active_context/collection_cache.rb | 10 +- .../lib/active_context/concerns/collection.rb | 4 +- .../databases/concerns/query_result.rb | 4 - .../preprocessors/content_fetcher.rb | 34 ++++++ .../lib/active_context/reference.rb | 3 +- .../databases/concerns/adapter_spec.rb | 2 +- .../preprocessors/content_fetcher_spec.rb | 85 ++++++++++++++ lib/api/files.rb | 4 +- locale/gitlab.pot | 6 + spec/features/users/bizible_csp_spec.rb | 32 +++++- .../work_items/work_items_finder_spec.rb | 87 ++++++++++++++ .../components/interval_pattern_input_spec.js | 16 +++ .../resolvers/work_items_resolver_spec.rb | 32 +++++- spec/graphql/types/current_user_type_spec.rb | 3 +- ...espace_user_level_permissions_type_spec.rb | 107 ++++++++++++++++++ ...espace_user_level_permissions_type_spec.rb | 85 ++++++++++++++ ...espace_user_level_permissions_type_spec.rb | 7 ++ .../namespaces/user_level_permissions_spec.rb | 39 +++++++ .../ci/pipeline_schedules_helper_spec.rb | 3 +- spec/models/deploy_token_spec.rb | 27 +++++ spec/models/work_item_spec.rb | 24 ++++ spec/requests/api/files_spec.rb | 16 ++- .../self_rotation_spec.rb | 10 +- .../self_rotation_spec.rb | 6 +- .../api/resource_access_tokens_spec.rb | 8 +- spec/requests/api/users_spec.rb | 4 +- .../settings/access_tokens_controller_spec.rb | 4 +- .../settings/access_tokens_controller_spec.rb | 4 +- .../user_permissions_shared_examples.rb | 23 ++++ ...ccess_tokens_controller_shared_examples.rb | 2 +- 71 files changed, 1160 insertions(+), 272 deletions(-) create mode 100644 app/graphql/types/namespaces/user_level_permissions.rb create mode 100644 app/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type.rb create mode 100644 app/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type.rb create mode 100644 app/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type.rb create mode 100644 config/initializers/active_context.rb create mode 100644 gems/gitlab-active-context/lib/active_context/preprocessors/content_fetcher.rb create mode 100644 gems/gitlab-active-context/spec/lib/active_context/preprocessors/content_fetcher_spec.rb create mode 100644 spec/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type_spec.rb create mode 100644 spec/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type_spec.rb create mode 100644 spec/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type_spec.rb create mode 100644 spec/graphql/types/namespaces/user_level_permissions_spec.rb create mode 100644 spec/support/shared_examples/graphql/types/namespaces/user_permissions_shared_examples.rb diff --git a/Gemfile.checksum b/Gemfile.checksum index 4a71d454f2b..adb7b93e7b9 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -343,8 +343,8 @@ {"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"}, {"name":"irb","version":"1.15.1","platform":"ruby","checksum":"d9bca745ac4207a8b728a52b98b766ca909b86ff1a504bcde3d6f8c84faae890"}, {"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"}, -{"name":"jaro_winkler","version":"1.6.0","platform":"java","checksum":"6cbb36eb4c2649834124d8b92957e577890e8157dd41be8252fde5b02b63b42b"}, -{"name":"jaro_winkler","version":"1.6.0","platform":"ruby","checksum":"8b081ab4ba7da5d16b438e62c4be58b87724bfeeb1527e62603f05ab0a2cc424"}, +{"name":"jaro_winkler","version":"1.6.1","platform":"java","checksum":"e4f64bc73edbd6210861be99691d890cddb34d77b97d0615995c06bc26ee6cdb"}, +{"name":"jaro_winkler","version":"1.6.1","platform":"ruby","checksum":"c056b61bbf7f1fc0151bde7c8f589a2d666d42d0cdb889395b9b73b328e1b393"}, {"name":"jira-ruby","version":"2.3.0","platform":"ruby","checksum":"abf26e6bff4a8ea40bae06f7df6276a5776905c63fb2070934823ca54f62eb62"}, {"name":"jmespath","version":"1.6.2","platform":"ruby","checksum":"238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1"}, {"name":"js_regex","version":"3.8.0","platform":"ruby","checksum":"7934bcdd5a0e6d5af4a520288fd4684a02a472ae55831d9178ccaf82356344b5"}, @@ -713,7 +713,7 @@ {"name":"slack-messenger","version":"2.3.6","platform":"ruby","checksum":"58581e587debcbb769336cc7ebe4eb6ae411947fccf347e967a17ac9813e66d8"}, {"name":"snaky_hash","version":"2.0.0","platform":"ruby","checksum":"fe8b2e39e8ff69320f7812af73ea06401579e29ff1734a7009567391600687de"}, {"name":"snowplow-tracker","version":"0.8.0","platform":"ruby","checksum":"7ba6f4f1443a829845fd28e63eda72d9d3d247f485310ddcccaebbc52b734a38"}, -{"name":"solargraph","version":"0.54.2","platform":"ruby","checksum":"fe22f56ec2efe64f674b0e9dd3ac8a99df5b5833c2ca84993bdb2af2bb0b6c56"}, +{"name":"solargraph","version":"0.54.4","platform":"ruby","checksum":"842705cc511a085e967314de8bd2dd89b00f90238b5582e665ffa39efbd880e0"}, {"name":"solargraph-rspec","version":"0.5.1","platform":"ruby","checksum":"0dfc9124f17b23e95c30acb82c1f799c865408a56b17099b2d6d7b23a76bface"}, {"name":"sorbet-runtime","version":"0.5.11647","platform":"ruby","checksum":"64b65112f2e6a5323310ca9ac0d7d9a6be63aade5a62a6225fe066042ff4fdb6"}, {"name":"spamcheck","version":"1.3.3","platform":"ruby","checksum":"3a29ba9dfcd59543d88054d38c657f79e0a6cf44d763df08ad47680abed50ec7"}, diff --git a/Gemfile.lock b/Gemfile.lock index 23f90785f63..13371a0e379 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1058,7 +1058,7 @@ GEM jaeger-client (1.1.0) opentracing (~> 0.3) thrift - jaro_winkler (1.6.0) + jaro_winkler (1.6.1) jira-ruby (2.3.0) activesupport atlassian-jwt @@ -1831,12 +1831,12 @@ GEM hashie version_gem (~> 1.1) snowplow-tracker (0.8.0) - solargraph (0.54.2) + solargraph (0.54.4) backport (~> 1.2) benchmark (~> 0.4) bundler (~> 2.0) diff-lcs (~> 1.4) - jaro_winkler (~> 1.6) + jaro_winkler (~> 1.6, >= 1.6.1) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) logger (~> 1.6) diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum index 4a71d454f2b..adb7b93e7b9 100644 --- a/Gemfile.next.checksum +++ b/Gemfile.next.checksum @@ -343,8 +343,8 @@ {"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"}, {"name":"irb","version":"1.15.1","platform":"ruby","checksum":"d9bca745ac4207a8b728a52b98b766ca909b86ff1a504bcde3d6f8c84faae890"}, {"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"}, -{"name":"jaro_winkler","version":"1.6.0","platform":"java","checksum":"6cbb36eb4c2649834124d8b92957e577890e8157dd41be8252fde5b02b63b42b"}, -{"name":"jaro_winkler","version":"1.6.0","platform":"ruby","checksum":"8b081ab4ba7da5d16b438e62c4be58b87724bfeeb1527e62603f05ab0a2cc424"}, +{"name":"jaro_winkler","version":"1.6.1","platform":"java","checksum":"e4f64bc73edbd6210861be99691d890cddb34d77b97d0615995c06bc26ee6cdb"}, +{"name":"jaro_winkler","version":"1.6.1","platform":"ruby","checksum":"c056b61bbf7f1fc0151bde7c8f589a2d666d42d0cdb889395b9b73b328e1b393"}, {"name":"jira-ruby","version":"2.3.0","platform":"ruby","checksum":"abf26e6bff4a8ea40bae06f7df6276a5776905c63fb2070934823ca54f62eb62"}, {"name":"jmespath","version":"1.6.2","platform":"ruby","checksum":"238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1"}, {"name":"js_regex","version":"3.8.0","platform":"ruby","checksum":"7934bcdd5a0e6d5af4a520288fd4684a02a472ae55831d9178ccaf82356344b5"}, @@ -713,7 +713,7 @@ {"name":"slack-messenger","version":"2.3.6","platform":"ruby","checksum":"58581e587debcbb769336cc7ebe4eb6ae411947fccf347e967a17ac9813e66d8"}, {"name":"snaky_hash","version":"2.0.0","platform":"ruby","checksum":"fe8b2e39e8ff69320f7812af73ea06401579e29ff1734a7009567391600687de"}, {"name":"snowplow-tracker","version":"0.8.0","platform":"ruby","checksum":"7ba6f4f1443a829845fd28e63eda72d9d3d247f485310ddcccaebbc52b734a38"}, -{"name":"solargraph","version":"0.54.2","platform":"ruby","checksum":"fe22f56ec2efe64f674b0e9dd3ac8a99df5b5833c2ca84993bdb2af2bb0b6c56"}, +{"name":"solargraph","version":"0.54.4","platform":"ruby","checksum":"842705cc511a085e967314de8bd2dd89b00f90238b5582e665ffa39efbd880e0"}, {"name":"solargraph-rspec","version":"0.5.1","platform":"ruby","checksum":"0dfc9124f17b23e95c30acb82c1f799c865408a56b17099b2d6d7b23a76bface"}, {"name":"sorbet-runtime","version":"0.5.11647","platform":"ruby","checksum":"64b65112f2e6a5323310ca9ac0d7d9a6be63aade5a62a6225fe066042ff4fdb6"}, {"name":"spamcheck","version":"1.3.3","platform":"ruby","checksum":"3a29ba9dfcd59543d88054d38c657f79e0a6cf44d763df08ad47680abed50ec7"}, diff --git a/Gemfile.next.lock b/Gemfile.next.lock index 23f90785f63..13371a0e379 100644 --- a/Gemfile.next.lock +++ b/Gemfile.next.lock @@ -1058,7 +1058,7 @@ GEM jaeger-client (1.1.0) opentracing (~> 0.3) thrift - jaro_winkler (1.6.0) + jaro_winkler (1.6.1) jira-ruby (2.3.0) activesupport atlassian-jwt @@ -1831,12 +1831,12 @@ GEM hashie version_gem (~> 1.1) snowplow-tracker (0.8.0) - solargraph (0.54.2) + solargraph (0.54.4) backport (~> 1.2) benchmark (~> 0.4) bundler (~> 2.0) diff-lcs (~> 1.4) - jaro_winkler (~> 1.6) + jaro_winkler (~> 1.6, >= 1.6.1) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) logger (~> 1.6) diff --git a/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js b/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js index e07d8c7afe8..7a618785842 100644 --- a/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js +++ b/app/assets/javascripts/ci/pipeline_schedules/mount_pipeline_schedules_form_app.js @@ -28,6 +28,7 @@ export default (selector, editing = false) => { settingsLink, canSetPipelineVariables, timezoneData, + workerCronExpression, } = containerEl.dataset; return new Vue({ @@ -43,6 +44,7 @@ export default (selector, editing = false) => { projectPath, schedulesPath, settingsLink, + workerCronExpression, }, render(createElement) { return createElement(PipelineSchedulesForm, { diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 7fec0ce32e3..93cdb881e37 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -288,6 +288,11 @@ "MergeRequestReviewer", "UserCore" ], + "UserLevelPermissions": [ + "GroupNamespaceUserLevelPermissions", + "ProjectNamespaceUserLevelPermissions", + "UserNamespaceUserLevelPermissions" + ], "VulnerabilityDetail": [ "VulnerabilityDetailBase", "VulnerabilityDetailBoolean", diff --git a/app/assets/javascripts/lib/utils/secret_detection.js b/app/assets/javascripts/lib/utils/secret_detection.js index b31601de37c..dbe9bd85370 100644 --- a/app/assets/javascripts/lib/utils/secret_detection.js +++ b/app/assets/javascripts/lib/utils/secret_detection.js @@ -39,7 +39,7 @@ const formatMessage = (findings, contentType) => { const header = sprintf(i18n.promptMessage(findings.length), { contentType }); const matchedPatterns = findings.map(({ patternName, matchedString }) => { - return `
  • ${escape(patternName)}: ${escape(matchedString)}
  • `; + return `
  • ${escape(patternName)}: ${escape(matchedString)}
  • `; }); const message = ` diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue index c387587fedd..5978e6dbff8 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue @@ -26,6 +26,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + inject: ['workerCronExpression'], props: { initialCronInterval: { type: String, @@ -54,6 +55,7 @@ export default { radioValue: this.initialCronInterval ? KEY_CUSTOM : KEY_EVERY_DAY, cronInterval: this.initialCronInterval, cronSyntaxUrl: `${DOCS_URL_IN_EE_DIR}/topics/cron/`, + pipelineScheduleWorkerUrl: `${DOCS_URL_IN_EE_DIR}/administration/cicd/#change-maximum-scheduled-pipeline-frequency`, }; }, computed: { @@ -149,12 +151,22 @@ export default { i18n: { learnCronSyntax: s__('PipelineScheduleIntervalPattern|Set a custom interval with Cron syntax.'), cronSyntaxLink: s__('PipelineScheduleIntervalPattern|What is Cron syntax?'), + pipelineScheduleWorkerExplanation: s__( + 'PipelineScheduleIntervalPattern|Pipelines cannot run more frequently than the pipeline schedule worker cron setting (%{workerCronExpression}) allows.', + ), + pipelineScheduleWorkerLink: s__('PipelineScheduleIntervalPattern|Learn more.'), }, }; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index a37138db175..1e7dd463b30 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -137,6 +137,8 @@ $container-margin: $gl-padding; $container-margin-xl: $gl-padding-24; $gl-border-radius-base: 4px; $gl-border-radius-base-inner: $gl-border-radius-base - 1px; +$gl-border-radius-lg: 8px; +$gl-border-radius-lg-inner: $gl-border-radius-lg - 1px; $border-radius-small: 2px; $border-radius-large: 8px; $default-icon-size: 16px; diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index ffdd695ca42..1ee12763c36 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -457,7 +457,7 @@ $diff-file-header-top: 11px; } .mr-section-container { - @apply gl-bg-section gl-border gl-border-section gl-rounded-base; + @apply gl-bg-section gl-border gl-border-section gl-rounded-lg; &:not(:first-child) { @apply gl-mt-5; @@ -475,14 +475,14 @@ $diff-file-header-top: 11px; > .mr-widget-section { &:first-child { - border-top-left-radius: $gl-border-radius-base-inner; - border-top-right-radius: $gl-border-radius-base-inner; + border-top-left-radius: $gl-border-radius-lg-inner; + border-top-right-radius: $gl-border-radius-lg-inner; } > :last-child, .deploy-heading:last-child { - border-bottom-left-radius: $gl-border-radius-base-inner; - border-bottom-right-radius: $gl-border-radius-base-inner; + border-bottom-left-radius: $gl-border-radius-lg-inner; + border-bottom-right-radius: $gl-border-radius-lg-inner; } } diff --git a/app/controllers/concerns/bizible_csp.rb b/app/controllers/concerns/bizible_csp.rb index 521f3127759..a19ab328baa 100644 --- a/app/controllers/concerns/bizible_csp.rb +++ b/app/controllers/concerns/bizible_csp.rb @@ -5,7 +5,8 @@ module BizibleCSP included do content_security_policy do |policy| - next unless helpers.bizible_enabled? || policy.directives.present? + next unless helpers.bizible_enabled? + next unless policy.directives.present? default_script_src = policy.directives['script-src'] || policy.directives['default-src'] script_src_values = Array.wrap(default_script_src) | ["'unsafe-eval'", 'https://cdn.bizible.com/scripts/bizible.js'] diff --git a/app/finders/work_items/work_items_finder.rb b/app/finders/work_items/work_items_finder.rb index 36a6d6e4308..d7214dae6d5 100644 --- a/app/finders/work_items/work_items_finder.rb +++ b/app/finders/work_items/work_items_finder.rb @@ -4,6 +4,12 @@ # with widgets support. Because WorkItems are internally Issues, WorkItemsFinder # can be almost identical to IssuesFinder, except it should return instances of # WorkItems instead of Issues +# Arguments: +# klass - actual WorkItems class +# current_user - currently logged in user, if any +# params: +# work_item_parent_ids: integer[] (list of work item ids) +# module WorkItems class WorkItemsFinder < IssuesFinder include Gitlab::Utils::StrongMemoize @@ -25,8 +31,8 @@ module WorkItems # We require namespace_level_work_items to be true here, since we need the namespace_ids CTE provided by the # by_parent method for performance reasons see: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181904 items = by_timeframe(items) if include_namespace_level_work_items? - - by_widgets(items) + items = by_widgets(items) + by_work_item_parent_ids(items) end def by_widgets(items) @@ -47,6 +53,14 @@ module WorkItems nil end + def by_work_item_parent_ids(items) + work_item_parent_ids = params[:work_item_parent_ids] + + return items unless work_item_parent_ids.present? + + items.with_work_item_parent_ids(work_item_parent_ids) + end + override :use_full_text_search? def use_full_text_search? return false if include_namespace_level_work_items? diff --git a/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb b/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb index 60479918915..03d54e71596 100644 --- a/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb +++ b/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb @@ -98,10 +98,25 @@ module WorkItems value.to_h } + argument :parent_ids, [::Types::GlobalIDType[::WorkItem]], + description: 'Filter work items by global IDs of their parent items (maximum is 100 items).', + required: false, + prepare: ->(global_ids, _ctx) { GitlabSchema.parse_gids(global_ids, expected_type: ::WorkItem).map(&:model_id) } + validates mutually_exclusive: [:assignee_usernames, :assignee_wildcard_id] validates mutually_exclusive: [:milestone_title, :milestone_wildcard_id] end + MAX_PARENT_IDS = 100 + + def resolve(**args) + if args[:parent_ids].to_a.size > MAX_PARENT_IDS + raise GraphQL::ExecutionError, "You can only provide up to #{MAX_PARENT_IDS} parent IDs at once." + end + + super + end + private override :prepare_finder_params @@ -116,6 +131,8 @@ module WorkItems rewrite_param_name(params[:or], :author_usernames, :author_username) rewrite_param_name(params[:or], :label_names, :label_name) + rewrite_param_name(params, :parent_ids, :work_item_parent_ids) + params end diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb index cc0674fc93f..002150d0ac5 100644 --- a/app/graphql/types/namespace_type.rb +++ b/app/graphql/types/namespace_type.rb @@ -140,6 +140,13 @@ module Types method: :itself, experiment: { milestone: '18.1' } + field :user_level_permissions, + Types::Namespaces::UserLevelPermissions, + null: true, + description: 'User permissions on the namespace.', + method: :itself, + experiment: { milestone: '18.1' } + markdown_field :description_html, null: true def achievements_path diff --git a/app/graphql/types/namespaces/user_level_permissions.rb b/app/graphql/types/namespaces/user_level_permissions.rb new file mode 100644 index 00000000000..2f5e90cf9f4 --- /dev/null +++ b/app/graphql/types/namespaces/user_level_permissions.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module UserLevelPermissions + include ::Types::BaseInterface + include ::IssuablesHelper + + graphql_name 'UserLevelPermissions' + + # rubocop:disable Layout/LineLength -- Expected file length + TYPE_MAPPINGS = { + ::Group => ::Types::Namespaces::UserLevelPermissions::GroupNamespaceUserLevelPermissionsType, + ::Namespaces::ProjectNamespace => ::Types::Namespaces::UserLevelPermissions::ProjectNamespaceUserLevelPermissionsType, + ::Namespaces::UserNamespace => ::Types::Namespaces::UserLevelPermissions::UserNamespaceUserLevelPermissionsType + }.freeze + # rubocop:enable Layout/LineLength + + field :can_admin_label, + GraphQL::Types::Boolean, + null: true, + description: 'Whether the current user can admin labels in the namespace.', + fallback_value: false + + field :can_create_projects, + GraphQL::Types::Boolean, + null: true, + description: 'Whether the current user can create projects in the namespace.', + fallback_value: false + + def self.type_mappings + TYPE_MAPPINGS + end + + def self.resolve_type(object, _context) + type_mappings[object.class] || raise("Unknown GraphQL type for namespace type #{object.class}") + end + + orphan_types(*type_mappings.values) + end + end +end + +::Types::Namespaces::UserLevelPermissions.prepend_mod diff --git a/app/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type.rb b/app/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type.rb new file mode 100644 index 00000000000..82d2d7fac90 --- /dev/null +++ b/app/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module UserLevelPermissions + class GroupNamespaceUserLevelPermissionsType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- parent is already authorized + graphql_name 'GroupNamespaceUserLevelPermissions' + implements ::Types::Namespaces::UserLevelPermissions + + alias_method :group, :object + + def can_admin_label + can?(current_user, :admin_label, group) + end + + def can_create_projects + can?(current_user, :create_projects, group) + end + end + end + end +end + +::Types::Namespaces::UserLevelPermissions::GroupNamespaceUserLevelPermissionsType.prepend_mod diff --git a/app/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type.rb b/app/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type.rb new file mode 100644 index 00000000000..2b098cbdf14 --- /dev/null +++ b/app/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module UserLevelPermissions + class ProjectNamespaceUserLevelPermissionsType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- parent is already authorized + graphql_name 'ProjectNamespaceUserLevelPermissions' + implements ::Types::Namespaces::UserLevelPermissions + + alias_method :project, :object + + def can_admin_label + can?(current_user, :admin_label, project) + end + end + end + end +end + +::Types::Namespaces::UserLevelPermissions::ProjectNamespaceUserLevelPermissionsType.prepend_mod diff --git a/app/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type.rb b/app/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type.rb new file mode 100644 index 00000000000..0664b35ee39 --- /dev/null +++ b/app/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module UserLevelPermissions + class UserNamespaceUserLevelPermissionsType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- parent is already authorized + graphql_name 'UserNamespaceUserLevelPermissions' + implements ::Types::Namespaces::UserLevelPermissions + end + end + end +end + +::Types::Namespaces::UserLevelPermissions::UserNamespaceUserLevelPermissionsType.prepend_mod diff --git a/app/helpers/ci/pipeline_schedules_helper.rb b/app/helpers/ci/pipeline_schedules_helper.rb index 1821e0976c6..7976c4304e6 100644 --- a/app/helpers/ci/pipeline_schedules_helper.rb +++ b/app/helpers/ci/pipeline_schedules_helper.rb @@ -13,7 +13,8 @@ module Ci schedules_path: pipeline_schedules_path(project), settings_link: project_settings_ci_cd_path(project), timezone_data: timezone_data.to_json, - can_set_pipeline_variables: Ability.allowed?(current_user, :set_pipeline_variables, project).to_s + can_set_pipeline_variables: Ability.allowed?(current_user, :set_pipeline_variables, project).to_s, + worker_cron_expression: schedule.worker_cron_expression } end end diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index 6082295ed24..d8fe9c36485 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -50,6 +50,11 @@ class DeployToken < ApplicationRecord accepts_nested_attributes_for :project_deploy_tokens scope :active, -> { where("revoked = false AND expires_at >= NOW()") } + scope :with_encrypted_tokens, ->(tokens) { + return none if tokens.empty? + + where(token_encrypted: tokens) + } def self.gitlab_deploy_token active.find_by(name: GITLAB_DEPLOY_TOKEN_NAME) diff --git a/app/models/work_item.rb b/app/models/work_item.rb index c8c48d012b6..223454728c1 100644 --- a/app/models/work_item.rb +++ b/app/models/work_item.rb @@ -53,6 +53,11 @@ class WorkItem < Issue .merge(::WorkItems::WidgetDefinition.by_enabled_widget_type(type)) end + scope :with_work_item_parent_ids, ->(parent_ids) { + joins("INNER JOIN work_item_parent_links ON work_item_parent_links.work_item_id = issues.id") + .where(work_item_parent_links: { work_item_parent_id: parent_ids }) + } + class << self def find_by_namespace_and_iid!(namespace, iid) find_by!(namespace: namespace, iid: iid) diff --git a/config/initializers/active_context.rb b/config/initializers/active_context.rb new file mode 100644 index 00000000000..a03ba727088 --- /dev/null +++ b/config/initializers/active_context.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +ActiveContext.configure do |config| + config.enabled = false + config.indexing_enabled = false +end diff --git a/doc/administration/dedicated/configure_instance/_index.md b/doc/administration/dedicated/configure_instance/_index.md index c96b01f6c13..1725154186b 100644 --- a/doc/administration/dedicated/configure_instance/_index.md +++ b/doc/administration/dedicated/configure_instance/_index.md @@ -90,12 +90,12 @@ Each change log entry includes the following details: Each configuration change has a status: -| Status | Description | -|---|---| -| Initiated | Configuration change is made in Switchboard, but not yet deployed to the instance. | +| Status | Description | +|-------------|-------------| +| Initiated | Configuration change is made in Switchboard, but not yet deployed to the instance. | | In progress | Configuration change is actively being deployed to the instance. | -| Complete | Configuration change has been deployed to the instance. | -| Delayed | Initial job to deploy a change has failed and the change has not yet been assigned to a new job. | +| Complete | Configuration change has been deployed to the instance. | +| Delayed | Initial job to deploy a change has failed and the change has not yet been assigned to a new job. | ### View the configuration change log diff --git a/doc/administration/dedicated/encryption.md b/doc/administration/dedicated/encryption.md index b088f2e1472..c686582f08b 100644 --- a/doc/administration/dedicated/encryption.md +++ b/doc/administration/dedicated/encryption.md @@ -54,12 +54,12 @@ standards for encryption across all storage services. The following table summarizes the functional differences between these options: -| Encryption key source | AWS-managed keys | Bring your own key (BYOK) | -|---|---|---| -| **Key generation** | Generated automatically if BYOK not provided. | You create your own AWS KMS keys. | -| **Ownership** | AWS manages on your behalf. | You own and manage your keys. | -| **Access control** | Only AWS services using the keys can decrypt and access them. You don't have direct access. | You control access through IAM policies in your AWS account. | -| **Setup** | No setup required. | Must be set up before onboarding. Cannot be enabled later. | +| Encryption key source | AWS-managed keys | Bring your own key (BYOK) | +|-----------------------|---------------------------------------------------------------------------------------------|---------------------------| +| **Key generation** | Generated automatically if BYOK not provided. | You create your own AWS KMS keys. | +| **Ownership** | AWS manages on your behalf. | You own and manage your keys. | +| **Access control** | Only AWS services using the keys can decrypt and access them. You don't have direct access. | You control access through IAM policies in your AWS account. | +| **Setup** | No setup required. | Must be set up before onboarding. Cannot be enabled later. | ### AWS-managed keys diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 4bf85e95297..e0593b99c25 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -12862,6 +12862,7 @@ Input type: `WorkItemExportInput` | `myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. | | `not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. | | `or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. | +| `parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). | | `projectPath` | [`ID!`](#id) | Full project path. | | `search` | [`String`](#string) | Search query for title or description. | | `selectedFields` | [`[AvailableExportFields!]`](#availableexportfields) | List of selected fields to be exported. Omit to export all available fields. | @@ -13041,6 +13042,7 @@ Input type: `WorkItemsCsvExportInput` | `myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. | | `not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. | | `or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. | +| `parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). | | `projectPath` | [`ID!`](#id) | Full project path. | | `search` | [`String`](#string) | Search query for title or description. | | `selectedFields` | [`[AvailableExportFields!]`](#availableexportfields) | List of selected fields to be exported. Omit to export all available fields. | @@ -24622,6 +24624,7 @@ Represents a vulnerability. The connection type is countable. | `dismissedBy` | [`UserCore`](#usercore) | User that dismissed the vulnerability. | | `externalIssueLinks` | [`VulnerabilityExternalIssueLinkConnection!`](#vulnerabilityexternalissuelinkconnection) | List of external issue links related to the vulnerability. (see [Connections](#connections)) | | `falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. | +| `findingTokenStatus` | [`VulnerabilityFindingTokenStatus`](#vulnerabilityfindingtokenstatus) | Status of the secret token associated with this vulnerability. Returns `null` if the `validity_checks` feature flag is disabled. | | `hasRemediations` | [`Boolean`](#boolean) | Indicates whether there is a remediation available for the vulnerability. | | `id` | [`ID!`](#id) | GraphQL ID of the vulnerability. | | `identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerability. | @@ -25249,6 +25252,7 @@ four standard [pagination arguments](#pagination-arguments): | `myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. | | `not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. | | `or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. | +| `parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). | | `search` | [`String`](#string) | Search query for title or description. | | `sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. | | `state` | [`IssuableState`](#issuablestate) | Current state of the work item. | @@ -27911,6 +27915,7 @@ GPG signature for a signed commit. | `totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. This only applies to namespaces under Project limit enforcement. | | `twoFactorGracePeriod` | [`Int`](#int) | Time before two-factor authentication is enforced. | | `updatedAt` | [`Time`](#time) | Timestamp of when the group was last updated. | +| `userLevelPermissions` {{< icon name="warning-solid" >}} | [`UserLevelPermissions`](#userlevelpermissions) | **Introduced** in GitLab 18.1. **Status**: Experiment. User permissions on the namespace. | | `userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. | | `valueStreamAnalytics` | [`ValueStreamAnalytics`](#valuestreamanalytics) | Information about Value Stream Analytics within the group. | | `visibility` | [`String`](#string) | Visibility of the namespace. | @@ -29574,6 +29579,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype). | `myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. | | `not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. | | `or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. | +| `parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). | | `requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. | | `search` | [`String`](#string) | Search query for title or description. | | `sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. | @@ -29646,6 +29652,7 @@ four standard [pagination arguments](#pagination-arguments): | `myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. | | `not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. | | `or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. | +| `parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). | | `requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. | | `search` | [`String`](#string) | Search query for title or description. | | `sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. | @@ -29846,6 +29853,17 @@ Limited group data accessible to users without full group read access (e.g. non- | `reportAbuse` | [`String`](#string) | Namespace report_abuse. | | `signIn` | [`String`](#string) | Namespace sign_in_path. | +### `GroupNamespaceUserLevelPermissions` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. | +| `canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. | +| `canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. | +| `canCreateProjects` | [`Boolean`](#boolean) | Whether the current user can create projects in the namespace. | + ### `GroupPermissions` #### Fields @@ -33428,6 +33446,7 @@ Product analytics events for a specific month and year. | `timelogCategories` {{< icon name="warning-solid" >}} | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in GitLab 15.3. **Status**: Experiment. Timelog categories for the namespace. | | `totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. | | `totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. This only applies to namespaces under Project limit enforcement. | +| `userLevelPermissions` {{< icon name="warning-solid" >}} | [`UserLevelPermissions`](#userlevelpermissions) | **Introduced** in GitLab 18.1. **Status**: Experiment. User permissions on the namespace. | | `userPermissions` | [`NamespacePermissions!`](#namespacepermissions) | Permissions for the current user on the resource. | | `visibility` | [`String`](#string) | Visibility of the namespace. | | `webUrl` | [`String`](#string) | URL of the object. | @@ -37655,6 +37674,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype). | `myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. | | `not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. | | `or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. | +| `parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). | | `requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. | | `search` | [`String`](#string) | Search query for title or description. | | `sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. | @@ -37723,6 +37743,7 @@ four standard [pagination arguments](#pagination-arguments): | `myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. | | `not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. | | `or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. | +| `parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). | | `requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. | | `search` | [`String`](#string) | Search query for title or description. | | `sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. | @@ -37864,6 +37885,17 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction). | `reportAbuse` | [`String`](#string) | Namespace report_abuse. | | `signIn` | [`String`](#string) | Namespace sign_in_path. | +### `ProjectNamespaceUserLevelPermissions` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. | +| `canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. | +| `canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. | +| `canCreateProjects` | [`Boolean`](#boolean) | Whether the current user can create projects in the namespace. | + ### `ProjectPermissions` #### Fields @@ -40683,6 +40715,17 @@ fields relate to interactions between the two entities. | `reportAbuse` | [`String`](#string) | Namespace report_abuse. | | `signIn` | [`String`](#string) | Namespace sign_in_path. | +### `UserNamespaceUserLevelPermissions` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. | +| `canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. | +| `canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. | +| `canCreateProjects` | [`Boolean`](#boolean) | Whether the current user can create projects in the namespace. | + ### `UserPermissions` #### Fields @@ -40946,6 +40989,7 @@ Represents a vulnerability. | `dismissedBy` | [`UserCore`](#usercore) | User that dismissed the vulnerability. | | `externalIssueLinks` | [`VulnerabilityExternalIssueLinkConnection!`](#vulnerabilityexternalissuelinkconnection) | List of external issue links related to the vulnerability. (see [Connections](#connections)) | | `falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. | +| `findingTokenStatus` | [`VulnerabilityFindingTokenStatus`](#vulnerabilityfindingtokenstatus) | Status of the secret token associated with this vulnerability. Returns `null` if the `validity_checks` feature flag is disabled. | | `hasRemediations` | [`Boolean`](#boolean) | Indicates whether there is a remediation available for the vulnerability. | | `id` | [`ID!`](#id) | GraphQL ID of the vulnerability. | | `identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerability. | @@ -41335,6 +41379,19 @@ Represents an external issue link of a vulnerability. | `id` | [`VulnerabilitiesExternalIssueLinkID!`](#vulnerabilitiesexternalissuelinkid) | GraphQL ID of the external issue link. | | `linkType` | [`VulnerabilityExternalIssueLinkType!`](#vulnerabilityexternalissuelinktype) | Type of the external issue link. | +### `VulnerabilityFindingTokenStatus` + +Represents the status of a secret token found in a vulnerability. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `createdAt` | [`Time!`](#time) | When the token status was created. | +| `id` | [`ID!`](#id) | ID of the finding token status. | +| `status` | [`VulnerabilityFindingTokenStatusState!`](#vulnerabilityfindingtokenstatusstate) | Status of the token (unknown, active, inactive). | +| `updatedAt` | [`Time!`](#time) | When the token status was last updated. | + ### `VulnerabilityIdentifier` Represents a vulnerability identifier. @@ -46458,6 +46515,16 @@ The type of the external issue link related to a vulnerability. | ----- | ----------- | | `CREATED` | Created link type. | +### `VulnerabilityFindingTokenStatusState` + +Status of a secret token found in a vulnerability. + +| Value | Description | +| ----- | ----------- | +| `ACTIVE` | Token is active and can be exploited. | +| `INACTIVE` | Token is inactive and cannot be exploited. | +| `UNKNOWN` | Token status is unknown. | + ### `VulnerabilityGrade` The grade of the vulnerable project. @@ -49304,6 +49371,23 @@ four standard [pagination arguments](#pagination-arguments): | ---- | ---- | ----------- | | `includeHidden` | [`Boolean`](#boolean) | Indicates whether or not achievements hidden from the profile should be included in the result. | +#### `UserLevelPermissions` + +Implementations: + +- [`GroupNamespaceUserLevelPermissions`](#groupnamespaceuserlevelpermissions) +- [`ProjectNamespaceUserLevelPermissions`](#projectnamespaceuserlevelpermissions) +- [`UserNamespaceUserLevelPermissions`](#usernamespaceuserlevelpermissions) + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. | +| `canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. | +| `canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. | +| `canCreateProjects` | [`Boolean`](#boolean) | Whether the current user can create projects in the namespace. | + #### `VulnerabilityStatisticInterface` Implementations: diff --git a/doc/api/graphql/removed_items.md b/doc/api/graphql/removed_items.md index 906538fdadf..d1a9ef378fb 100644 --- a/doc/api/graphql/removed_items.md +++ b/doc/api/graphql/removed_items.md @@ -24,14 +24,14 @@ Fields removed in GitLab 17.0. ### GraphQL Fields -| Field name | GraphQL type | Deprecated in | Removal MR | Use instead | -|---|---|---|---|---| -| `architectureName` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | -| `executorName` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | -| `ipAddress` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | -| `platformName` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | -| `revision` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | -| `version` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | +| Field name | GraphQL type | Deprecated in | Removal MR | Use instead | +|--------------------|--------------|---------------|-------------------------------------------------------------------------|-------------| +| `architectureName` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | +| `executorName` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | +| `ipAddress` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | +| `platformName` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | +| `revision` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | +| `version` | `CiRunner` | 16.2 | [!124751](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124751) | Use this field in `manager` object instead. | ## GitLab 16.0 @@ -39,21 +39,21 @@ Fields removed in GitLab 16.0. ### GraphQL Fields -| Field name | GraphQL type | Deprecated in | Removal MR | Use instead | -|---|---|---|---|---| -| `name` | `PipelineSecurityReportFinding` | [15.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89571) | [!119055](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119055) | `title` | -| `external` | `ReleaseAssetLink` | 15.9 | [!111750](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111750) | None | -| `confidence` | `PipelineSecurityReportFinding` | 15.4 | [!118617](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118617) | None | -| `PAUSED` | `CiRunnerStatus` | 14.8 | [!118635](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118635) | `CiRunner.paused: true` | -| `ACTIVE` | `CiRunnerStatus` | 14.8 | [!118635](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118635) | `CiRunner.paused: false` | +| Field name | GraphQL type | Deprecated in | Removal MR | Use instead | +|--------------|---------------------------------|---------------------------------------------------------------------|-------------------------------------------------------------------------|-------------| +| `name` | `PipelineSecurityReportFinding` | [15.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89571) | [!119055](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119055) | `title` | +| `external` | `ReleaseAssetLink` | 15.9 | [!111750](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111750) | None | +| `confidence` | `PipelineSecurityReportFinding` | 15.4 | [!118617](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118617) | None | +| `PAUSED` | `CiRunnerStatus` | 14.8 | [!118635](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118635) | `CiRunner.paused: true` | +| `ACTIVE` | `CiRunnerStatus` | 14.8 | [!118635](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118635) | `CiRunner.paused: false` | ### GraphQL Mutations -| Argument name | Mutation | Deprecated in | Use instead | -| -------------------- | -------------------- |---------------------------------------------------------------------|------------------------------------------------| -| - | `vulnerabilityFindingDismiss` | [15.5](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99170) | `vulnerabilityDismiss` or `securityFindingDismiss` | -| - | `apiFuzzingCiConfigurationCreate` | [15.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87241) | `todos` | -| - | `CiCdSettingsUpdate` | [15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/361801) | `ProjectCiCdSettingsUpdate` | +| Argument name | Mutation | Deprecated in | Use instead | +|---------------|-----------------------------------|---------------------------------------------------------------------|-------------| +| - | `vulnerabilityFindingDismiss` | [15.5](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99170) | `vulnerabilityDismiss` or `securityFindingDismiss` | +| - | `apiFuzzingCiConfigurationCreate` | [15.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87241) | `todos` | +| - | `CiCdSettingsUpdate` | [15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/361801) | `ProjectCiCdSettingsUpdate` | ## GitLab 15.0 @@ -63,20 +63,20 @@ Fields removed in GitLab 15.0. [Removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85382) in GitLab 15.0: -| Argument name | Mutation | Deprecated in | Use instead | -| -------------------- | -------------------- | ------------- | -------------------------- | -| - | `clusterAgentTokenDelete`| 14.7 | `clusterAgentTokenRevoke` | +| Argument name | Mutation | Deprecated in | Use instead | +|---------------|---------------------------|---------------|-------------| +| - | `clusterAgentTokenDelete` | 14.7 | `clusterAgentTokenRevoke` | ### GraphQL Fields [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/342882) in GitLab 15.0: -| Argument name | Field name | Deprecated in | Use instead | -| -------------------- | --------------------| ------------- | -------------------------- | -| - | `pipelines` | 14.5 | None | +| Argument name | Field name | Deprecated in | Use instead | +|---------------|-------------|---------------|-------------| +| - | `pipelines` | 14.5 | None | ### GraphQL Types -| Field name | GraphQL type | Deprecated in | Use instead | -| ------------------------------------------ | ------------------------ | ------------- | ---------------------------------------------------------------------------------- | +| Field name | GraphQL type | Deprecated in | Use instead | +|--------------------------------------------|--------------------------|---------------|-------------| | `defaultMergeCommitMessageWithDescription` | `GraphQL::Types::String` | 14.5 | None. Define a [merge commit template](../../user/project/merge_requests/commit_templates.md) in your project and use `defaultMergeCommitMessage`. | diff --git a/doc/ci/jobs/ci_job_token.md b/doc/ci/jobs/ci_job_token.md index 672bb66d48f..7e046c95438 100644 --- a/doc/ci/jobs/ci_job_token.md +++ b/doc/ci/jobs/ci_job_token.md @@ -240,7 +240,7 @@ The compaction algorithm: ```plaintext group1/group2/group3 group1/group2/group4 - group1/group2/group6 + group1/group5/group6 ``` 1. If the allowlist is over the 200 entry limit, the algorithm compacts again: diff --git a/doc/development/documentation/site_architecture/automation.md b/doc/development/documentation/site_architecture/automation.md index 2f6983633c6..58d083b7ce5 100644 --- a/doc/development/documentation/site_architecture/automation.md +++ b/doc/development/documentation/site_architecture/automation.md @@ -39,7 +39,7 @@ Other pages are generated by using non-standard processes. These pages often use that are coded across multiple repositories. | Page | Details | Owner | -|---|---|---| +|------|---------|-------| | [All feature flags in GitLab](../../../user/feature_flags.md) | [Generated during docs build](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/raketasks.md#generate-the-feature-flag-tables) | [Technical Writing](https://handbook.gitlab.com/handbook/product/ux/technical-writing/) | | [GitLab Runner feature flags](https://docs.gitlab.com/runner/configuration/feature-flags.html) | [Page source](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/ec6e1797d2173a95c8ac7f726bd62f6f110b7211/docs/configuration/feature-flags.md?plain=1#L39) | [Runner](https://handbook.gitlab.com/handbook/engineering/development/ops/verify/runner/) | | [GitLab Runner Kubernetes API settings](https://docs.gitlab.com/runner/executors/kubernetes/) | Generated with [mage](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/.gitlab/ci/qa.gitlab-ci.yml#L133) | [Runner](https://handbook.gitlab.com/handbook/engineering/development/ops/verify/runner/) | diff --git a/doc/development/documentation/styleguide/_index.md b/doc/development/documentation/styleguide/_index.md index 969aab120ec..6e72fc081b6 100644 --- a/doc/development/documentation/styleguide/_index.md +++ b/doc/development/documentation/styleguide/_index.md @@ -324,17 +324,17 @@ documentation even if the probability of a token being exploited is low. Use these fake tokens as examples: -| Token type | Token value | -|:----------------------|:-------------------------------------------------------------------| -| Personal access token | `` | +| Token type | Token value | +|:----------------------|:------------| +| Personal access token | `` | | Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` | | Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` | -| CI/CD variable | `Li8j-mLUVA3eZYjPfd_H` | -| Project runner token | `yrnZW46BrtBFqM7xDzE7dddd` | -| Instance runner token | `6Vk7ZsosqQyfreAxXTZr` | -| Trigger token | `be20d8dcc028677c931e04f3871a9b` | -| Webhook secret token | `6XhDroRcYPM5by_h-HLY` | -| Health check token | `Tu7BgjR9qeZTEyRzGG2P` | +| CI/CD variable | `Li8j-mLUVA3eZYjPfd_H` | +| Project runner token | `yrnZW46BrtBFqM7xDzE7dddd` | +| Instance runner token | `6Vk7ZsosqQyfreAxXTZr` | +| Trigger token | `be20d8dcc028677c931e04f3871a9b` | +| Webhook secret token | `6XhDroRcYPM5by_h-HLY` | +| Health check token | `Tu7BgjR9qeZTEyRzGG2P` | ### Contractions @@ -346,12 +346,12 @@ Some contractions, however, should be avoided: -| Do not use a contraction | Example | Use instead | -|-------------------------------|--------------------------------------------------|------------------------------------------------------------------| -| With a proper noun and a verb | **Terraform's** a helpful tool. | **Terraform** is a helpful tool. | -| To emphasize a negative | **Don't** install X with Y. | **Do not** install X with Y. | -| In reference documentation | **Don't** set a limit. | **Do not** set a limit. | -| In error messages | Requests to localhost **aren't** allowed. | Requests to localhost **are not** allowed. | +| Do not use a contraction | Example | Use instead | +|-------------------------------|-------------------------------------------|-------------| +| With a proper noun and a verb | **Terraform's** a helpful tool. | **Terraform** is a helpful tool. | +| To emphasize a negative | **Don't** install X with Y. | **Do not** install X with Y. | +| In reference documentation | **Don't** set a limit. | **Do not** set a limit. | +| In error messages | Requests to localhost **aren't** allowed. | Requests to localhost **are not** allowed. | @@ -745,6 +745,10 @@ To make tables easier to maintain: | Setting 3 | `0` | Another short description. | ``` +Always align the delimiter (second) row of the table with the header (first) row. +Avoid using shortened delimiter rows like `|-|-|-|` or `|--|--|`. +If a large table does not auto-format well, you should still align the delimiter row with the header row. + ### Editor extensions for table formatting To ensure consistent table formatting across all Markdown files, consider formatting your tables @@ -775,7 +779,8 @@ plugin, but it does not have a **Follow header row length** setting. ### Updates to existing tables -When you add or edit rows in an existing table, the cells in the new rows might be wider. +When you add or edit rows in an existing table, some rows might not be aligned anymore. +You do not need to realign the entire table if only changing a few rows. If you realign the columns to account for the width, the diff becomes difficult to read, because the entire table shows as modified. @@ -793,9 +798,9 @@ When creating tables of lists of features (such the features available to each role on the [Permissions](../../../user/permissions.md#project-members-permissions) page), use these phrases: -| Option | Markdown | Displayed result | -|--------|--------------------------|------------------------| -| No | `{{}} No` | {{< icon name="dash-circle" >}} No | +| Option | Markdown | Displayed result | +|--------|---------------------------------------------------|------------------| +| No | `{{}} No` | {{< icon name="dash-circle" >}} No | | Yes | `{{}} Yes` | {{< icon name="check-circle-filled" >}} Yes | Do not use these SVG icons in API documentation. @@ -817,8 +822,8 @@ Put the tag at the end of the sentence. Leave one space between the sentence and For example: ```markdown -| App name | Description | -|:---------|:-------------------------------| +| App name | Description | +|:---------|:------------| | App A | Description text. 1 | | App B | Description text. 2 | ``` @@ -838,8 +843,8 @@ For example: The table and footnotes would render as follows: -| App name | Description | -|:---------|:-------------------------------| +| App name | Description | +|:---------|:------------| | App A | Description text. 1 | | App B | Description text. 2 | @@ -1493,15 +1498,15 @@ GUI diagramming tools can help authors overcome Mermaid's complexity and layout the preferred GUI tool because, when using the editor, both the diagram and its definition are stored in the SVG file, so it can be easily edited. Draw.io is also integrated with the GitLab wiki. -| Feature| Mermaid | Draw.io | -|--------|---------|---------| -| **Editor required** | Text editor | Draw.io editor | -| **WYSIWYG editing** | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes | -| **Text content findable by `grep`** | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="dash-circle" >}} No | -| **Appearance controlled by** | Web site's CSS | Diagram's author | -| **File format** | SVG | SVG | +| Feature | Mermaid | Draw.io | +|-------------------------------------------|-------------------------------------------------------------------------|---------| +| **Editor required** | Text editor | Draw.io editor | +| **WYSIWYG editing** | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes | +| **Text content findable by `grep`** | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="dash-circle" >}} No | +| **Appearance controlled by** | Web site's CSS | Diagram's author | +| **File format** | SVG | SVG | | **VS Code integration (with extensions)** | {{< icon name="check-circle-filled" >}} Yes (Preview and local editing) | {{< icon name="check-circle-filled" >}} Yes (Preview and local editing) | -| **Generated dynamically** | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="dash-circle" >}} No | +| **Generated dynamically** | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="dash-circle" >}} No | #### Guidelines diff --git a/doc/development/export_csv.md b/doc/development/export_csv.md index b8a9d0322a1..0484dee9934 100644 --- a/doc/development/export_csv.md +++ b/doc/development/export_csv.md @@ -7,14 +7,14 @@ title: Export to CSV This document lists the different implementations of CSV export in GitLab codebase. -| Export type | Implementation | Advantages | Disadvantages | Existing examples | -|---|---|---|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Streaming | - Query and yield data in batches to a response stream.
    - Download starts immediately. | - Report available immediately. | - No progress indicator.
    - Requires a reliable connection. | [Export audit event log](../administration/compliance/audit_event_reports.md#exporting-audit-events) | -| Downloading | - Query and write data in batches to a temporary file.
    - Loads the file into memory.
    - Sends the file to the client. | - Report available immediately. | - Large amount of data might cause request timeout.
    - Memory intensive.
    - Request expires when the user goes to a different page. | - [Export Chain of Custody Report](../user/compliance/compliance_center/compliance_chain_of_custody_report.md)
    - [Export License Usage File](../subscriptions/self_managed/_index.md#export-your-license-usage) | -| As email attachment | - Asynchronously process the query with background job.
    - Email uses the export as an attachment. | - Asynchronous processing. | - Requires users use a different app (email) to download the CSV.
    - Email providers may limit attachment size. | - [Export issues](../user/project/issues/csv_export.md)
    - [Export merge requests](../user/project/merge_requests/csv_export.md) | -| As downloadable link in email (*) | - Asynchronously process the query with background job.
    - Email uses an export link. | - Asynchronous processing.
    - Bypasses email provider attachment size limit. | - Requires users use a different app (email).
    - Requires additional storage and cleanup. | [Export User Permissions](https://gitlab.com/gitlab-org/gitlab/-/issues/1772) | -| Polling (non-persistent state) | - Asynchronously processes the query with the background job.
    - Frontend(FE) polls every few seconds to check if CSV file is ready. | - Asynchronous processing.
    - Automatically downloads to local machine on completion.
    - In-app solution. | - Non-persistable request - request expires when the user goes to a different page.
    - API is processed for each polling request. | [Export Vulnerabilities](../user/application_security/vulnerability_report/_index.md#exporting) | -| Polling (persistent state) (*) | - Asynchronously processes the query with background job.
    - Backend (BE) maintains the export state
    - FE polls every few seconds to check status.
    - FE shows 'Download link' when export is ready.
    - User can download or regenerate a new report. | - Asynchronous processing.
    - No database calls made during the polling requests (HTTP 304 status is returned until export status changes).
    - Does not require user to stay on page until export is complete.
    - In-app solution.
    - Can be expanded into a generic CSV feature (such as dashboard / CSV API). | - Requires to maintain export states in DB.
    - Does not automatically download the CSV export to local machine, requires users to select 'Download'. | [Export Merge Commits Report](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43055) | +| Export type | Implementation | Advantages | Disadvantages | Existing examples | +|-----------------------------------|----------------|------------|---------------|-------------------| +| Streaming | - Query and yield data in batches to a response stream.
    - Download starts immediately. | - Report available immediately. | - No progress indicator.
    - Requires a reliable connection. | [Export audit event log](../administration/compliance/audit_event_reports.md#exporting-audit-events) | +| Downloading | - Query and write data in batches to a temporary file.
    - Loads the file into memory.
    - Sends the file to the client. | - Report available immediately. | - Large amount of data might cause request timeout.
    - Memory intensive.
    - Request expires when the user goes to a different page. | - [Export Chain of Custody Report](../user/compliance/compliance_center/compliance_chain_of_custody_report.md)
    - [Export License Usage File](../subscriptions/self_managed/_index.md#export-your-license-usage) | +| As email attachment | - Asynchronously process the query with background job.
    - Email uses the export as an attachment. | - Asynchronous processing. | - Requires users use a different app (email) to download the CSV.
    - Email providers may limit attachment size. | - [Export issues](../user/project/issues/csv_export.md)
    - [Export merge requests](../user/project/merge_requests/csv_export.md) | +| As downloadable link in email (*) | - Asynchronously process the query with background job.
    - Email uses an export link. | - Asynchronous processing.
    - Bypasses email provider attachment size limit. | - Requires users use a different app (email).
    - Requires additional storage and cleanup. | [Export User Permissions](https://gitlab.com/gitlab-org/gitlab/-/issues/1772) | +| Polling (non-persistent state) | - Asynchronously processes the query with the background job.
    - Frontend(FE) polls every few seconds to check if CSV file is ready. | - Asynchronous processing.
    - Automatically downloads to local machine on completion.
    - In-app solution. | - Non-persistable request - request expires when the user goes to a different page.
    - API is processed for each polling request. | [Export Vulnerabilities](../user/application_security/vulnerability_report/_index.md#exporting) | +| Polling (persistent state) (*) | - Asynchronously processes the query with background job.
    - Backend (BE) maintains the export state
    - FE polls every few seconds to check status.
    - FE shows 'Download link' when export is ready.
    - User can download or regenerate a new report. | - Asynchronous processing.
    - No database calls made during the polling requests (HTTP 304 status is returned until export status changes).
    - Does not require user to stay on page until export is complete.
    - In-app solution.
    - Can be expanded into a generic CSV feature (such as dashboard / CSV API). | - Requires to maintain export states in DB.
    - Does not automatically download the CSV export to local machine, requires users to select 'Download'. | [Export Merge Commits Report](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43055) | {{< alert type="note" >}} diff --git a/doc/development/fe_guide/haml.md b/doc/development/fe_guide/haml.md index 8fc3005536c..945770a3bce 100644 --- a/doc/development/fe_guide/haml.md +++ b/doc/development/fe_guide/haml.md @@ -89,23 +89,23 @@ Currently only the listed components are available but more components are plann ##### Arguments -| Argument | Description | Type | Required (default value) | -|---|---|---|---| -| `method` | Attribute on the object passed to `gitlab_ui_form_for`. | `Symbol` | `true` | -| `label` | Checkbox label. `label` slot can be used instead of this argument if HTML is needed. | `String` | `false` (`nil`) | -| `help_text` | Help text displayed below the checkbox. `help_text` slot can be used instead of this argument if HTML is needed. | `String` | `false` (`nil`) | -| `checkbox_options` | Options that are passed to [Rails `check_box` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-check_box). | `Hash` | `false` (`{}`) | -| `checked_value` | Value when checkbox is checked. | `String` | `false` (`'1'`) | -| `unchecked_value` | Value when checkbox is unchecked. | `String` | `false` (`'0'`) | -| `label_options` | Options that are passed to [Rails `label` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label). | `Hash` | `false` (`{}`) | +| Argument | Type | Required (default value) | Description | +|--------------------|----------|--------------------------|-------------| +| `method` | `Symbol` | `true` | Attribute on the object passed to `gitlab_ui_form_for`. | +| `label` | `String` | `false` (`nil`) | Checkbox label. `label` slot can be used instead of this argument if HTML is needed. | +| `help_text` | `String` | `false` (`nil`) | Help text displayed below the checkbox. `help_text` slot can be used instead of this argument if HTML is needed. | +| `checkbox_options` | `Hash` | `false` (`{}`) | Options that are passed to [Rails `check_box` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-check_box). | +| `checked_value` | `String` | `false` (`'1'`) | Value when checkbox is checked. | +| `unchecked_value` | `String` | `false` (`'0'`) | Value when checkbox is unchecked. | +| `label_options` | `Hash` | `false` (`{}`) | Options that are passed to [Rails `label` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label). | ##### Slots This component supports [ViewComponent slots](https://viewcomponent.org/guide/slots.html). -| Slot | Description | -|---|---| -| `label` | Checkbox label content. This slot can be used instead of the `label` argument. | +| Slot | Description | +|-------------|-------------| +| `label` | Checkbox label content. This slot can be used instead of the `label` argument. | | `help_text` | Help text content displayed below the checkbox. This slot can be used instead of the `help_text` argument. | #### `gitlab_ui_radio_component` @@ -114,20 +114,20 @@ This component supports [ViewComponent slots](https://viewcomponent.org/guide/sl ##### Arguments -| Argument | Description | Type | Required (default value) | -|---|---|---|---| -| `method` | Attribute on the object passed to `gitlab_ui_form_for`. | `Symbol` | `true` | -| `value` | The value of the radio tag. | `Symbol` | `true` | -| `label` | Radio label. `label` slot can be used instead of this argument if HTML content is needed inside the label. | `String` | `false` (`nil`) | -| `help_text` | Help text displayed below the radio button. `help_text` slot can be used instead of this argument if HTML content is needed inside the help text. | `String` | `false` (`nil`) | -| `radio_options` | Options that are passed to [Rails `radio_button` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-radio_button). | `Hash` | `false` (`{}`) | -| `label_options` | Options that are passed to [Rails `label` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label). | `Hash` | `false` (`{}`) | +| Argument | Type | Required (default value) | Description | +|-----------------|----------|--------------------------|-------------| +| `method` | `Symbol` | `true` | Attribute on the object passed to `gitlab_ui_form_for`. | +| `value` | `Symbol` | `true` | The value of the radio tag. | +| `label` | `String` | `false` (`nil`) | Radio label. `label` slot can be used instead of this argument if HTML content is needed inside the label. | +| `help_text` | `String` | `false` (`nil`) | Help text displayed below the radio button. `help_text` slot can be used instead of this argument if HTML content is needed inside the help text. | +| `radio_options` | `Hash` | `false` (`{}`) | Options that are passed to [Rails `radio_button` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-radio_button). | +| `label_options` | `Hash` | `false` (`{}`) | Options that are passed to [Rails `label` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label). | ##### Slots This component supports [ViewComponent slots](https://viewcomponent.org/guide/slots.html). -| Slot | Description | -|---|---| -| `label` | Checkbox label content. This slot can be used instead of the `label` argument. | +| Slot | Description | +|-------------|-------------| +| `label` | Checkbox label content. This slot can be used instead of the `label` argument. | | `help_text` | Help text content displayed below the radio button. This slot can be used instead of the `help_text` argument. | diff --git a/doc/development/metrics.md b/doc/development/metrics.md index d16982f514b..f6d81ac61ee 100644 --- a/doc/development/metrics.md +++ b/doc/development/metrics.md @@ -109,12 +109,12 @@ For example, if you search for more than seven days of data, the API returns onl The following table shows what type of aggregation is used for each search period: -|Period|Aggregation used| -|---|---| -| Less than 30 minutes | Raw data as ingested | -| More than 30 minutes and less than one hour | By minute | -| More than one hour and less than 72 hours | Hourly | -| More than 72 hours | Daily | +| Period | Aggregation used | +|---------------------------------------------|------------------| +| Less than 30 minutes | Raw data as ingested | +| More than 30 minutes and less than one hour | By minute | +| More than one hour and less than 72 hours | Hourly | +| More than 72 hours | Daily | ### Metrics ingestion limits diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index b59f24a6825..5550d8cc350 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -113,11 +113,11 @@ The result of a [database migration pipeline](database/database_migration_pipeli includes the timing information for migrations. {{< /alert >}} -| Migration Type | Recommended Duration | Notes | -|----|----|---| -| Regular migrations | `<= 3 minutes` | A valid exception are changes without which application functionality or performance would be severely degraded and which cannot be delayed. | -| Post-deployment migrations | `<= 10 minutes` | A valid exception are schema changes, since they must not happen in background migrations. | -| Background migrations | `> 10 minutes` | Since these are suitable for larger tables, it's not possible to set a precise timing guideline, however, any single query must stay below [`1 second` execution time](database/query_performance.md#timing-guidelines-for-queries) with cold caches. | +| Migration Type | Recommended Duration | Notes | +|----------------------------|----------------------|-------| +| Regular migrations | `<= 3 minutes` | A valid exception are changes without which application functionality or performance would be severely degraded and which cannot be delayed. | +| Post-deployment migrations | `<= 10 minutes` | A valid exception are schema changes, since they must not happen in background migrations. | +| Background migrations | `> 10 minutes` | Since these are suitable for larger tables, it's not possible to set a precise timing guideline, however, any single query must stay below [`1 second` execution time](database/query_performance.md#timing-guidelines-for-queries) with cold caches. | ## Large Tables Limitations diff --git a/doc/development/routing.md b/doc/development/routing.md index 08301f6eb80..64b9168d4a2 100644 --- a/doc/development/routing.md +++ b/doc/development/routing.md @@ -85,11 +85,11 @@ make it unnoticeable for users, because we don't want them to receive `404 Not F if we can avoid it. This table describes the minimum required in different cases: -| URL description | Example | What to do | -|---|---|---| -| Can be used in scripts and automation | `snippet#raw` | Support both an old and new URL for one major release. Then, support a redirect from an old URL to a new URL for another major release. | -| Likely to be saved or shared | `issue#show` | Add a redirect from an old URL to a new URL until the next major release. | -| Limited use, unlikely to be shared | `admin#labels` | No extra steps required. | +| URL description | Example | What to do | +|---------------------------------------|----------------|------------| +| Can be used in scripts and automation | `snippet#raw` | Support both an old and new URL for one major release. Then, support a redirect from an old URL to a new URL for another major release. | +| Likely to be saved or shared | `issue#show` | Add a redirect from an old URL to a new URL until the next major release. | +| Limited use, unlikely to be shared | `admin#labels` | No extra steps required. | In all cases, an old route should only be removed once traffic to it has dropped sufficiently (for instance, according to logs or BigQuery). Otherwise, more diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md index 5fcddf98f7d..579770596ed 100644 --- a/doc/development/secure_coding_guidelines.md +++ b/doc/development/secure_coding_guidelines.md @@ -14,24 +14,24 @@ goal of reducing the number of vulnerabilities released over time. For each of the vulnerabilities listed in this document, AppSec aims to have a SAST rule either in the form of a semgrep rule (or a RuboCop rule) that runs in the CI pipeline. Below is a table of all existing guidelines and their coverage status: -| Guideline | Rule | Status | -|---|---|---| -| [Regular Expressions](#regular-expressions-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_regex.yml) | ✅ | -| [ReDOS](#denial-of-service-redos--catastrophic-backtracking) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_2.yml), [3](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/merge_requests/59#note_2443657926) | ✅ | -| [JWT](#json-web-tokens-jwt) | Pending | ❌ | -| [SSRF](#server-side-request-forgery-ssrf) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_url-1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_http.yml?ref_type=heads) | ✅ | -| [XSS](#xss-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_redirect.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_html_safe.yml) | ✅ | -| [XXE](#xml-external-entities) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_change_unsafe_nokogiri_parse_option.yml?ref_type=heads), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_initialize_unsafe_nokogiri_parse_option.yml?ref_type=heads), [3](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_set_unsafe_nokogiri_parse_option.yml?ref_type=heads), [4](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_unsafe_xml_libraries.yml?ref_type=heads) | ✅ | -| [Path traversal](#path-traversal-guidelines) (Ruby) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_path_traversal.yml?ref_type=heads) | ✅ | -| [Path traversal](#path-traversal-guidelines) (Go) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/merge_requests/39) | ✅ | -| [OS command injection](#os-command-injection-guidelines) (Ruby) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_command_injection.yml?ref_type=heads) | ✅ | -| [OS command injection](#os-command-injection-guidelines) (Go) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/go/go_dangerous_exec_command.yml?ref_type=heads) | ✅ | -| [Insecure TLS ciphers](#tls-minimum-recommended-version) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_ciphers.yml?ref_type=heads) | ✅ | -| [Archive operations](#working-with-archive-files) (Ruby) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_archive_operations.yml?ref_type=heads) | ✅ | -| [Archive operations](#working-with-archive-files) (Go) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/go/go_insecure_archive_operations.yml) | ✅ | -| [URL spoofing](#url-spoofing) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_url_spoofing.yml) | ✅ | -| [Request Parameter Typing](#request-parameter-typing) | `StrongParams` RuboCop | ✅ | -| [Paid tiers for vulnerability mitigation](#paid-tiers-for-vulnerability-mitigation) | N/A | | +| Guideline | Status | Rule | +|-------------------------------------------------------------------------------------|--------|------| +| [Regular Expressions](#regular-expressions-guidelines) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_regex.yml) | +| [ReDOS](#denial-of-service-redos--catastrophic-backtracking) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_2.yml), [3](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/merge_requests/59#note_2443657926) | +| [JWT](#json-web-tokens-jwt) | ❌ | Pending | +| [SSRF](#server-side-request-forgery-ssrf) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_url-1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_http.yml?ref_type=heads) | +| [XSS](#xss-guidelines) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_redirect.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_html_safe.yml) | +| [XXE](#xml-external-entities) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_change_unsafe_nokogiri_parse_option.yml?ref_type=heads), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_initialize_unsafe_nokogiri_parse_option.yml?ref_type=heads), [3](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_set_unsafe_nokogiri_parse_option.yml?ref_type=heads), [4](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xml_injection_unsafe_xml_libraries.yml?ref_type=heads) | +| [Path traversal](#path-traversal-guidelines) (Ruby) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_path_traversal.yml?ref_type=heads) | +| [Path traversal](#path-traversal-guidelines) (Go) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/merge_requests/39) | +| [OS command injection](#os-command-injection-guidelines) (Ruby) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_command_injection.yml?ref_type=heads) | +| [OS command injection](#os-command-injection-guidelines) (Go) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/go/go_dangerous_exec_command.yml?ref_type=heads) | +| [Insecure TLS ciphers](#tls-minimum-recommended-version) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_ciphers.yml?ref_type=heads) | +| [Archive operations](#working-with-archive-files) (Ruby) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_archive_operations.yml?ref_type=heads) | +| [Archive operations](#working-with-archive-files) (Go) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/go/go_insecure_archive_operations.yml) | +| [URL spoofing](#url-spoofing) | ✅ | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_url_spoofing.yml) | +| [Request Parameter Typing](#request-parameter-typing) | ✅ | `StrongParams` RuboCop | +| [Paid tiers for vulnerability mitigation](#paid-tiers-for-vulnerability-mitigation) | | N/A | ## Process for creating new guidelines and accompanying rules diff --git a/doc/development/uploads/working_with_uploads.md b/doc/development/uploads/working_with_uploads.md index 422733b2bcd..71599a978d0 100644 --- a/doc/development/uploads/working_with_uploads.md +++ b/doc/development/uploads/working_with_uploads.md @@ -136,11 +136,11 @@ metadata from the uploaded file. There are a couple of different ways you can implement this. The main choice is _where_ to implement the processing, or "who is the processor". -|Processor|Direct Upload possible?|Can reject HTTP request?|Implementation| -|---|---|---|---| -|Sidekiq|yes|no|Straightforward| -|Workhorse|yes|yes|Complex| -|Rails|no|yes|Easy| +| Processor | Direct Upload possible? | Can reject HTTP request? | Implementation | +|-----------|-------------------------|--------------------------|----------------| +| Sidekiq | yes | no | Straightforward | +| Workhorse | yes | yes | Complex | +| Rails | no | yes | Easy | Processing in Rails looks appealing but it tends to lead to scaling problems down the road because you cannot use direct upload. You are @@ -210,10 +210,10 @@ pre-processing behaviors are skipped silently. CarrierWave has 2 storage engines: -|CarrierWave class|GitLab name|Description| -|---|---|---| -|`CarrierWave::Storage::File`|`ObjectStorage::Store::LOCAL` |Local files, accessed through the Ruby `stdlib` | -| `CarrierWave::Storage::Fog`|`ObjectStorage::Store::REMOTE`|Cloud files, accessed through the [Fog gem](https://github.com/fog/fog)| +| CarrierWave class | GitLab name | Description | +|------------------------------|--------------------------------|-------------| +| `CarrierWave::Storage::File` | `ObjectStorage::Store::LOCAL` | Local files, accessed through the Ruby `stdlib` | +| `CarrierWave::Storage::Fog` | `ObjectStorage::Store::REMOTE` | Cloud files, accessed through the [Fog gem](https://github.com/fog/fog) | GitLab uses both of these engines, depending on configuration. diff --git a/doc/user/analytics/value_streams_dashboard.md b/doc/user/analytics/value_streams_dashboard.md index 1169f3e5b09..c01bb5d92e3 100644 --- a/doc/user/analytics/value_streams_dashboard.md +++ b/doc/user/analytics/value_streams_dashboard.md @@ -168,12 +168,12 @@ Hovering over each bar reveals a dialog that explains the score's definition. For example, if a project has a high score for deployment frequency (velocity), it means that the project has one or more deploys to production per day. -| Metric | Description | High | Medium | Low | -|--------|-------------|------|--------|-----| -| Deployment frequency | The number of deploys to production per day | ≥30 | 1-29 | \<1 | -| Lead time for changes | The number of days to go from code committed to code successfully running in production| ≤7 | 8-29 | ≥30 | -| Time to restore service | The number of days to restore service when a service incident or a defect that impacts users occurs | ≤1 | 2-6 | ≥7 | -| Change failure rate | The percentage of changes to production resulted in degraded service | ≤15% | 16%-44% | ≥45% | +| Metric | High | Medium | Low | Description | +|-------------------------|------|---------|------|-------------| +| Deployment frequency | ≥30 | 1-29 | \<1 | The number of deploys to production per day | +| Lead time for changes | ≤7 | 8-29 | ≥30 | The number of days to go from code committed to code successfully running in production | +| Time to restore service | ≤1 | 2-6 | ≥7 | The number of days to restore service when a service incident or a defect that impacts users occurs | +| Change failure rate | ≤15% | 16%-44% | ≥45% | The percentage of changes to production resulted in degraded service | To learn more, see the blog post [Inside DORA Performers score in GitLab Value Streams Dashboard](https://about.gitlab.com/blog/2024/01/18/inside-dora-performers-score-in-gitlab-value-streams-dashboard/). @@ -322,18 +322,18 @@ After you have set up the project, set up the configuration file: 1. In the default branch, create the configuration file: `.gitlab/analytics/dashboards/value_streams/value_streams.yaml`. 1. In the `value_streams.yaml` configuration file, fill in the configuration options: -|Field|Description| -|---|---| -| `title` | Custom name for the panel | -|`queryOverrides` (formerly `data`) | Overrides data query parameters specific to each visualization. | -|`namespace` (subfield of `queryOverrides`) | Group or project path to use for the panel | -|`filters` (subfield of `queryOverrides`) | Filters the query for each visualization type. See [supported visualizations](#supported-visualization-filters). | -| `visualization` | The type of visualization to be rendered. Supported options are `dora_chart`, `dora_performers_score`, and `usage_overview`. | -| `gridAttributes` | The size and positioning of the panel | -| `xPos` (subfield of `gridAttributes`) | Horizontal position of the panel | -| `yPos` (subfield of `gridAttributes`) | Vertical position of the panel | -| `width` (subfield of `gridAttributes`) | Width of the panel (max. 12) | -| `height` (subfield of `gridAttributes`) | Height of the panel | +| Field | Description | +|--------------------------------------------|-------------| +| `title` | Custom name for the panel | +| `queryOverrides` (formerly `data`) | Overrides data query parameters specific to each visualization. | +| `namespace` (subfield of `queryOverrides`) | Group or project path to use for the panel | +| `filters` (subfield of `queryOverrides`) | Filters the query for each visualization type. See [supported visualizations](#supported-visualization-filters). | +| `visualization` | The type of visualization to be rendered. Supported options are `dora_chart`, `dora_performers_score`, and `usage_overview`. | +| `gridAttributes` | The size and positioning of the panel | +| `xPos` (subfield of `gridAttributes`) | Horizontal position of the panel | +| `yPos` (subfield of `gridAttributes`) | Vertical position of the panel | +| `width` (subfield of `gridAttributes`) | Width of the panel (max. 12) | +| `height` (subfield of `gridAttributes`) | Height of the panel | ```yaml # version - The latest version of the analytics dashboard schema @@ -413,18 +413,18 @@ The `filters` subfield on the `queryOverrides` field can be used to customize th Filters for the `dora_chart` visualization. -|Filter|Description|Supported values| -|---|---|---| -|`excludeMetrics`| Hides rows by metric ID from the chart panel | `deployment_frequency`, `lead_time_for_changes`,`time_to_restore_service`, `change_failure_rate`, `lead_time`, `cycle_time`, `issues`, `issues_completed`, `deploys`, `merge_request_throughput`, `median_time_to_merge`, `contributor_count`, `vulnerability_critical`, `vulnerability_high`| -| `labels` | Filters data by labels | Any available group label. Label filtering is supported by the following metrics: `lead_time`, `cycle_time`, `issues`, `issues_completed`, `merge_request_throughput`, `median_time_to_merge`. | +| Filter | Description | Supported values | +|------------------|----------------------------------------------|------------------| +| `excludeMetrics` | Hides rows by metric ID from the chart panel | `deployment_frequency`, `lead_time_for_changes`,`time_to_restore_service`, `change_failure_rate`, `lead_time`, `cycle_time`, `issues`, `issues_completed`, `deploys`, `merge_request_throughput`, `median_time_to_merge`, `contributor_count`, `vulnerability_critical`, `vulnerability_high` | +| `labels` | Filters data by labels | Any available group label. Label filtering is supported by the following metrics: `lead_time`, `cycle_time`, `issues`, `issues_completed`, `merge_request_throughput`, `median_time_to_merge`. | #### DORA Performers score panel filters Filters for the `dora_performers_score` visualization. -|Filter|Description|Supported values| -|---|---|---| -|`projectTopics`|Filters the projects shown based on their assigned [topics](../project/project_topics.md)| Any available group topic| +| Filter | Description | Supported values | +|-----------------|-------------------------------------------------------------------------------------------|------------------| +| `projectTopics` | Filters the projects shown based on their assigned [topics](../project/project_topics.md) | Any available group topic | #### Usage overview panel filters @@ -432,34 +432,34 @@ Filters for the `usage_overview` visualization. ##### Group and subgroup namespaces -|Filter|Description|Supported values| -|---|---|---| -|`include`|Limits the metrics returned, by default displays all available| `groups`, `projects`, `issues`, `merge_requests`, `pipelines`, `users`| +| Filter | Description | Supported values | +|-----------|----------------------------------------------------------------|------------------| +| `include` | Limits the metrics returned, by default displays all available | `groups`, `projects`, `issues`, `merge_requests`, `pipelines`, `users` | ##### Project namespaces -|Filter|Description|Supported values| -|---|---|---| -|`include`|Limits the metrics returned, by default displays all available| `issues`, `merge_requests`, `pipelines`| +| Filter | Description | Supported values | +|-----------|----------------------------------------------------------------|------------------| +| `include` | Limits the metrics returned, by default displays all available | `issues`, `merge_requests`, `pipelines` | ## Dashboard metrics and drill-down reports -| Metric | Description | Drill-down report | Documentation page | ID | -| ------ | ----------- | --------------- | ------------------ | -- | -| Deployment frequency | Average number of deployments to production per day. This metric measures how often value is delivered to end users. | [Deployment frequency tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=deployment-frequency) | [Deployment frequency](dora_metrics.md#deployment-frequency) | `deployment_frequency` | -| Lead time for changes | The time to successfully deliver a commit into production. This metric reflects the efficiency of CI/CD pipelines. | [Lead time tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=lead-time) | [Lead time for changes](dora_metrics.md#lead-time-for-changes) | `lead_time_for_changes` | -| Time to restore service | The time it takes an organization to recover from a failure in production. | [Time to restore service tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=time-to-restore-service) | [Time to restore service](dora_metrics.md#time-to-restore-service) | `time_to_restore_service` | -| Change failure rate | Percentage of deployments that cause an incident in production. | [Change failure rate tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=change-failure-rate) | [Change failure rate](dora_metrics.md#change-failure-rate) | `change_failure_rate` | -| Lead time | Median time from issue created to issue closed. | [Value Stream Analytics](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/_index.md#lifecycle-metrics) | `lead_time` | -| Cycle time | Median time from the earliest commit of a linked issue's merge request to when that issue is closed. | [VSA overview](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/_index.md#lifecycle-metrics) | `cycle_time` | -| Issues created | Number of new issues created. | [Issue Analytics](https://gitlab.com/groups/gitlab-org/-/issues_analytics) | [Issue Analytics](../group/issues_analytics/_index.md) | `issues` | -| Issues closed | Number of issues closed by month. | [Issue Analytics](https://gitlab.com/groups/gitlab-org/-/issues_analytics) | [Issue Analytics](../group/issues_analytics/_index.md) | `issues_completed` | -| Number of deploys | Total number of deploys to production. | [Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Merge request analytics](merge_request_analytics.md) | `deploys` | -| Merge request throughput | The number of merge requests merged by month. | [Groups Productivity analytics](productivity_analytics.md), [Projects Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Groups Productivity analytics](productivity_analytics.md) [Projects Merge request analytics](merge_request_analytics.md) | `merge_request_throughput` | -| Median time to merge | Median time between merge request created and merge request merged. | [Groups Productivity analytics](productivity_analytics.md), [Projects Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Groups Productivity analytics](productivity_analytics.md) [Projects Merge request analytics](merge_request_analytics.md) | `median_time_to_merge` | -| Contributor count | Number of monthly unique users with contributions in the group.| [Contribution Analytics](https://gitlab.com/groups/gitlab-org/-/contribution_analytics) | [User contribution events](../profile/contributions_calendar.md#user-contribution-events) | `contributor_count` | -| Critical vulnerabilities over time | Critical vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/_index.md) | `vulnerability_critical` | -| High vulnerabilities over time | High vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/_index.md) | `vulnerability_high` | +| Metric | ID | Description | Drill-down report | Documentation page | +|------------------------------------|----------------------------|-------------|-------------------|--------------------| +| Deployment frequency | `deployment_frequency` | Average number of deployments to production per day. This metric measures how often value is delivered to end users. | [Deployment frequency tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=deployment-frequency) | [Deployment frequency](dora_metrics.md#deployment-frequency) | +| Lead time for changes | `lead_time_for_changes` | The time to successfully deliver a commit into production. This metric reflects the efficiency of CI/CD pipelines. | [Lead time tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=lead-time) | [Lead time for changes](dora_metrics.md#lead-time-for-changes) | +| Time to restore service | `time_to_restore_service` | The time it takes an organization to recover from a failure in production. | [Time to restore service tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=time-to-restore-service) | [Time to restore service](dora_metrics.md#time-to-restore-service) | +| Change failure rate | `change_failure_rate` | Percentage of deployments that cause an incident in production. | [Change failure rate tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=change-failure-rate) | [Change failure rate](dora_metrics.md#change-failure-rate) | +| Lead time | `lead_time` | Median time from issue created to issue closed. | [Value Stream Analytics](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/_index.md#lifecycle-metrics) | +| Cycle time | `cycle_time` | Median time from the earliest commit of a linked issue's merge request to when that issue is closed. | [VSA overview](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/_index.md#lifecycle-metrics) | +| Issues created | `issues` | Number of new issues created. | [Issue Analytics](https://gitlab.com/groups/gitlab-org/-/issues_analytics) | [Issue Analytics](../group/issues_analytics/_index.md) | +| Issues closed | `issues_completed` | Number of issues closed by month. | [Issue Analytics](https://gitlab.com/groups/gitlab-org/-/issues_analytics) | [Issue Analytics](../group/issues_analytics/_index.md) | +| Number of deploys | `deploys` | Total number of deploys to production. | [Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Merge request analytics](merge_request_analytics.md) | +| Merge request throughput | `merge_request_throughput` | The number of merge requests merged by month. | [Groups Productivity analytics](productivity_analytics.md), [Projects Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Groups Productivity analytics](productivity_analytics.md) [Projects Merge request analytics](merge_request_analytics.md) | +| Median time to merge | `median_time_to_merge` | Median time between merge request created and merge request merged. | [Groups Productivity analytics](productivity_analytics.md), [Projects Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Groups Productivity analytics](productivity_analytics.md) [Projects Merge request analytics](merge_request_analytics.md) | +| Contributor count | `contributor_count` | Number of monthly unique users with contributions in the group. | [Contribution Analytics](https://gitlab.com/groups/gitlab-org/-/contribution_analytics) | [User contribution events](../profile/contributions_calendar.md#user-contribution-events) | +| Critical vulnerabilities over time | `vulnerability_critical` | Critical vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/_index.md) | +| High vulnerabilities over time | `vulnerability_high` | High vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/_index.md) | ## Metrics with Jira diff --git a/doc/user/packages/package_registry/tutorial_generate_sbom.md b/doc/user/packages/package_registry/tutorial_generate_sbom.md index 069b34da865..6f91f5599f6 100644 --- a/doc/user/packages/package_registry/tutorial_generate_sbom.md +++ b/doc/user/packages/package_registry/tutorial_generate_sbom.md @@ -21,12 +21,12 @@ An organization that's interested in using a software product may require an SBO If you're familiar with the GitLab package registry, you might wonder what the difference is between an SBOM and a [dependency list](../../application_security/dependency_list/_index.md). The following table highlights the key differences: -| Differences | Dependency list | SBOM | -|---|---|---| -| **Scope** | Shows dependencies for individual projects. | Creates an inventory of all packages published across your group. | -| **Direction** | Tracks what your projects depend on (incoming dependencies). | Tracks what your group publishes (outgoing packages). | -| **Coverage** | Based on package manifests, like `package.json` or `pom.xml`. | Covers actual published artifacts in your package registry. | -| **Format** | GitLab-specific feature. | Generates standard CycloneDX SBOMs that can be used with external tools. | +| Differences | Dependency list | SBOM | +|---------------|---------------------------------------------------------------|------| +| **Scope** | Shows dependencies for individual projects. | Creates an inventory of all packages published across your group. | +| **Direction** | Tracks what your projects depend on (incoming dependencies). | Tracks what your group publishes (outgoing packages). | +| **Coverage** | Based on package manifests, like `package.json` or `pom.xml`. | Covers actual published artifacts in your package registry. | +| **Format** | GitLab-specific feature. | Generates standard CycloneDX SBOMs that can be used with external tools. | ## What is CycloneDX? diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md index 5bedb00a3f8..7c3c00d1cc9 100644 --- a/doc/user/project/badges.md +++ b/doc/user/project/badges.md @@ -83,13 +83,13 @@ For example, you can use code similar to the following to add the test coverage The following table shows the default test coverage limits and badge colors: -| Test coverage | Percentage limits | Badge color | -|---|---|---| -| Good | 95 up to and including 100% | `#4c1` | -| Acceptable | 90 up to 95% | `#a3c51c` | -| Medium | 75 up to 90% | `#dfb317` | -| Low | 0 up to 75% | `#e05d44` | -| Unknown | No coverage | `#9f9f9f` | +| Test coverage | Percentage limits | Badge color | +|---------------|-----------------------------|-------------| +| Good | 95 up to and including 100% | `#4c1` | +| Acceptable | 90 up to 95% | `#a3c51c` | +| Medium | 75 up to 90% | `#dfb317` | +| Low | 0 up to 75% | `#e05d44` | +| Unknown | No coverage | `#9f9f9f` | {{< alert type="note" >}} @@ -101,11 +101,11 @@ The following table shows the default test coverage limits and badge colors: You can override the default limits by passing the following query parameters in the coverage report badge URL: -| Query parameter | Acceptable values | Default | -|---|---|---| -| `min_good` | Any value between 3 and 100 | 95 | -| `min_acceptable` | Any value between 2 and `min_good`−1 | 90 | -| `min_medium` | Any value between 1 and `min_acceptable`−1 | 75 | +| Query parameter | Acceptable values | Default | +|------------------|----------------------------------------------|---------| +| `min_good` | Any value between `3` and `100` | `95` | +| `min_acceptable` | Any value between `2` and `min_good`−1 | `90` | +| `min_medium` | Any value between `1` and `min_acceptable`−1 | `75` | For example: diff --git a/gems/gitlab-active-context/lib/active_context/collection_cache.rb b/gems/gitlab-active-context/lib/active_context/collection_cache.rb index ae8eb716d66..5b1a8d2f883 100644 --- a/gems/gitlab-active-context/lib/active_context/collection_cache.rb +++ b/gems/gitlab-active-context/lib/active_context/collection_cache.rb @@ -2,9 +2,9 @@ module ActiveContext module CollectionCache - class << self - TTL = 1.minute + TTL = 1.minute + class << self def collections refresh_cache if cache_expired? @@ -25,10 +25,14 @@ module ActiveContext private + def ttl + Rails.env.test? ? 0.seconds : TTL + end + def cache_expired? return true unless @last_refreshed_at - Time.current - @last_refreshed_at > TTL + Time.current - @last_refreshed_at > ttl end def refresh_cache diff --git a/gems/gitlab-active-context/lib/active_context/concerns/collection.rb b/gems/gitlab-active-context/lib/active_context/concerns/collection.rb index e9c90b3bd98..81682eb4cea 100644 --- a/gems/gitlab-active-context/lib/active_context/concerns/collection.rb +++ b/gems/gitlab-active-context/lib/active_context/concerns/collection.rb @@ -83,7 +83,9 @@ module ActiveContext def references reference_klasses = Array.wrap(self.class.reference_klasses) routing = self.class.routing(object) - collection_id = self.class.collection_record.id + collection_id = self.class.collection_record&.id + + raise StandardError, "#{self.class} expected to have a collection record" unless collection_id reference_klasses.map do |reference_klass| reference_klass.serialize(collection_id: collection_id, routing: routing, data: object) diff --git a/gems/gitlab-active-context/lib/active_context/databases/concerns/query_result.rb b/gems/gitlab-active-context/lib/active_context/databases/concerns/query_result.rb index b4180485634..d27df7166b5 100644 --- a/gems/gitlab-active-context/lib/active_context/databases/concerns/query_result.rb +++ b/gems/gitlab-active-context/lib/active_context/databases/concerns/query_result.rb @@ -18,10 +18,6 @@ module ActiveContext @authorized_results ||= collection.redact_unauthorized_results!(self) end - def ids - each.pluck('ref_id') - end - def each raise NotImplementedError end diff --git a/gems/gitlab-active-context/lib/active_context/preprocessors/content_fetcher.rb b/gems/gitlab-active-context/lib/active_context/preprocessors/content_fetcher.rb new file mode 100644 index 00000000000..4a97be03fce --- /dev/null +++ b/gems/gitlab-active-context/lib/active_context/preprocessors/content_fetcher.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActiveContext + module Preprocessors + module ContentFetcher + extend ActiveSupport::Concern + + ContentNotFoundError = Class.new(StandardError) + + class_methods do + def fetch_content(refs:, query:, collection:, content_field: 'content') + matches = ::ActiveContext.adapter.client.search( + user: nil, + collection: collection, + query: query + ) + + content_by_id = matches.each_with_object({}) do |match, hash| + hash[match['id']] = match[content_field] + end + + with_per_ref_handling(refs, error_types: [ContentNotFoundError]) do |ref| + unless content_by_id.key?(ref.identifier) + raise ContentNotFoundError, "content not found for chunk with id: #{ref.identifier}" + end + + ref.documents << { content: content_by_id[ref.identifier] } + ref + end + end + end + end + end +end diff --git a/gems/gitlab-active-context/lib/active_context/reference.rb b/gems/gitlab-active-context/lib/active_context/reference.rb index c047549819c..5920ba6ea96 100644 --- a/gems/gitlab-active-context/lib/active_context/reference.rb +++ b/gems/gitlab-active-context/lib/active_context/reference.rb @@ -5,6 +5,7 @@ module ActiveContext extend Concerns::ReferenceUtils extend Concerns::Preprocessor include Preprocessors::Chunking + include Preprocessors::ContentFetcher include Preprocessors::Embeddings include Preprocessors::Preload @@ -48,7 +49,7 @@ module ActiveContext @routing = routing @serialized_args = Array(args) @ref_version = Time.now.to_i - @include_ref_fields = collection.include_ref_fields + @include_ref_fields = @collection.respond_to?(:include_ref_fields) ? @collection.include_ref_fields : true init end diff --git a/gems/gitlab-active-context/spec/lib/active_context/databases/concerns/adapter_spec.rb b/gems/gitlab-active-context/spec/lib/active_context/databases/concerns/adapter_spec.rb index 21d34cc38c7..ef6c0daa8b0 100644 --- a/gems/gitlab-active-context/spec/lib/active_context/databases/concerns/adapter_spec.rb +++ b/gems/gitlab-active-context/spec/lib/active_context/databases/concerns/adapter_spec.rb @@ -33,7 +33,7 @@ RSpec.describe ActiveContext::Databases::Concerns::Adapter do end let(:connection) { double('Connection') } - let(:options) { { host: 'localhost' } } + let(:options) { { 'host' => 'localhost' } } subject(:adapter) { test_class.new(connection, options: options) } diff --git a/gems/gitlab-active-context/spec/lib/active_context/preprocessors/content_fetcher_spec.rb b/gems/gitlab-active-context/spec/lib/active_context/preprocessors/content_fetcher_spec.rb new file mode 100644 index 00000000000..c753ce65e5a --- /dev/null +++ b/gems/gitlab-active-context/spec/lib/active_context/preprocessors/content_fetcher_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +RSpec.describe ActiveContext::Preprocessors::ContentFetcher do + let(:reference_class) do + Class.new(Test::References::Mock) do + include ::ActiveContext::Preprocessors::ContentFetcher + + add_preprocessor :fetch_content do |refs| + fetch_content(refs: refs, query: '*', collection: 'mock_collection') + end + end + end + + let(:reference_1) { reference_class.new(collection_id: collection_id, routing: partition, args: 'id1') } + let(:reference_2) { reference_class.new(collection_id: collection_id, routing: partition, args: 'id2') } + + let(:mock_adapter) { double } + let(:mock_collection) { double(name: collection_name, partition_for: partition, include_ref_fields: true) } + let(:mock_connection) { double(id: connection_id) } + + let(:query) { '*' } + let(:connection_id) { 3 } + let(:partition) { 2 } + let(:collection_id) { 1 } + let(:collection_name) { 'mock_collection' } + + let(:search_results) do + [ + { 'id' => 'id1', 'content' => 'Content for document 1' }, + { 'id' => 'id2', 'content' => 'Content for document 2' } + ] + end + + subject(:process_refs) { ActiveContext::Reference.preprocess_references([reference_1, reference_2]) } + + before do + allow(ActiveContext).to receive(:adapter).and_return(mock_adapter) + allow(mock_adapter).to receive_message_chain(:client, :search).and_return(search_results) + allow(ActiveContext::CollectionCache).to receive(:fetch).and_return(mock_collection) + allow(ActiveContext::Logger).to receive(:exception).and_return(nil) + end + + describe '.fetch_content' do + context 'when content is found for all references' do + it 'fetches content and adds it to reference documents' do + process_refs + + expect(reference_1.documents).to include({ content: 'Content for document 1' }) + expect(reference_2.documents).to include({ content: 'Content for document 2' }) + end + + it 'calls search with the correct parameters' do + expect(mock_adapter).to receive_message_chain(:client, :search).with( + user: nil, + collection: collection_name, + query: query + ) + + process_refs + end + + it 'returns the references' do + result = process_refs + + expect(result[:successful]).to eq([reference_1, reference_2]) + expect(result[:failed]).to be_empty + end + end + + context 'when content is not found for some references' do + let(:search_results) do + [ + { 'id' => 'id1', 'content' => 'Content for document 1' } + ] + end + + it 'adds the ref to the failed refs result', :aggregate_failures do + result = process_refs + + expect(result[:successful]).to eq([reference_1]) + expect(result[:failed]).to eq([reference_2]) + end + end + end +end diff --git a/lib/api/files.rb b/lib/api/files.rb index 0338d00ac0b..6c0583be67b 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -52,7 +52,9 @@ module API end def authorize_ai_access! - forbidden!('Insufficient permissions for Duo Workflow') unless can?(current_user, :duo_workflow, user_project) + return if can?(current_user, :duo_workflow, user_project) || can?(current_user, :access_duo_agentic_chat, user_project) + + forbidden!('Insufficient permissions for Duo Workflow') end def commit_response(attrs) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3a88053cca1..043200c1939 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -44801,6 +44801,12 @@ msgstr "" msgid "PipelineScheduleIntervalPattern|Custom" msgstr "" +msgid "PipelineScheduleIntervalPattern|Learn more." +msgstr "" + +msgid "PipelineScheduleIntervalPattern|Pipelines cannot run more frequently than the pipeline schedule worker cron setting (%{workerCronExpression}) allows." +msgstr "" + msgid "PipelineScheduleIntervalPattern|Set a custom interval with Cron syntax." msgstr "" diff --git a/spec/features/users/bizible_csp_spec.rb b/spec/features/users/bizible_csp_spec.rb index 6ed56af301d..11184271a65 100644 --- a/spec/features/users/bizible_csp_spec.rb +++ b/spec/features/users/bizible_csp_spec.rb @@ -3,13 +3,39 @@ require 'spec_helper' RSpec.describe 'Bizible content security policy', feature_category: :subscription_management do + include ContentSecurityPolicyHelpers + + let(:bizible_enabled) { true } + let(:csp) { ActionDispatch::ContentSecurityPolicy.new { |p| p.default_src '' } } + let(:script_src) { ["'unsafe-eval'", 'https://cdn.bizible.com/scripts/bizible.js'] } + + subject(:csp_header) { response_headers['Content-Security-Policy'] } + before do - stub_config(extra: { one_trust_id: SecureRandom.uuid }) + stub_config(extra: { bizible: bizible_enabled }) + stub_feature_flags(ecomm_instrumentation: true) + stub_csp_for_controller(RegistrationsController, csp) + + visit new_user_registration_path end it 'has proper Content Security Policy headers' do - visit root_path + expect(find_csp_directive('script-src', header: csp_header)).to include(*script_src) + end - expect(response_headers['Content-Security-Policy']).to include('https://cdn.bizible.com/scripts/bizible.js') + context 'when disabled' do + let(:bizible_enabled) { false } + + it 'does not have Content Security Policy headers' do + expect(csp_header).not_to include(*script_src) + end + end + + context 'when CSP is absent' do + let(:csp) { ActionDispatch::ContentSecurityPolicy.new } + + it 'does not have Content Security Policy headers' do + expect(csp_header).not_to include(*script_src) + end end end diff --git a/spec/finders/work_items/work_items_finder_spec.rb b/spec/finders/work_items/work_items_finder_spec.rb index aba6b43510e..de72a171326 100644 --- a/spec/finders/work_items/work_items_finder_spec.rb +++ b/spec/finders/work_items/work_items_finder_spec.rb @@ -82,5 +82,92 @@ RSpec.describe WorkItems::WorkItemsFinder, feature_category: :team_planning do end end end + + context 'when using parent_ids filter' do + let(:scope) { 'all' } + + context 'when user has access to child item' do + let_it_be(:child_item1) { create(:work_item, project: project1) } + let_it_be(:parent_item1) { create(:work_item, :epic, project: project1) } + + let(:params) { { work_item_parent_ids: [parent_item1.id] } } + + before do + create(:parent_link, work_item_parent: parent_item1, work_item: child_item1) + end + + it 'returns corresponding child work items' do + expect(items).to contain_exactly(child_item1) + end + end + + context 'when filtering by parent item from different project' do + let_it_be(:another_project) { create(:project) } + let_it_be(:child_item2) { create(:work_item, project: project1) } + let_it_be(:parent_item2) { create(:work_item, :epic, project: another_project) } + + let(:params) { { work_item_parent_ids: [parent_item2.id] } } + + before do + create(:parent_link, work_item_parent: parent_item2, work_item: child_item2) + end + + it 'returns corresponding child work items' do + expect(items).to contain_exactly(child_item2) + end + end + + context 'when filtering by multiple parent items' do + let_it_be(:child_item3) { create(:work_item, project: project1) } + let_it_be(:child_item4) { create(:work_item, project: project1) } + + let_it_be(:parent_item3) { create(:work_item, :epic, project: project1) } + let_it_be(:parent_item4) { create(:work_item, :epic, project: project1) } + + let(:params) { { work_item_parent_ids: [parent_item3.id, parent_item4.id] } } + + before do + create(:parent_link, work_item_parent: parent_item3, work_item: child_item3) + create(:parent_link, work_item_parent: parent_item4, work_item: child_item4) + end + + it 'returns corresponding child work items' do + expect(items).to contain_exactly(child_item3, child_item4) + end + end + + context 'when user does not have access to child items' do + let_it_be(:confidential_work_item) { create(:work_item, confidential: true, project: project1) } + let_it_be(:parent_item5) { create(:work_item, :epic, confidential: true, project: project1) } + + let(:search_user) { user2 } + let(:params) { { work_item_parent_ids: [parent_item5.id] } } + + before do + create(:parent_link, work_item_parent: parent_item5, work_item: confidential_work_item) + end + + it 'does not return those items' do + expect(items).to be_empty + end + end + + context 'when user does not have access to child and parent items' do + let_it_be(:private_project) { create(:project, :private) } + let_it_be(:private_work_item) { create(:work_item, project: private_project) } + let_it_be(:private_parent_item) { create(:work_item, :epic, project: private_project) } + + let(:search_user) { user2 } + let(:params) { { work_item_parent_ids: [private_parent_item.id] } } + + before do + create(:parent_link, work_item_parent: private_parent_item, work_item: private_work_item) + end + + it 'does not return those items' do + expect(items).to be_empty + end + end + end end end diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js index 7cb0e3ee38b..ee2b7c19b2c 100644 --- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js +++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js @@ -20,6 +20,7 @@ describe('Interval Pattern Input Component', () => { const customKey = 'custom'; const everyDayKey = 'everyDay'; const cronIntervalNotInPreset = `0 12 * * *`; + const workerCronValue = '3-59/10 * * * *'; const findEveryDayRadio = () => wrapper.findByTestId(everyDayKey); const findEveryWeekRadio = () => wrapper.findByTestId('everyWeek'); @@ -35,6 +36,7 @@ describe('Interval Pattern Input Component', () => { const selectEveryWeekRadio = () => findEveryWeekRadio().setChecked(true); const selectEveryMonthRadio = () => findEveryMonthRadio().setChecked(true); const selectCustomRadio = () => findCustomRadio().setChecked(true); + const findWorkerCronExpressionHint = () => wrapper.findByTestId('worker-cron-expression-hint'); const createWrapper = (props = {}, data = {}) => { wrapper = mountExtended(IntervalPatternInput, { @@ -47,6 +49,9 @@ describe('Interval Pattern Input Component', () => { randomDay: mockDay, }; }, + provide: { + workerCronExpression: workerCronValue, + }, }); }; @@ -64,6 +69,17 @@ describe('Interval Pattern Input Component', () => { window.gl = oldWindowGl; }); + describe('the worker cron expression hint', () => { + beforeEach(() => { + createWrapper(); + }); + + it('displays the expected value', () => { + expect(findWorkerCronExpressionHint().exists()).toBe(true); + expect(findWorkerCronExpressionHint().text()).toContain(workerCronValue); + }); + }); + describe('the input field defaults', () => { beforeEach(() => { createWrapper(); diff --git a/spec/graphql/resolvers/work_items_resolver_spec.rb b/spec/graphql/resolvers/work_items_resolver_spec.rb index ed3aeae40c5..36236164ceb 100644 --- a/spec/graphql/resolvers/work_items_resolver_spec.rb +++ b/spec/graphql/resolvers/work_items_resolver_spec.rb @@ -161,6 +161,35 @@ RSpec.describe Resolvers::WorkItemsResolver, feature_category: :team_planning do expect(batch_sync { resolve_items(iids: iids).to_a }).to contain_exactly(item1, item2) end + + context 'with parent_ids filter' do + context 'when filtering by more than 100 parent ids' do + let(:too_many_parent_ids) { (1..101).to_a } + + it 'throws an error' do + response = batch_sync { resolve_items(parent_ids: too_many_parent_ids) } + + expect(response).to be_a(GraphQL::ExecutionError) + expect(response.message).to eq('You can only provide up to 100 parent IDs at once.') + end + end + + context 'when converting global ids to work item ids' do + let_it_be(:work_item1) { create(:work_item) } + let_it_be(:work_item2) { create(:work_item) } + + let(:global_ids) { [work_item1.to_global_id, work_item2.to_global_id] } + let(:context) { { arg_style: :internal_prepared } } + + it 'correctly processes global IDs and maps to work item model_ids' do + expect(GitlabSchema).to receive(:parse_gids) + .with(global_ids, expected_type: ::WorkItem) + .and_call_original + + batch_sync { resolve_items({ parent_ids: global_ids.map(&:to_s) }, context) } + end + end + end end end @@ -214,7 +243,8 @@ RSpec.describe Resolvers::WorkItemsResolver, feature_category: :team_planning do def resolve_items(args = {}, context = {}) context[:current_user] = current_user + arg_style = context[:arg_style] ||= :internal - resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal) + resolve(described_class, obj: project, args: args, ctx: context, arg_style: arg_style) end end diff --git a/spec/graphql/types/current_user_type_spec.rb b/spec/graphql/types/current_user_type_spec.rb index 1f6596ea079..c33bf23a474 100644 --- a/spec/graphql/types/current_user_type_spec.rb +++ b/spec/graphql/types/current_user_type_spec.rb @@ -41,7 +41,8 @@ RSpec.describe GitlabSchema.types['CurrentUser'], feature_category: :user_profil subscribed types updatedAfter - updatedBefore] + updatedBefore + parentIds] is_expected.to have_graphql_arguments(expected_fields) is_expected.to have_graphql_type(Types::WorkItemType.connection_type) diff --git a/spec/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type_spec.rb b/spec/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type_spec.rb new file mode 100644 index 00000000000..5d3ff23d59d --- /dev/null +++ b/spec/graphql/types/namespaces/user_level_permissions/group_namespace_user_level_permissions_type_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::UserLevelPermissions::GroupNamespaceUserLevelPermissionsType, feature_category: :shared do + include GraphqlHelpers + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + + subject(:type) { described_class.resolve_type(group, {}) } + + it_behaves_like 'expose all user permissions fields for the namespace' + + describe "permission values" do + let_it_be(:non_member) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:owner) { create(:user) } + let_it_be(:group) do + create( + :group, + :private, + guests: [guest], + developers: [developer], + maintainers: [maintainer], + owners: [owner] + ) + end + + context "for can_admin_label permission" do + where(:user_role, :expected) do + :non_member | false + :guest | false + :developer | true + :maintainer | true + :owner | true + end + + with_them do + let(:current_user) { send(user_role) } + + it "returns the correct permission value" do + actual = resolve_field(:can_admin_label, group, current_user: current_user) + + expect(actual).to eq(expected) + end + end + end + + context "for can_create_projects permission" do + where(:user_role, :expected) do + :non_member | false + :guest | false + :developer | false + :maintainer | true + :owner | true + end + + with_them do + let(:current_user) { send(user_role) } + + it "returns the correct permission value" do + actual = resolve_field(:can_create_projects, group, current_user: current_user) + + expect(actual).to eq(expected) + end + end + end + end + + context "when group settings restrict permissions" do + let_it_be(:group) { create(:group, :private) } + let_it_be(:developer) { create(:user) } + let_it_be(:owner) { create(:user) } + + before_all do + group.add_developer(developer) + group.add_owner(owner) + end + + context "when create project is restricted" do + before do + allow_next_instance_of(Ability) do |instance| + allow(instance).to receive(:can?).with(:create_projects, group).and_return(false) + end + end + + it "returns false" do + expect(resolve_field(:can_create_projects, group, current_user: user)).to be(false) + end + end + + context "when label administration is restricted" do + before do + allow_next_instance_of(Ability) do |instance| + allow(instance).to receive(:can?).with(:admin_label, group).and_return(false) + end + end + + it "returns false" do + expect(resolve_field(:can_admin_label, group, current_user: user)).to be(false) + end + end + end +end diff --git a/spec/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type_spec.rb b/spec/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type_spec.rb new file mode 100644 index 00000000000..90b7e67cbee --- /dev/null +++ b/spec/graphql/types/namespaces/user_level_permissions/project_namespace_user_level_permissions_type_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::UserLevelPermissions::ProjectNamespaceUserLevelPermissionsType, feature_category: :shared do + include GraphqlHelpers + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :private) } + let_it_be(:project_namespace) { project.project_namespace } + + subject(:type) { described_class.resolve_type(project_namespace, {}) } + + it_behaves_like 'expose all user permissions fields for the namespace' + + describe "permission values" do + let_it_be(:non_member) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:owner) { create(:user) } + + before_all do + project.add_guest(guest) + project.add_developer(developer) + project.add_maintainer(maintainer) + project.add_owner(owner) + end + + # Test the implemented method that returns actual permission values + context "for can_admin_label permission" do + where(:user_role, :expected) do + :non_member | false + :guest | false + :developer | true + :maintainer | true + :owner | true + end + + with_them do + let(:current_user) { send(user_role) } + + it "returns the correct permission value" do + actual = resolve_field(:can_admin_label, project_namespace, current_user: current_user) + + expect(actual).to eq(expected) + end + end + end + + # Unified test for all non-implemented permissions that return nil + context "for non-implemented permissions" do + let(:current_user) { maintainer } + + it "returns nil for can_create_projects" do + expect(resolve_field(:can_create_projects, project_namespace, current_user: current_user)).to be(false) + end + + if Gitlab.ee? + it "returns nil for can_bulk_edit_epics" do + expect(resolve_field(:can_bulk_edit_epics, project_namespace, current_user: current_user)).to be(false) + end + + it "returns nil for can_create_epic" do + expect(resolve_field(:can_create_epic, project_namespace, current_user: current_user)).to be(false) + end + end + end + end + + context "when project settings restrict permissions" do + context "when label administration is restricted" do + before do + allow_next_instance_of(Ability) do |instance| + allow(instance).to receive(:can?).with(:admin_label, project).and_return(false) + end + end + + it "returns false" do + expect(resolve_field(:can_admin_label, project_namespace, current_user: user)).to be(false) + end + end + end +end diff --git a/spec/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type_spec.rb b/spec/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type_spec.rb new file mode 100644 index 00000000000..c40295dcada --- /dev/null +++ b/spec/graphql/types/namespaces/user_level_permissions/user_namespace_user_level_permissions_type_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::UserLevelPermissions::UserNamespaceUserLevelPermissionsType, feature_category: :shared do + it_behaves_like 'expose all user permissions fields for the namespace' +end diff --git a/spec/graphql/types/namespaces/user_level_permissions_spec.rb b/spec/graphql/types/namespaces/user_level_permissions_spec.rb new file mode 100644 index 00000000000..f36be39a40e --- /dev/null +++ b/spec/graphql/types/namespaces/user_level_permissions_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::UserLevelPermissions, feature_category: :shared do + include GraphqlHelpers + using RSpec::Parameterized::TableSyntax + + where(:namespace_class, :namespace_type_name) do + ::Group | ::Types::Namespaces::UserLevelPermissions::GroupNamespaceUserLevelPermissionsType + ::Namespaces::ProjectNamespace | ::Types::Namespaces::UserLevelPermissions::ProjectNamespaceUserLevelPermissionsType + end + + with_them do + describe ".resolve_type" do + it "knows the correct type for objects" do + namespace = namespace_class.new + + expect(described_class.resolve_type(namespace, {})) + .to eq(namespace_type_name) + end + end + + describe ".orphan_types" do + it "includes the type" do + expect(described_class.orphan_types).to include(namespace_type_name) + end + end + end + + it "raises an error for an unknown type" do + namespace = build(:project) + + expect { described_class.resolve_type(namespace, {}) } + .to raise_error("Unknown GraphQL type for namespace type #{namespace.class}") + end + + it_behaves_like "expose all user permissions fields for the namespace" +end diff --git a/spec/helpers/ci/pipeline_schedules_helper_spec.rb b/spec/helpers/ci/pipeline_schedules_helper_spec.rb index f0f9e0bc840..9ea54deed0a 100644 --- a/spec/helpers/ci/pipeline_schedules_helper_spec.rb +++ b/spec/helpers/ci/pipeline_schedules_helper_spec.rb @@ -27,7 +27,8 @@ RSpec.describe Ci::PipelineSchedulesHelper, feature_category: :continuous_integr schedules_path: pipeline_schedules_path(project), settings_link: project_settings_ci_cd_path(project), timezone_data: timezones.to_json, - can_set_pipeline_variables: 'false' + can_set_pipeline_variables: 'false', + worker_cron_expression: pipeline_schedule.worker_cron_expression }) end end diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index da7fdbec295..2e8ae3d38b1 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -489,4 +489,31 @@ RSpec.describe DeployToken, feature_category: :continuous_delivery do it { is_expected.to match(/gldt-[A-Za-z0-9_-]{20}/) } end + + describe '.with_encrypted_tokens' do + let(:deploy_token_1) { create(:deploy_token) } + let(:deploy_token_2) { create(:deploy_token) } + let(:deploy_token_3) { create(:deploy_token) } + + it 'returns deploy tokens matching the given token values' do + encrypted_token_values = [deploy_token_1.token_encrypted, deploy_token_3.token_encrypted] + + result = described_class.with_encrypted_tokens(encrypted_token_values) + + expect(result).to contain_exactly(deploy_token_1, deploy_token_3) + expect(result).not_to include(deploy_token_2) + end + + it 'returns an empty relation when no tokens match' do + result = described_class.with_encrypted_tokens(['non-existent-token']) + + expect(result).to be_empty + end + + it 'returns an empty relation when given an empty array' do + result = described_class.with_encrypted_tokens([]) + + expect(result).to be_empty + end + end end diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb index 2933a16ae6b..db28dae8df3 100644 --- a/spec/models/work_item_spec.rb +++ b/spec/models/work_item_spec.rb @@ -102,6 +102,30 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do end end + describe '.with_parent_ids' do + let_it_be(:parent_item) { create(:work_item, :epic, project: reusable_project) } + + context 'when given valid parent IDs' do + let_it_be(:child_item) { create(:work_item, project: reusable_project) } + + before do + create(:parent_link, work_item_parent: parent_item, work_item: child_item) + end + + it 'returns the work items with the specified parent IDs' do + expect(described_class.with_work_item_parent_ids([parent_item.id])).to contain_exactly(child_item) + end + end + + context 'when work item does not have parent link' do + let_it_be(:work_item_without_parent) { create(:work_item, project: reusable_project) } + + it 'does not return the work item' do + expect(described_class.with_work_item_parent_ids([parent_item.id])).to be_empty + end + end + end + describe '#create_dates_source_from_current_dates' do let_it_be(:start_date) { nil } let_it_be(:due_date) { nil } diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 51a5a5ca87c..8a2aa834c49 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -123,11 +123,25 @@ RSpec.describe API::Files, feature_category: :source_code_management do expect(response).to have_gitlab_http_status(expected_status) end + context 'when the user has agentic chat permission for the project' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :access_duo_agentic_chat, project) + .and_return(true) + end + + it 'is successful' do + file_action + + expect(response).to have_gitlab_http_status(expected_status) + end + end + context 'when the user does not have duo_workflow permission for the project' do before do allow(Ability).to receive(:allowed?).and_call_original allow(Ability).to receive(:allowed?).with(user, :duo_workflow, project) - .and_return(false) + .and_return(false) end it 'returns a forbidden error' do diff --git a/spec/requests/api/personal_access_tokens/self_rotation_spec.rb b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb index a162bfb1ff7..84c93711e33 100644 --- a/spec/requests/api/personal_access_tokens/self_rotation_spec.rb +++ b/spec/requests/api/personal_access_tokens/self_rotation_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :system_access do let(:path) { '/personal_access_tokens/self/rotate' } let(:token) { create(:personal_access_token, user: current_user) } - let(:expiry_date) { Date.today + 1.week } + let(:expiry_date) { 1.week.from_now } let(:params) { {} } let_it_be(:current_user) { create(:user) } @@ -19,7 +19,7 @@ RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :syste expect(response).to have_gitlab_http_status(:ok) expect(json_response['token']).not_to eq(token.token) - expect(json_response['expires_at']).to eq(expiry_date.to_s) + expect(json_response['expires_at']).to eq(expiry_date.to_date.iso8601) expect(token.reload).to be_revoked end end @@ -38,7 +38,7 @@ RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :syste it_behaves_like 'rotating token succeeds' context 'when expiry is defined' do - let(:expiry_date) { Date.today + 1.month } + let(:expiry_date) { 1.month.from_now } let(:params) { { expires_at: expiry_date } } it_behaves_like 'rotating token succeeds' @@ -70,7 +70,7 @@ RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :syste it_behaves_like 'rotating token succeeds' context 'when expiry is defined' do - let(:expiry_date) { Date.today + 1.month } + let(:expiry_date) { 1.month.from_now } let(:params) { { expires_at: expiry_date } } it_behaves_like 'rotating token succeeds' @@ -182,7 +182,7 @@ RSpec.describe API::PersonalAccessTokens::SelfRotation, feature_category: :syste it_behaves_like 'rotating token succeeds' context 'when expiry is defined' do - let(:expiry_date) { Date.today + 1.month } + let(:expiry_date) { 1.month.from_now } let(:params) { { expires_at: expiry_date } } it_behaves_like 'rotating token succeeds' diff --git a/spec/requests/api/resource_access_tokens/self_rotation_spec.rb b/spec/requests/api/resource_access_tokens/self_rotation_spec.rb index d95da2c750e..e0f810df98e 100644 --- a/spec/requests/api/resource_access_tokens/self_rotation_spec.rb +++ b/spec/requests/api/resource_access_tokens/self_rotation_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe API::ResourceAccessTokens::SelfRotation, feature_category: :system_access do let(:token) { create(:personal_access_token, user: current_user) } - let(:expiry_date) { Time.zone.today + 1.week } + let(:expiry_date) { 1.week.from_now } let(:params) { {} } subject(:rotate_token) { post(api(path, personal_access_token: token), params: params) } @@ -15,7 +15,7 @@ RSpec.describe API::ResourceAccessTokens::SelfRotation, feature_category: :syste expect(response).to have_gitlab_http_status(:ok) expect(json_response['token']).not_to eq(token.token) - expect(json_response['expires_at']).to eq(expiry_date.to_s) + expect(json_response['expires_at']).to eq(expiry_date.to_date.iso8601) expect(token.reload).to be_revoked end end @@ -123,7 +123,7 @@ RSpec.describe API::ResourceAccessTokens::SelfRotation, feature_category: :syste it_behaves_like 'rotating token succeeds' context 'when expiry is defined' do - let(:expiry_date) { Time.zone.today + 1.month } + let(:expiry_date) { 1.week.from_now } let(:params) { { expires_at: expiry_date } } it_behaves_like 'rotating token succeeds' diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb index 81d3d456c80..39da6b1ac2f 100644 --- a/spec/requests/api/resource_access_tokens_spec.rb +++ b/spec/requests/api/resource_access_tokens_spec.rb @@ -637,7 +637,7 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do expect(response).to have_gitlab_http_status(:ok) expect(json_response['token']).not_to eq(token.token) - expect(json_response['expires_at']).to eq((Date.today + 1.week).to_s) + expect(json_response['expires_at']).to eq(1.week.from_now.to_date.iso8601) end end @@ -676,7 +676,7 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do if source_type == 'project' expect(response).to have_gitlab_http_status(:ok) expect(json_response['token']).not_to eq(token.token) - expect(json_response['expires_at']).to eq((Date.today + 1.week).to_s) + expect(json_response['expires_at']).to eq(1.week.from_now.to_date.iso8601) else expect(response).to have_gitlab_http_status(:unauthorized) end @@ -685,7 +685,7 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do end context 'when expiry is defined' do - let(:expiry_date) { Date.today + 1.month } + let(:expiry_date) { 1.month.from_now } let(:params) { { expires_at: expiry_date } } before do @@ -698,7 +698,7 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do expect(response).to have_gitlab_http_status(:ok) expect(json_response['token']).not_to eq(token.token) - expect(json_response['expires_at']).to eq(expiry_date.to_s) + expect(json_response['expires_at']).to eq(expiry_date.to_date.iso8601) end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 46588c7fda1..15ac29a1c67 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -5007,7 +5007,7 @@ RSpec.describe API::Users, :with_current_organization, :aggregate_failures, feat describe 'POST /users/:user_id/personal_access_tokens', :with_current_organization do let(:name) { 'new pat' } let(:description) { 'new pat description' } - let(:expires_at) { 3.days.from_now.to_date.to_s } + let(:expires_at) { 3.days.from_now } let(:scopes) { %w[api read_user] } let(:path) { "/users/#{user.id}/personal_access_tokens" } let(:params) { { name: name, scopes: scopes, expires_at: expires_at, description: description } } @@ -5049,7 +5049,7 @@ RSpec.describe API::Users, :with_current_organization, :aggregate_failures, feat expect(json_response['name']).to eq(name) expect(json_response['description']).to eq(description) expect(json_response['scopes']).to eq(scopes) - expect(json_response['expires_at']).to eq(expires_at) + expect(json_response['expires_at']).to eq(expires_at.to_date.iso8601) expect(json_response['id']).to be_present expect(json_response['created_at']).to be_present expect(json_response['active']).to be_truthy diff --git a/spec/requests/groups/settings/access_tokens_controller_spec.rb b/spec/requests/groups/settings/access_tokens_controller_spec.rb index af93e4a6519..f65e5bcec77 100644 --- a/spec/requests/groups/settings/access_tokens_controller_spec.rb +++ b/spec/requests/groups/settings/access_tokens_controller_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Groups::Settings::AccessTokensController, feature_category: :syst end describe 'POST /:namespace/-/settings/access_tokens' do - let(:access_token_params) { { name: 'Nerd bot', description: 'Nerd bot description', scopes: ["api"], expires_at: Date.today + 1.month } } + let(:access_token_params) { { name: 'Nerd bot', description: 'Nerd bot description', scopes: ["api"], expires_at: 1.month.from_now } } subject do post group_settings_access_tokens_path(resource), params: { resource_access_token: access_token_params } @@ -84,7 +84,7 @@ RSpec.describe Groups::Settings::AccessTokensController, feature_category: :syst end context 'with custom access level' do - let(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: Date.today + 1.month, access_level: 20 } } + let(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: 1.month.from_now, access_level: 20 } } subject { post group_settings_access_tokens_path(resource), params: { resource_access_token: access_token_params } } diff --git a/spec/requests/projects/settings/access_tokens_controller_spec.rb b/spec/requests/projects/settings/access_tokens_controller_spec.rb index 6fd839cfebc..68524c94d27 100644 --- a/spec/requests/projects/settings/access_tokens_controller_spec.rb +++ b/spec/requests/projects/settings/access_tokens_controller_spec.rb @@ -54,7 +54,7 @@ RSpec.describe Projects::Settings::AccessTokensController, feature_category: :sy end describe 'POST /:namespace/:project/-/settings/access_tokens' do - let(:access_token_params) { { name: 'Nerd bot', description: 'Nerd bot description', scopes: ["api"], expires_at: Date.today + 1.month } } + let(:access_token_params) { { name: 'Nerd bot', description: 'Nerd bot description', scopes: ["api"], expires_at: 1.month.from_now } } subject do post project_settings_access_tokens_path(resource), params: { resource_access_token: access_token_params } @@ -85,7 +85,7 @@ RSpec.describe Projects::Settings::AccessTokensController, feature_category: :sy end context 'with custom access level' do - let(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: Date.today + 1.month, access_level: 20 } } + let(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: 1.month.from_now, access_level: 20 } } subject { post project_settings_access_tokens_path(resource), params: { resource_access_token: access_token_params } } diff --git a/spec/support/shared_examples/graphql/types/namespaces/user_permissions_shared_examples.rb b/spec/support/shared_examples/graphql/types/namespaces/user_permissions_shared_examples.rb new file mode 100644 index 00000000000..10394c0958e --- /dev/null +++ b/spec/support/shared_examples/graphql/types/namespaces/user_permissions_shared_examples.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.shared_examples "expose all user permissions fields for the namespace" do + include GraphqlHelpers + + specify do + expected_fields = %i[ + canAdminLabel + canCreateProjects + ] + + if Gitlab.ee? + expected_fields.push(*%i[ + canCreateEpic + canBulkEditEpics + ]) + end + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb index f135d29d532..003d73f93ec 100644 --- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb +++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb @@ -168,7 +168,7 @@ RSpec.shared_examples 'POST resource access tokens available' do expect(created_token.name).to eq(access_token_params[:name]) expect(created_token.description).to eq(access_token_params[:description]) expect(created_token.scopes).to eq(access_token_params[:scopes]) - expect(created_token.expires_at).to eq(access_token_params[:expires_at]) + expect(created_token.expires_at).to eq(access_token_params[:expires_at].to_date) expect(resource.member(created_token.user).access_level).to eq(access_level) end