Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ad393550bb
commit
a013498719
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -288,6 +288,11 @@
|
|||
"MergeRequestReviewer",
|
||||
"UserCore"
|
||||
],
|
||||
"UserLevelPermissions": [
|
||||
"GroupNamespaceUserLevelPermissions",
|
||||
"ProjectNamespaceUserLevelPermissions",
|
||||
"UserNamespaceUserLevelPermissions"
|
||||
],
|
||||
"VulnerabilityDetail": [
|
||||
"VulnerabilityDetailBase",
|
||||
"VulnerabilityDetailBoolean",
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const formatMessage = (findings, contentType) => {
|
|||
const header = sprintf(i18n.promptMessage(findings.length), { contentType });
|
||||
|
||||
const matchedPatterns = findings.map(({ patternName, matchedString }) => {
|
||||
return `<li>${escape(patternName)}: ${escape(matchedString)}</li>`;
|
||||
return `<li class='gl-wrap-anywhere'>${escape(patternName)}: ${escape(matchedString)}</li>`;
|
||||
});
|
||||
|
||||
const message = `
|
||||
|
|
|
|||
|
|
@ -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.'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="gl-mb-3 gl-mt-0 gl-text-subtle" data-testid="worker-cron-expression-hint">
|
||||
{{ sprintf($options.i18n.pipelineScheduleWorkerExplanation, { workerCronExpression }) }}
|
||||
<gl-link :href="pipelineScheduleWorkerUrl" target="_blank">
|
||||
{{ $options.i18n.pipelineScheduleWorkerLink }}
|
||||
</gl-link>
|
||||
</p>
|
||||
<gl-form-radio-group v-model="radioValue" :name="inputNameAttribute">
|
||||
<gl-form-radio
|
||||
v-for="option in radioOptions"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import PersistentUserCallout from './persistent_user_callout';
|
||||
|
||||
const PERSISTENT_USER_CALLOUTS = [
|
||||
'.js-persistent-callout',
|
||||
// NOTE: unique callouts selectors are to be removed within https://gitlab.com/gitlab-org/gitlab/-/issues/527723
|
||||
'.js-recovery-settings-callout',
|
||||
'.js-users-over-license-callout',
|
||||
'.js-admin-licensed-user-count-threshold',
|
||||
|
|
@ -32,7 +34,6 @@ const PERSISTENT_USER_CALLOUTS = [
|
|||
'.js-pipl-compliance-alert',
|
||||
'.gcp-signup-offer',
|
||||
'.js-gold-trial-callout',
|
||||
'.js-namespace-user-cap-reached-alert',
|
||||
'.js-data-collection-callout',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ export default {
|
|||
:variant="iconVariant"
|
||||
:class="iconClass"
|
||||
/>
|
||||
<span v-if="workItemTypeText" :class="{ 'gl-sr-only': !showText }">{{ workItemTypeText }}</span>
|
||||
<span v-if="workItemTypeText" :class="{ 'gl-sr-only !gl-absolute': !showText }">{{
|
||||
workItemTypeText
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
ActiveContext.configure do |config|
|
||||
config.enabled = false
|
||||
config.indexing_enabled = false
|
||||
end
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -12862,6 +12862,7 @@ Input type: `WorkItemExportInput`
|
|||
| <a id="mutationworkitemexportmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
|
||||
| <a id="mutationworkitemexportnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
|
||||
| <a id="mutationworkitemexportor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
|
||||
| <a id="mutationworkitemexportparentids"></a>`parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). |
|
||||
| <a id="mutationworkitemexportprojectpath"></a>`projectPath` | [`ID!`](#id) | Full project path. |
|
||||
| <a id="mutationworkitemexportsearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="mutationworkitemexportselectedfields"></a>`selectedFields` | [`[AvailableExportFields!]`](#availableexportfields) | List of selected fields to be exported. Omit to export all available fields. |
|
||||
|
|
@ -13041,6 +13042,7 @@ Input type: `WorkItemsCsvExportInput`
|
|||
| <a id="mutationworkitemscsvexportmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
|
||||
| <a id="mutationworkitemscsvexportnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
|
||||
| <a id="mutationworkitemscsvexportor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
|
||||
| <a id="mutationworkitemscsvexportparentids"></a>`parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). |
|
||||
| <a id="mutationworkitemscsvexportprojectpath"></a>`projectPath` | [`ID!`](#id) | Full project path. |
|
||||
| <a id="mutationworkitemscsvexportsearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="mutationworkitemscsvexportselectedfields"></a>`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.
|
|||
| <a id="countablevulnerabilitydismissedby"></a>`dismissedBy` | [`UserCore`](#usercore) | User that dismissed the vulnerability. |
|
||||
| <a id="countablevulnerabilityexternalissuelinks"></a>`externalIssueLinks` | [`VulnerabilityExternalIssueLinkConnection!`](#vulnerabilityexternalissuelinkconnection) | List of external issue links related to the vulnerability. (see [Connections](#connections)) |
|
||||
| <a id="countablevulnerabilityfalsepositive"></a>`falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. |
|
||||
| <a id="countablevulnerabilityfindingtokenstatus"></a>`findingTokenStatus` | [`VulnerabilityFindingTokenStatus`](#vulnerabilityfindingtokenstatus) | Status of the secret token associated with this vulnerability. Returns `null` if the `validity_checks` feature flag is disabled. |
|
||||
| <a id="countablevulnerabilityhasremediations"></a>`hasRemediations` | [`Boolean`](#boolean) | Indicates whether there is a remediation available for the vulnerability. |
|
||||
| <a id="countablevulnerabilityid"></a>`id` | [`ID!`](#id) | GraphQL ID of the vulnerability. |
|
||||
| <a id="countablevulnerabilityidentifiers"></a>`identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerability. |
|
||||
|
|
@ -25249,6 +25252,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="currentuserworkitemsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
|
||||
| <a id="currentuserworkitemsnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
|
||||
| <a id="currentuserworkitemsor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
|
||||
| <a id="currentuserworkitemsparentids"></a>`parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). |
|
||||
| <a id="currentuserworkitemssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="currentuserworkitemssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
|
||||
| <a id="currentuserworkitemsstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of the work item. |
|
||||
|
|
@ -27911,6 +27915,7 @@ GPG signature for a signed commit.
|
|||
| <a id="grouptotalrepositorysizeexcess"></a>`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. |
|
||||
| <a id="grouptwofactorgraceperiod"></a>`twoFactorGracePeriod` | [`Int`](#int) | Time before two-factor authentication is enforced. |
|
||||
| <a id="groupupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp of when the group was last updated. |
|
||||
| <a id="groupuserlevelpermissions"></a>`userLevelPermissions` {{< icon name="warning-solid" >}} | [`UserLevelPermissions`](#userlevelpermissions) | **Introduced** in GitLab 18.1. **Status**: Experiment. User permissions on the namespace. |
|
||||
| <a id="groupuserpermissions"></a>`userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. |
|
||||
| <a id="groupvaluestreamanalytics"></a>`valueStreamAnalytics` | [`ValueStreamAnalytics`](#valuestreamanalytics) | Information about Value Stream Analytics within the group. |
|
||||
| <a id="groupvisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
|
||||
|
|
@ -29574,6 +29579,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
|
|||
| <a id="groupworkitemstatecountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
|
||||
| <a id="groupworkitemstatecountsnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
|
||||
| <a id="groupworkitemstatecountsor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
|
||||
| <a id="groupworkitemstatecountsparentids"></a>`parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). |
|
||||
| <a id="groupworkitemstatecountsrequirementlegacywidget"></a>`requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. |
|
||||
| <a id="groupworkitemstatecountssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="groupworkitemstatecountssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
|
||||
|
|
@ -29646,6 +29652,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="groupworkitemsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
|
||||
| <a id="groupworkitemsnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
|
||||
| <a id="groupworkitemsor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
|
||||
| <a id="groupworkitemsparentids"></a>`parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). |
|
||||
| <a id="groupworkitemsrequirementlegacywidget"></a>`requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. |
|
||||
| <a id="groupworkitemssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="groupworkitemssort"></a>`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-
|
|||
| <a id="groupnamespacelinksreportabuse"></a>`reportAbuse` | [`String`](#string) | Namespace report_abuse. |
|
||||
| <a id="groupnamespacelinkssignin"></a>`signIn` | [`String`](#string) | Namespace sign_in_path. |
|
||||
|
||||
### `GroupNamespaceUserLevelPermissions`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupnamespaceuserlevelpermissionscanadminlabel"></a>`canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. |
|
||||
| <a id="groupnamespaceuserlevelpermissionscanbulkeditepics"></a>`canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. |
|
||||
| <a id="groupnamespaceuserlevelpermissionscancreateepic"></a>`canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. |
|
||||
| <a id="groupnamespaceuserlevelpermissionscancreateprojects"></a>`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.
|
|||
| <a id="namespacetimelogcategories"></a>`timelogCategories` {{< icon name="warning-solid" >}} | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in GitLab 15.3. **Status**: Experiment. Timelog categories for the namespace. |
|
||||
| <a id="namespacetotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
|
||||
| <a id="namespacetotalrepositorysizeexcess"></a>`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. |
|
||||
| <a id="namespaceuserlevelpermissions"></a>`userLevelPermissions` {{< icon name="warning-solid" >}} | [`UserLevelPermissions`](#userlevelpermissions) | **Introduced** in GitLab 18.1. **Status**: Experiment. User permissions on the namespace. |
|
||||
| <a id="namespaceuserpermissions"></a>`userPermissions` | [`NamespacePermissions!`](#namespacepermissions) | Permissions for the current user on the resource. |
|
||||
| <a id="namespacevisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
|
||||
| <a id="namespaceweburl"></a>`webUrl` | [`String`](#string) | URL of the object. |
|
||||
|
|
@ -37655,6 +37674,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
|
|||
| <a id="projectworkitemstatecountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
|
||||
| <a id="projectworkitemstatecountsnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
|
||||
| <a id="projectworkitemstatecountsor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
|
||||
| <a id="projectworkitemstatecountsparentids"></a>`parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). |
|
||||
| <a id="projectworkitemstatecountsrequirementlegacywidget"></a>`requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. |
|
||||
| <a id="projectworkitemstatecountssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="projectworkitemstatecountssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
|
||||
|
|
@ -37723,6 +37743,7 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| <a id="projectworkitemsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values `NONE` and `ANY` are supported. |
|
||||
| <a id="projectworkitemsnot"></a>`not` | [`NegatedWorkItemFilterInput`](#negatedworkitemfilterinput) | Negated work item arguments. |
|
||||
| <a id="projectworkitemsor"></a>`or` | [`UnionedWorkItemFilterInput`](#unionedworkitemfilterinput) | List of arguments with inclusive `OR`. |
|
||||
| <a id="projectworkitemsparentids"></a>`parentIds` | [`[WorkItemID!]`](#workitemid) | Filter work items by global IDs of their parent items (maximum is 100 items). |
|
||||
| <a id="projectworkitemsrequirementlegacywidget"></a>`requirementLegacyWidget` {{< icon name="warning-solid" >}} | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in GitLab 15.9. Use work item IID filter instead. |
|
||||
| <a id="projectworkitemssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="projectworkitemssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
|
||||
|
|
@ -37864,6 +37885,17 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
|
|||
| <a id="projectnamespacelinksreportabuse"></a>`reportAbuse` | [`String`](#string) | Namespace report_abuse. |
|
||||
| <a id="projectnamespacelinkssignin"></a>`signIn` | [`String`](#string) | Namespace sign_in_path. |
|
||||
|
||||
### `ProjectNamespaceUserLevelPermissions`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectnamespaceuserlevelpermissionscanadminlabel"></a>`canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. |
|
||||
| <a id="projectnamespaceuserlevelpermissionscanbulkeditepics"></a>`canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. |
|
||||
| <a id="projectnamespaceuserlevelpermissionscancreateepic"></a>`canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. |
|
||||
| <a id="projectnamespaceuserlevelpermissionscancreateprojects"></a>`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.
|
|||
| <a id="usernamespacelinksreportabuse"></a>`reportAbuse` | [`String`](#string) | Namespace report_abuse. |
|
||||
| <a id="usernamespacelinkssignin"></a>`signIn` | [`String`](#string) | Namespace sign_in_path. |
|
||||
|
||||
### `UserNamespaceUserLevelPermissions`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="usernamespaceuserlevelpermissionscanadminlabel"></a>`canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. |
|
||||
| <a id="usernamespaceuserlevelpermissionscanbulkeditepics"></a>`canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. |
|
||||
| <a id="usernamespaceuserlevelpermissionscancreateepic"></a>`canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. |
|
||||
| <a id="usernamespaceuserlevelpermissionscancreateprojects"></a>`canCreateProjects` | [`Boolean`](#boolean) | Whether the current user can create projects in the namespace. |
|
||||
|
||||
### `UserPermissions`
|
||||
|
||||
#### Fields
|
||||
|
|
@ -40946,6 +40989,7 @@ Represents a vulnerability.
|
|||
| <a id="vulnerabilitydismissedby"></a>`dismissedBy` | [`UserCore`](#usercore) | User that dismissed the vulnerability. |
|
||||
| <a id="vulnerabilityexternalissuelinks"></a>`externalIssueLinks` | [`VulnerabilityExternalIssueLinkConnection!`](#vulnerabilityexternalissuelinkconnection) | List of external issue links related to the vulnerability. (see [Connections](#connections)) |
|
||||
| <a id="vulnerabilityfalsepositive"></a>`falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. |
|
||||
| <a id="vulnerabilityfindingtokenstatus"></a>`findingTokenStatus` | [`VulnerabilityFindingTokenStatus`](#vulnerabilityfindingtokenstatus) | Status of the secret token associated with this vulnerability. Returns `null` if the `validity_checks` feature flag is disabled. |
|
||||
| <a id="vulnerabilityhasremediations"></a>`hasRemediations` | [`Boolean`](#boolean) | Indicates whether there is a remediation available for the vulnerability. |
|
||||
| <a id="vulnerabilityid"></a>`id` | [`ID!`](#id) | GraphQL ID of the vulnerability. |
|
||||
| <a id="vulnerabilityidentifiers"></a>`identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerability. |
|
||||
|
|
@ -41335,6 +41379,19 @@ Represents an external issue link of a vulnerability.
|
|||
| <a id="vulnerabilityexternalissuelinkid"></a>`id` | [`VulnerabilitiesExternalIssueLinkID!`](#vulnerabilitiesexternalissuelinkid) | GraphQL ID of the external issue link. |
|
||||
| <a id="vulnerabilityexternalissuelinklinktype"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="vulnerabilityfindingtokenstatuscreatedat"></a>`createdAt` | [`Time!`](#time) | When the token status was created. |
|
||||
| <a id="vulnerabilityfindingtokenstatusid"></a>`id` | [`ID!`](#id) | ID of the finding token status. |
|
||||
| <a id="vulnerabilityfindingtokenstatusstatus"></a>`status` | [`VulnerabilityFindingTokenStatusState!`](#vulnerabilityfindingtokenstatusstate) | Status of the token (unknown, active, inactive). |
|
||||
| <a id="vulnerabilityfindingtokenstatusupdatedat"></a>`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.
|
|||
| ----- | ----------- |
|
||||
| <a id="vulnerabilityexternalissuelinktypecreated"></a>`CREATED` | Created link type. |
|
||||
|
||||
### `VulnerabilityFindingTokenStatusState`
|
||||
|
||||
Status of a secret token found in a vulnerability.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="vulnerabilityfindingtokenstatusstateactive"></a>`ACTIVE` | Token is active and can be exploited. |
|
||||
| <a id="vulnerabilityfindingtokenstatusstateinactive"></a>`INACTIVE` | Token is inactive and cannot be exploited. |
|
||||
| <a id="vulnerabilityfindingtokenstatusstateunknown"></a>`UNKNOWN` | Token status is unknown. |
|
||||
|
||||
### `VulnerabilityGrade`
|
||||
|
||||
The grade of the vulnerable project.
|
||||
|
|
@ -49304,6 +49371,23 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="useruserachievementsincludehidden"></a>`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 |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="userlevelpermissionscanadminlabel"></a>`canAdminLabel` | [`Boolean`](#boolean) | Whether the current user can admin labels in the namespace. |
|
||||
| <a id="userlevelpermissionscanbulkeditepics"></a>`canBulkEditEpics` | [`Boolean`](#boolean) | Whether the current user can bulk edit epics in the group. |
|
||||
| <a id="userlevelpermissionscancreateepic"></a>`canCreateEpic` | [`Boolean`](#boolean) | Whether the current user can create epics in the group. |
|
||||
| <a id="userlevelpermissionscancreateprojects"></a>`canCreateProjects` | [`Boolean`](#boolean) | Whether the current user can create projects in the namespace. |
|
||||
|
||||
#### `VulnerabilityStatisticInterface`
|
||||
|
||||
Implementations:
|
||||
|
|
|
|||
|
|
@ -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`. |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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/) |
|
||||
|
|
|
|||
|
|
@ -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 | `<your_access_token>` |
|
||||
| Token type | Token value |
|
||||
|:----------------------|:------------|
|
||||
| Personal access token | `<your_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:
|
|||
|
||||
<!-- vale gitlab_base.Possessive = NO -->
|
||||
|
||||
| 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. |
|
||||
|
||||
<!-- vale gitlab_base.Possessive = YES -->
|
||||
|
||||
|
|
@ -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 | `{{</* icon name="dash-circle" */>}} No` | {{< icon name="dash-circle" >}} No |
|
||||
| Option | Markdown | Displayed result |
|
||||
|--------|---------------------------------------------------|------------------|
|
||||
| No | `{{</* icon name="dash-circle" */>}} No` | {{< icon name="dash-circle" >}} No |
|
||||
| Yes | `{{</* icon name="check-circle-filled" */>}} 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. <sup>1</sup> |
|
||||
| App B | Description text. <sup>2</sup> |
|
||||
```
|
||||
|
|
@ -838,8 +843,8 @@ For example:
|
|||
|
||||
The table and footnotes would render as follows:
|
||||
|
||||
| App name | Description |
|
||||
|:---------|:-------------------------------|
|
||||
| App name | Description |
|
||||
|:---------|:------------|
|
||||
| App A | Description text. <sup>1</sup> |
|
||||
| App B | Description text. <sup>2</sup> |
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.<br>- Download starts immediately. | - Report available immediately. | - No progress indicator.<br>- 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.<br>- Loads the file into memory.<br>- Sends the file to the client. | - Report available immediately. | - Large amount of data might cause request timeout.<br>- Memory intensive.<br>- 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)<br>- [Export License Usage File](../subscriptions/self_managed/_index.md#export-your-license-usage) |
|
||||
| As email attachment | - Asynchronously process the query with background job.<br>- Email uses the export as an attachment. | - Asynchronous processing. | - Requires users use a different app (email) to download the CSV.<br>- Email providers may limit attachment size. | - [Export issues](../user/project/issues/csv_export.md)<br>- [Export merge requests](../user/project/merge_requests/csv_export.md) |
|
||||
| As downloadable link in email (*) | - Asynchronously process the query with background job.<br>- Email uses an export link. | - Asynchronous processing.<br>- Bypasses email provider attachment size limit. | - Requires users use a different app (email).<br>- 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.<br>- Frontend(FE) polls every few seconds to check if CSV file is ready. | - Asynchronous processing.<br>- Automatically downloads to local machine on completion.<br>- In-app solution. | - Non-persistable request - request expires when the user goes to a different page.<br>- 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.<br>- Backend (BE) maintains the export state<br>- FE polls every few seconds to check status.<br>- FE shows 'Download link' when export is ready.<br>- User can download or regenerate a new report. | - Asynchronous processing.<br>- No database calls made during the polling requests (HTTP 304 status is returned until export status changes).<br>- Does not require user to stay on page until export is complete.<br>- In-app solution.<br>- Can be expanded into a generic CSV feature (such as dashboard / CSV API). | - Requires to maintain export states in DB.<br>- 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.<br>- Download starts immediately. | - Report available immediately. | - No progress indicator.<br>- 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.<br>- Loads the file into memory.<br>- Sends the file to the client. | - Report available immediately. | - Large amount of data might cause request timeout.<br>- Memory intensive.<br>- 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)<br>- [Export License Usage File](../subscriptions/self_managed/_index.md#export-your-license-usage) |
|
||||
| As email attachment | - Asynchronously process the query with background job.<br>- Email uses the export as an attachment. | - Asynchronous processing. | - Requires users use a different app (email) to download the CSV.<br>- Email providers may limit attachment size. | - [Export issues](../user/project/issues/csv_export.md)<br>- [Export merge requests](../user/project/merge_requests/csv_export.md) |
|
||||
| As downloadable link in email (*) | - Asynchronously process the query with background job.<br>- Email uses an export link. | - Asynchronous processing.<br>- Bypasses email provider attachment size limit. | - Requires users use a different app (email).<br>- 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.<br>- Frontend(FE) polls every few seconds to check if CSV file is ready. | - Asynchronous processing.<br>- Automatically downloads to local machine on completion.<br>- In-app solution. | - Non-persistable request - request expires when the user goes to a different page.<br>- 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.<br>- Backend (BE) maintains the export state<br>- FE polls every few seconds to check status.<br>- FE shows 'Download link' when export is ready.<br>- User can download or regenerate a new report. | - Asynchronous processing.<br>- No database calls made during the polling requests (HTTP 304 status is returned until export status changes).<br>- Does not require user to stay on page until export is complete.<br>- In-app solution.<br>- Can be expanded into a generic CSV feature (such as dashboard / CSV API). | - Requires to maintain export states in DB.<br>- 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" >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <!-- This cannot be validated programmatically //--> | |
|
||||
| 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 <!-- This cannot be validated programmatically //--> |
|
||||
|
||||
## Process for creating new guidelines and accompanying rules
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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% | <span style="color: #4c1">■</span> `#4c1` |
|
||||
| Acceptable | 90 up to 95% | <span style="color:#a3c51c"> ■</span> `#a3c51c` |
|
||||
| Medium | 75 up to 90% | <span style="color: #dfb317">■</span> `#dfb317` |
|
||||
| Low | 0 up to 75% | <span style="color: #e05d44">■</span> `#e05d44` |
|
||||
| Unknown | No coverage | <span style="color: #9f9f9f">■</span> `#9f9f9f` |
|
||||
| Test coverage | Percentage limits | Badge color |
|
||||
|---------------|-----------------------------|-------------|
|
||||
| Good | 95 up to and including 100% | <span style="color: #4c1">■</span> `#4c1` |
|
||||
| Acceptable | 90 up to 95% | <span style="color:#a3c51c"> ■</span> `#a3c51c` |
|
||||
| Medium | 75 up to 90% | <span style="color: #dfb317">■</span> `#dfb317` |
|
||||
| Low | 0 up to 75% | <span style="color: #e05d44">■</span> `#e05d44` |
|
||||
| Unknown | No coverage | <span style="color: #9f9f9f">■</span> `#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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue