Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
36ba0f6636
commit
664a3c2024
|
|
@ -786,7 +786,6 @@ RSpec/FeatureCategory:
|
|||
- 'ee/spec/models/ee/pages_deployment_spec.rb'
|
||||
- 'ee/spec/models/ee/preloaders/group_policy_preloader_spec.rb'
|
||||
- 'ee/spec/models/ee/project_authorization_spec.rb'
|
||||
- 'ee/spec/models/ee/project_statistics_spec.rb'
|
||||
- 'ee/spec/models/ee/protected_ref_spec.rb'
|
||||
- 'ee/spec/models/ee/release_spec.rb'
|
||||
- 'ee/spec/models/ee/resource_label_event_spec.rb'
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
{"name":"devise","version":"4.9.3","platform":"ruby","checksum":"480638d6c51b97f56da6e28d4f3e2a1b8e606681b316aa594b87c6ab94923488"},
|
||||
{"name":"devise-two-factor","version":"4.1.1","platform":"ruby","checksum":"c95f5b07533e62217aaed3c386874d94e2d472fb5f2b6598afe8600fc17a8b95"},
|
||||
{"name":"diff-lcs","version":"1.5.0","platform":"ruby","checksum":"49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67"},
|
||||
{"name":"diffy","version":"3.4.2","platform":"ruby","checksum":"36b42ffbe5138ddc56182107c24ad8d6b066ecfd2876829f391e3a4993d89ae1"},
|
||||
{"name":"diffy","version":"3.4.3","platform":"ruby","checksum":"4264b9e7db00d1cd426fcd32e36565779163cedc2340a95b0e6f025e71f9aaa7"},
|
||||
{"name":"digest-crc","version":"0.6.5","platform":"ruby","checksum":"5ca456f3352dc5ff17eb95deb3dd5a79dc79f8bf751d8005abca5b7b9b252124"},
|
||||
{"name":"discordrb-webhooks","version":"3.5.0","platform":"ruby","checksum":"52fba8bce3b08059d4a41a1e73a9a152958e788a9330275450126b44f01c23b1"},
|
||||
{"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"},
|
||||
|
|
|
|||
|
|
@ -525,7 +525,7 @@ GEM
|
|||
railties (~> 7.0)
|
||||
rotp (~> 6.0)
|
||||
diff-lcs (1.5.0)
|
||||
diffy (3.4.2)
|
||||
diffy (3.4.3)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
discordrb-webhooks (3.5.0)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
{"name":"devise","version":"4.9.3","platform":"ruby","checksum":"480638d6c51b97f56da6e28d4f3e2a1b8e606681b316aa594b87c6ab94923488"},
|
||||
{"name":"devise-two-factor","version":"4.1.1","platform":"ruby","checksum":"c95f5b07533e62217aaed3c386874d94e2d472fb5f2b6598afe8600fc17a8b95"},
|
||||
{"name":"diff-lcs","version":"1.5.0","platform":"ruby","checksum":"49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67"},
|
||||
{"name":"diffy","version":"3.4.2","platform":"ruby","checksum":"36b42ffbe5138ddc56182107c24ad8d6b066ecfd2876829f391e3a4993d89ae1"},
|
||||
{"name":"diffy","version":"3.4.3","platform":"ruby","checksum":"4264b9e7db00d1cd426fcd32e36565779163cedc2340a95b0e6f025e71f9aaa7"},
|
||||
{"name":"digest-crc","version":"0.6.5","platform":"ruby","checksum":"5ca456f3352dc5ff17eb95deb3dd5a79dc79f8bf751d8005abca5b7b9b252124"},
|
||||
{"name":"discordrb-webhooks","version":"3.5.0","platform":"ruby","checksum":"52fba8bce3b08059d4a41a1e73a9a152958e788a9330275450126b44f01c23b1"},
|
||||
{"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"},
|
||||
|
|
|
|||
|
|
@ -537,7 +537,7 @@ GEM
|
|||
railties (~> 7.0)
|
||||
rotp (~> 6.0)
|
||||
diff-lcs (1.5.0)
|
||||
diffy (3.4.2)
|
||||
diffy (3.4.3)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
discordrb-webhooks (3.5.0)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
{
|
||||
"AccessLevelInterface": [
|
||||
"ContainerProtectionAccessLevel",
|
||||
"ContainerProtectionTagRule"
|
||||
],
|
||||
"AlertManagementIntegration": [
|
||||
"AlertManagementHttpIntegration",
|
||||
"AlertManagementPrometheusIntegration"
|
||||
|
|
|
|||
|
|
@ -152,10 +152,10 @@ export default {
|
|||
<template v-if="activePanel">
|
||||
<div data-testid="active-panel-template" class="gl-flex gl-items-center gl-py-5">
|
||||
<div class="col-auto">
|
||||
<img aria-hidden :src="activePanel.imageSrc" />
|
||||
<img :alt="''" :src="activePanel.imageSrc" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4>{{ activePanel.title }}</h4>
|
||||
<h1 class="gl-heading-2-fixed gl-my-3">{{ activePanel.title }}</h1>
|
||||
|
||||
<p v-if="hasTextDetails">{{ details }}</p>
|
||||
<component :is="details" v-else v-bind="detailProps" />
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ export default {
|
|||
return this.glFeatures.notificationsTodosButtons;
|
||||
},
|
||||
parentWorkItem() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY)?.parent;
|
||||
return this.findWidget(WIDGET_TYPE_HIERARCHY)?.parent;
|
||||
},
|
||||
parentWorkItemId() {
|
||||
return this.parentWorkItem?.id;
|
||||
|
|
@ -364,10 +364,10 @@ export default {
|
|||
return this.workItem.workItemType?.iconName;
|
||||
},
|
||||
hasDescriptionWidget() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_DESCRIPTION);
|
||||
return this.findWidget(WIDGET_TYPE_DESCRIPTION);
|
||||
},
|
||||
hasDesignWidget() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_DESIGNS) && this.$router;
|
||||
return this.findWidget(WIDGET_TYPE_DESIGNS) && this.$router;
|
||||
},
|
||||
showUploadDesign() {
|
||||
return this.hasDesignWidget && this.workspacePermissions.createDesign;
|
||||
|
|
@ -376,10 +376,10 @@ export default {
|
|||
return this.hasDesignWidget && this.workspacePermissions.moveDesign;
|
||||
},
|
||||
workItemNotificationsSubscribed() {
|
||||
return Boolean(this.isWidgetPresent(WIDGET_TYPE_NOTIFICATIONS)?.subscribed);
|
||||
return Boolean(this.findWidget(WIDGET_TYPE_NOTIFICATIONS)?.subscribed);
|
||||
},
|
||||
workItemCurrentUserTodos() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_CURRENT_USER_TODOS);
|
||||
return this.findWidget(WIDGET_TYPE_CURRENT_USER_TODOS);
|
||||
},
|
||||
showWorkItemCurrentUserTodos() {
|
||||
return Boolean(this.$options.isLoggedIn && this.workItemCurrentUserTodos);
|
||||
|
|
@ -388,22 +388,22 @@ export default {
|
|||
return this.workItemCurrentUserTodos?.currentUserTodos?.nodes;
|
||||
},
|
||||
workItemAssignees() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
|
||||
return this.findWidget(WIDGET_TYPE_ASSIGNEES);
|
||||
},
|
||||
workItemAwardEmoji() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_AWARD_EMOJI);
|
||||
return this.findWidget(WIDGET_TYPE_AWARD_EMOJI);
|
||||
},
|
||||
workItemHierarchy() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY);
|
||||
return this.findWidget(WIDGET_TYPE_HIERARCHY);
|
||||
},
|
||||
workItemNotes() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_NOTES);
|
||||
return this.findWidget(WIDGET_TYPE_NOTES);
|
||||
},
|
||||
workItemWeight() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
|
||||
return this.findWidget(WIDGET_TYPE_WEIGHT);
|
||||
},
|
||||
workItemDevelopment() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_DEVELOPMENT);
|
||||
return this.findWidget(WIDGET_TYPE_DEVELOPMENT);
|
||||
},
|
||||
workItemBodyClass() {
|
||||
return {
|
||||
|
|
@ -415,11 +415,11 @@ export default {
|
|||
},
|
||||
workItemLinkedItems() {
|
||||
return this.workItemType === WORK_ITEM_TYPE_VALUE_EPIC
|
||||
? this.isWidgetPresent(WIDGET_TYPE_LINKED_ITEMS) && this.hasLinkedItemsEpicsFeature
|
||||
: this.isWidgetPresent(WIDGET_TYPE_LINKED_ITEMS);
|
||||
? this.findWidget(WIDGET_TYPE_LINKED_ITEMS) && this.hasLinkedItemsEpicsFeature
|
||||
: this.findWidget(WIDGET_TYPE_LINKED_ITEMS);
|
||||
},
|
||||
showWorkItemTree() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY) && this.allowedChildTypes?.length > 0;
|
||||
return this.findWidget(WIDGET_TYPE_HIERARCHY) && this.allowedChildTypes?.length > 0;
|
||||
},
|
||||
titleClassHeader() {
|
||||
return {
|
||||
|
|
@ -495,7 +495,7 @@ export default {
|
|||
enableEditMode() {
|
||||
this.editMode = true;
|
||||
},
|
||||
isWidgetPresent(type) {
|
||||
findWidget(type) {
|
||||
return this.widgets?.find((widget) => widget.type === type);
|
||||
},
|
||||
toggleConfidentiality(confidentialStatus) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,50 @@ module Types
|
|||
field :revision, GraphQL::Types::String, null: true, description: 'Revision of the tag.'
|
||||
field :short_revision, GraphQL::Types::String, null: true, description: 'Short revision of the tag.'
|
||||
field :total_size, GraphQL::Types::BigInt, null: true, description: 'Size of the tag.'
|
||||
|
||||
field :protection,
|
||||
Types::ContainerRegistry::Protection::AccessLevelType,
|
||||
null: true,
|
||||
experiment: { milestone: '17.9' },
|
||||
description: 'Minimum GitLab access level required to push and delete container image tags. ' \
|
||||
'If multiple protection rules match an image tag, the highest access levels are applied'
|
||||
|
||||
def protection
|
||||
return unless Feature.enabled?(:container_registry_protected_tags, project)
|
||||
|
||||
highest_matching_rule
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project
|
||||
object.repository.project
|
||||
end
|
||||
|
||||
def highest_matching_rule
|
||||
result = ::ContainerRegistry::Protection::TagRule.new
|
||||
|
||||
project.container_registry_protection_tag_rules.each do |rule|
|
||||
next unless Gitlab::UntrustedRegexp.new(rule.tag_name_pattern).match?(object.name)
|
||||
|
||||
set_max_access_level(result, rule)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def set_max_access_level(result, rule)
|
||||
%i[push delete].each do |action|
|
||||
attribute = :"minimum_access_level_for_#{action}"
|
||||
|
||||
result[attribute] = [
|
||||
# minimum_access_level_for_push_before_type_cast will return the
|
||||
# enum's numeric value so we can correctly use .max on it.
|
||||
result.method(:"#{attribute}_before_type_cast").call.to_i,
|
||||
rule.method(:"#{attribute}_before_type_cast").call
|
||||
].max
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module ContainerRegistry
|
||||
module Protection
|
||||
module AccessLevelInterface
|
||||
include BaseInterface
|
||||
|
||||
field :minimum_access_level_for_delete,
|
||||
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
|
||||
null: true,
|
||||
experiment: { milestone: '17.8' },
|
||||
description:
|
||||
'Minimum GitLab access level required to delete container image tags from the container repository. ' \
|
||||
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
|
||||
'If the value is `nil`, no minimum access level is enforced. ' \
|
||||
'Users with the Developer role or higher can delete tags by default.'
|
||||
|
||||
field :minimum_access_level_for_push,
|
||||
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
|
||||
null: true,
|
||||
experiment: { milestone: '17.8' },
|
||||
description:
|
||||
'Minimum GitLab access level required to push container image tags to the container repository. ' \
|
||||
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
|
||||
'If the value is `nil`, no minimum access level is enforced. ' \
|
||||
'Users with the Developer role or higher can push tags by default.'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module ContainerRegistry
|
||||
module Protection
|
||||
class AccessLevelType < ::Types::BaseObject # rubocop:disable Graphql/AuthorizeTypes -- it inherits the same authorization as the caller
|
||||
graphql_name 'ContainerProtectionAccessLevel'
|
||||
description 'Represents the most restrictive permissions for a container image tag'
|
||||
|
||||
implements Types::ContainerRegistry::Protection::AccessLevelInterface
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,6 +8,8 @@ module Types
|
|||
description 'A container repository tag protection rule designed to prevent users with a certain ' \
|
||||
'access level or lower from altering the container registry.'
|
||||
|
||||
implements Types::ContainerRegistry::Protection::AccessLevelInterface
|
||||
|
||||
authorize :admin_container_image
|
||||
|
||||
field :id,
|
||||
|
|
@ -23,26 +25,6 @@ module Types
|
|||
description:
|
||||
'The pattern that matches container image tags to protect. ' \
|
||||
'For example, `v1.*`. Wildcard character `*` allowed.'
|
||||
|
||||
# rubocop:disable GraphQL/ExtractType -- These are stored as separate fields
|
||||
field :minimum_access_level_for_delete,
|
||||
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
|
||||
null: true,
|
||||
experiment: { milestone: '17.8' },
|
||||
description:
|
||||
'Minimum GitLab access level required to delete container image tags from the container repository. ' \
|
||||
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
|
||||
'If the value is `nil`, the default minimum access level is `DEVELOPER`.'
|
||||
|
||||
field :minimum_access_level_for_push,
|
||||
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
|
||||
null: true,
|
||||
experiment: { milestone: '17.8' },
|
||||
description:
|
||||
'Minimum GitLab access level required to push container image tags to the container repository. ' \
|
||||
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
|
||||
'If the value is `nil`, the default minimum access level is `DEVELOPER`.'
|
||||
# rubocop:enable GraphQL/ExtractType -- These are stored as user preferences
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Integrations
|
||||
module Base
|
||||
module CustomIssueTracker
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Base::IssueTracker
|
||||
include HasIssueTrackerFields
|
||||
|
||||
class_methods do
|
||||
def title
|
||||
s_('IssueTracker|Custom issue tracker')
|
||||
end
|
||||
|
||||
def description
|
||||
s_("IssueTracker|Use a custom issue tracker as this project's issue tracker.")
|
||||
end
|
||||
|
||||
def help
|
||||
build_help_page_url(
|
||||
'user/project/integrations/custom_issue_tracker.md',
|
||||
s_("IssueTracker|Use a custom issue tracker that is not in the integration list.")
|
||||
)
|
||||
end
|
||||
|
||||
def to_param
|
||||
'custom_issue_tracker'
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Integrations
|
||||
module Base
|
||||
module DiffblueCover
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def title
|
||||
'Diffblue Cover'
|
||||
end
|
||||
|
||||
def description
|
||||
s_('DiffblueCover|Automatically write comprehensive, human-like Java unit tests.')
|
||||
end
|
||||
|
||||
def to_param
|
||||
'diffblue_cover'
|
||||
end
|
||||
|
||||
def help
|
||||
s_('DiffblueCover|Automatically write comprehensive, human-like Java unit tests.')
|
||||
end
|
||||
|
||||
def supported_events
|
||||
[]
|
||||
end
|
||||
|
||||
def diffblue_link
|
||||
ActionController::Base.helpers.link_to(
|
||||
s_('DiffblueCover|Try Diffblue Cover'),
|
||||
'https://www.diffblue.com/try-cover/gitlab/',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
field :diffblue_license_key,
|
||||
section: Integrations::Base::Integration::SECTION_TYPE_CONNECTION,
|
||||
type: :password,
|
||||
title: -> { s_('DiffblueCover|License key') },
|
||||
description: -> { s_('DiffblueCover|Diffblue Cover license key.') },
|
||||
non_empty_password_title: -> { s_('DiffblueCover|License key') },
|
||||
non_empty_password_help: -> {
|
||||
s_(
|
||||
'DiffblueCover|Leave blank to use your current license key.'
|
||||
)
|
||||
},
|
||||
exposes_secrets: true,
|
||||
required: true,
|
||||
is_secret: true,
|
||||
placeholder: 'XXXX-XXXX-XXXX-XXXX',
|
||||
help: -> {
|
||||
format(
|
||||
s_(
|
||||
'DiffblueCover|Enter your Diffblue Cover license key or ' \
|
||||
'go to %{diffblue_link} to obtain a free trial license.'
|
||||
),
|
||||
diffblue_link: diffblue_link
|
||||
)
|
||||
}
|
||||
|
||||
field :diffblue_access_token_name,
|
||||
section: Integrations::Base::Integration::SECTION_TYPE_CONFIGURATION,
|
||||
title: -> { s_('DiffblueCover|Name') },
|
||||
description: -> { s_('DiffblueCover|Access token name used by Diffblue Cover in pipelines.') },
|
||||
required: true,
|
||||
placeholder: -> { s_('DiffblueCover|My token name') }
|
||||
|
||||
field :diffblue_access_token_secret,
|
||||
section: Integrations::Base::Integration::SECTION_TYPE_CONFIGURATION,
|
||||
type: :password,
|
||||
title: -> { s_('DiffblueCover|Secret') },
|
||||
description: -> { s_('DiffblueCover|Access token secret used by Diffblue Cover in pipelines.') },
|
||||
non_empty_password_title: -> { s_('DiffblueCover|Secret') },
|
||||
non_empty_password_help: -> { s_('DiffblueCover|Leave blank to use your current secret value.') },
|
||||
required: true,
|
||||
is_secret: true,
|
||||
placeholder: 'glpat-XXXXXXXXXXXXXXXXXXXX' # gitleaks:allow
|
||||
|
||||
with_options if: :activated? do
|
||||
validates :diffblue_license_key, presence: true
|
||||
validates :diffblue_access_token_name, presence: true
|
||||
validates :diffblue_access_token_secret, presence: true
|
||||
end
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
ActionController::Base.helpers.image_path('illustrations/third-party-logos/integrations-logos/diffblue.svg')
|
||||
end
|
||||
|
||||
def sections
|
||||
[
|
||||
{
|
||||
type: Integrations::Base::Integration::SECTION_TYPE_CONNECTION,
|
||||
title: s_('DiffblueCover|Integration details'),
|
||||
description:
|
||||
s_(
|
||||
'DiffblueCover|Diffblue Cover is a generative AI platform that automatically ' \
|
||||
'writes comprehensive, human-like Java unit tests. Integrate Diffblue ' \
|
||||
'Cover into your CI/CD workflow for fully autonomous operation.'
|
||||
)
|
||||
},
|
||||
{
|
||||
type: Integrations::Base::Integration::SECTION_TYPE_CONFIGURATION,
|
||||
title: s_('DiffblueCover|Access token'),
|
||||
description:
|
||||
'You must have a GitLab access token for Diffblue Cover to access your project. ' \
|
||||
'Use a GitLab access token with at least the Developer role and ' \
|
||||
'the <code>api</code> and <code>write_repository</code> permissions.'
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def execute(_data) end
|
||||
|
||||
def ci_variables
|
||||
return [] unless activated?
|
||||
|
||||
[
|
||||
{ key: 'DIFFBLUE_LICENSE_KEY', value: diffblue_license_key, public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN_NAME', value: diffblue_access_token_name, public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN', value: diffblue_access_token_secret, public: false, masked: true }
|
||||
]
|
||||
end
|
||||
|
||||
def testable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Integrations
|
||||
module Base
|
||||
module Ewm
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Base::IssueTracker
|
||||
include HasIssueTrackerFields
|
||||
|
||||
class_methods do
|
||||
def title
|
||||
'EWM'
|
||||
end
|
||||
|
||||
def description
|
||||
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
|
||||
end
|
||||
|
||||
def help
|
||||
build_help_page_url(
|
||||
'user/project/integrations/ewm.md',
|
||||
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
|
||||
)
|
||||
end
|
||||
|
||||
def to_param
|
||||
'ewm'
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
|
||||
|
||||
def reference_pattern(*)
|
||||
@reference_pattern ||= %r{(?<issue>\b(?:bug|task|work item|workitem|rtcwi|defect)\b\s+\d+)}i
|
||||
end
|
||||
|
||||
def issue_url(iid)
|
||||
issues_url.gsub(':id', iid.to_s.split(' ')[-1])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,28 +2,6 @@
|
|||
|
||||
module Integrations
|
||||
class CustomIssueTracker < Integration
|
||||
include Base::IssueTracker
|
||||
include HasIssueTrackerFields
|
||||
|
||||
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
|
||||
|
||||
def self.title
|
||||
s_('IssueTracker|Custom issue tracker')
|
||||
end
|
||||
|
||||
def self.description
|
||||
s_("IssueTracker|Use a custom issue tracker as this project's issue tracker.")
|
||||
end
|
||||
|
||||
def self.help
|
||||
build_help_page_url(
|
||||
'user/project/integrations/custom_issue_tracker.md',
|
||||
s_("IssueTracker|Use a custom issue tracker that is not in the integration list.")
|
||||
)
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'custom_issue_tracker'
|
||||
end
|
||||
include Integrations::Base::CustomIssueTracker
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,125 +2,6 @@
|
|||
|
||||
module Integrations
|
||||
class DiffblueCover < Integration
|
||||
field :diffblue_license_key,
|
||||
section: SECTION_TYPE_CONNECTION,
|
||||
type: :password,
|
||||
title: -> { s_('DiffblueCover|License key') },
|
||||
description: -> { s_('DiffblueCover|Diffblue Cover license key.') },
|
||||
non_empty_password_title: -> { s_('DiffblueCover|License key') },
|
||||
non_empty_password_help: -> {
|
||||
s_(
|
||||
'DiffblueCover|Leave blank to use your current license key.'
|
||||
)
|
||||
},
|
||||
exposes_secrets: true,
|
||||
required: true,
|
||||
is_secret: true,
|
||||
placeholder: 'XXXX-XXXX-XXXX-XXXX',
|
||||
help: -> {
|
||||
format(
|
||||
s_(
|
||||
'DiffblueCover|Enter your Diffblue Cover license key or ' \
|
||||
'go to %{diffblue_link} to obtain a free trial license.'
|
||||
),
|
||||
diffblue_link: diffblue_link
|
||||
)
|
||||
}
|
||||
|
||||
field :diffblue_access_token_name,
|
||||
section: SECTION_TYPE_CONFIGURATION,
|
||||
title: -> { s_('DiffblueCover|Name') },
|
||||
description: -> { s_('DiffblueCover|Access token name used by Diffblue Cover in pipelines.') },
|
||||
required: true,
|
||||
placeholder: -> { s_('DiffblueCover|My token name') }
|
||||
|
||||
field :diffblue_access_token_secret,
|
||||
section: SECTION_TYPE_CONFIGURATION,
|
||||
type: :password,
|
||||
title: -> { s_('DiffblueCover|Secret') },
|
||||
description: -> { s_('DiffblueCover|Access token secret used by Diffblue Cover in pipelines.') },
|
||||
non_empty_password_title: -> { s_('DiffblueCover|Secret') },
|
||||
non_empty_password_help: -> { s_('DiffblueCover|Leave blank to use your current secret value.') },
|
||||
required: true,
|
||||
is_secret: true,
|
||||
placeholder: 'glpat-XXXXXXXXXXXXXXXXXXXX' # gitleaks:allow
|
||||
|
||||
with_options if: :activated? do
|
||||
validates :diffblue_license_key, presence: true
|
||||
validates :diffblue_access_token_name, presence: true
|
||||
validates :diffblue_access_token_secret, presence: true
|
||||
end
|
||||
|
||||
def self.title
|
||||
'Diffblue Cover'
|
||||
end
|
||||
|
||||
def self.description
|
||||
s_('DiffblueCover|Automatically write comprehensive, human-like Java unit tests.')
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'diffblue_cover'
|
||||
end
|
||||
|
||||
def self.help
|
||||
s_('DiffblueCover|Automatically write comprehensive, human-like Java unit tests.')
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
ActionController::Base.helpers.image_path('illustrations/third-party-logos/integrations-logos/diffblue.svg')
|
||||
end
|
||||
|
||||
def self.supported_events
|
||||
[]
|
||||
end
|
||||
|
||||
def sections
|
||||
[
|
||||
{
|
||||
type: SECTION_TYPE_CONNECTION,
|
||||
title: s_('DiffblueCover|Integration details'),
|
||||
description:
|
||||
s_(
|
||||
'DiffblueCover|Diffblue Cover is a generative AI platform that automatically ' \
|
||||
'writes comprehensive, human-like Java unit tests. Integrate Diffblue ' \
|
||||
'Cover into your CI/CD workflow for fully autonomous operation.'
|
||||
)
|
||||
},
|
||||
{
|
||||
type: SECTION_TYPE_CONFIGURATION,
|
||||
title: s_('DiffblueCover|Access token'),
|
||||
description:
|
||||
'You must have a GitLab access token for Diffblue Cover to access your project. ' \
|
||||
'Use a GitLab access token with at least the Developer role and ' \
|
||||
'the <code>api</code> and <code>write_repository</code> permissions.'
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def execute(_data) end
|
||||
|
||||
def ci_variables
|
||||
return [] unless activated?
|
||||
|
||||
[
|
||||
{ key: 'DIFFBLUE_LICENSE_KEY', value: diffblue_license_key, public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN_NAME', value: diffblue_access_token_name, public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN', value: diffblue_access_token_secret, public: false, masked: true }
|
||||
]
|
||||
end
|
||||
|
||||
def testable?
|
||||
false
|
||||
end
|
||||
|
||||
def self.diffblue_link
|
||||
ActionController::Base.helpers.link_to(
|
||||
s_('DiffblueCover|Try Diffblue Cover'),
|
||||
'https://www.diffblue.com/try-cover/gitlab/',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
)
|
||||
end
|
||||
include Integrations::Base::DiffblueCover
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,36 +2,6 @@
|
|||
|
||||
module Integrations
|
||||
class Ewm < Integration
|
||||
include Base::IssueTracker
|
||||
include HasIssueTrackerFields
|
||||
|
||||
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
|
||||
|
||||
def reference_pattern(only_long: true)
|
||||
@reference_pattern ||= %r{(?<issue>\b(?:bug|task|work item|workitem|rtcwi|defect)\b\s+\d+)}i
|
||||
end
|
||||
|
||||
def self.title
|
||||
'EWM'
|
||||
end
|
||||
|
||||
def self.description
|
||||
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
|
||||
end
|
||||
|
||||
def self.help
|
||||
build_help_page_url(
|
||||
'user/project/integrations/ewm.md',
|
||||
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
|
||||
)
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'ewm'
|
||||
end
|
||||
|
||||
def issue_url(iid)
|
||||
issues_url.gsub(':id', iid.to_s.split(' ')[-1])
|
||||
end
|
||||
include Integrations::Base::Ewm
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Integrations
|
||||
module Instance
|
||||
class CustomIssueTracker < Integration
|
||||
# To be updated as part of https://gitlab.com/gitlab-org/gitlab/-/issues/474809
|
||||
include Integrations::Base::CustomIssueTracker
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Integrations
|
||||
module Instance
|
||||
class DiffblueCover < Integration
|
||||
# To be updated as part of https://gitlab.com/gitlab-org/gitlab/-/issues/474809
|
||||
include Integrations::Base::DiffblueCover
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Integrations
|
||||
module Instance
|
||||
class Ewm < Integration
|
||||
# To be updated as part of https://gitlab.com/gitlab-org/gitlab/-/issues/474809
|
||||
include Integrations::Base::Ewm
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
= gitlab_ui_form_with url: configure_import_bulk_imports_path(namespace_id: params[:parent_id]), class: 'gl-show-field-errors' do |f|
|
||||
.gl-border-l-solid.gl-border-r-solid.gl-border-t-solid.gl-border-default.gl-border-1.gl-p-5.gl-mt-4
|
||||
.gl-flex.gl-items-center.gl-justify-between
|
||||
%h4.gl-flex
|
||||
%h2.gl-flex.gl-heading-2-fixed.gl-my-3
|
||||
= s_('GroupsNew|Import groups by direct transfer')
|
||||
= render Pajamas::ButtonComponent.new(href: history_import_bulk_imports_path, category: :secondary, variant: :confirm, size: :small) do
|
||||
= s_('BulkImport|View import history')
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
= gitlab_ui_form_for '', url: import_gitlab_group_path, namespace: 'import_group', class: 'group-form gl-show-field-errors', multipart: true do |f|
|
||||
.gl-border-l-solid.gl-border-r-solid.gl-border-default.gl-border-1.gl-p-5
|
||||
%h4
|
||||
%h2.gl-heading-2-fixed.gl-my-3
|
||||
= _('Import group from file')
|
||||
= render Pajamas::AlertComponent.new(variant: :warning,
|
||||
alert_options: { class: 'gl-mb-5' },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateSiphonNotes < ClickHouse::Migration
|
||||
def up
|
||||
execute <<-SQL
|
||||
CREATE TABLE IF NOT EXISTS siphon_notes
|
||||
(
|
||||
note Nullable(String),
|
||||
noteable_type LowCardinality(String),
|
||||
author_id Nullable(Int64),
|
||||
created_at Nullable(DateTime64(6, 'UTC')),
|
||||
updated_at Nullable(DateTime64(6, 'UTC')),
|
||||
project_id Nullable(Int64),
|
||||
attachment Nullable(String) DEFAULT '',
|
||||
line_code Nullable(String),
|
||||
commit_id Nullable(String),
|
||||
noteable_id Nullable(Int64),
|
||||
system Bool DEFAULT false,
|
||||
st_diff Nullable(String),
|
||||
updated_by_id Nullable(Int64),
|
||||
type LowCardinality(String) DEFAULT '',
|
||||
position Nullable(String),
|
||||
original_position Nullable(String),
|
||||
resolved_at Nullable(DateTime64(6, 'UTC')),
|
||||
resolved_by_id Nullable(Int64),
|
||||
discussion_id Nullable(String),
|
||||
note_html Nullable(String),
|
||||
cached_markdown_version Nullable(Int64),
|
||||
change_position Nullable(String),
|
||||
resolved_by_push Nullable(Bool),
|
||||
review_id Nullable(Int64),
|
||||
confidential Nullable(Bool),
|
||||
last_edited_at Nullable(DateTime64(6, 'UTC')),
|
||||
internal Bool DEFAULT false,
|
||||
id Int64,
|
||||
namespace_id Nullable(Int64),
|
||||
imported_from Int8 DEFAULT 0,
|
||||
_siphon_replicated_at DateTime64(6, 'UTC') DEFAULT now(),
|
||||
_siphon_deleted Bool DEFAULT FALSE
|
||||
)
|
||||
ENGINE = ReplacingMergeTree(_siphon_replicated_at, _siphon_deleted)
|
||||
PRIMARY KEY id
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<-SQL
|
||||
DROP TABLE IF EXISTS siphon_notes
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
@ -739,16 +739,37 @@ For Linux package (Omnibus):
|
|||
gitlab_rails['backup_upload_connection'] = {
|
||||
'provider' => 'AWS',
|
||||
'region' => 'eu-west-1',
|
||||
# Choose one authentication method
|
||||
# IAM Profile
|
||||
'use_iam_profile' => true
|
||||
# OR AWS Access and Secret key
|
||||
'aws_access_key_id' => 'AKIAKIAKI',
|
||||
'aws_secret_access_key' => 'secret123'
|
||||
# If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key
|
||||
# 'use_iam_profile' => true
|
||||
}
|
||||
gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
|
||||
# Consider using multipart uploads when file size reaches 100MB. Enter a number in bytes.
|
||||
# gitlab_rails['backup_multipart_chunk_size'] = 104857600
|
||||
```
|
||||
|
||||
1. If you're using the IAM Profile authentication method, ensure the instance where `backup-utility` is to be run has the following policy set (replace `<backups-bucket>` with the correct bucket name):
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:DeleteObject"
|
||||
],
|
||||
"Resource": "arn:aws:s3:::<backups-bucket>/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation)
|
||||
for the changes to take effect
|
||||
|
||||
|
|
@ -889,57 +910,6 @@ For self-compiled installations:
|
|||
1. [Restart GitLab](../restart_gitlab.md#self-compiled-installations)
|
||||
for the changes to take effect
|
||||
|
||||
If you're uploading your backups to S3, you should create a new
|
||||
IAM user with restricted access rights. To give the upload user access only for
|
||||
uploading backups create the following IAM profile, replacing `my.s3.bucket`
|
||||
with the name of your bucket:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Stmt1412062044000",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:AbortMultipartUpload",
|
||||
"s3:GetBucketAcl",
|
||||
"s3:GetBucketLocation",
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAcl",
|
||||
"s3:ListBucketMultipartUploads",
|
||||
"s3:PutObject",
|
||||
"s3:PutObjectAcl"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::my.s3.bucket/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sid": "Stmt1412062097000",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetBucketLocation",
|
||||
"s3:ListAllMyBuckets"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sid": "Stmt1412062128000",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::my.s3.bucket"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### Using Google Cloud Storage
|
||||
|
||||
To use Google Cloud Storage to save backups, you must first create an
|
||||
|
|
|
|||
|
|
@ -230,7 +230,6 @@ To set up an instance profile:
|
|||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:PutObject",
|
||||
|
|
@ -238,6 +237,13 @@ To set up an instance profile:
|
|||
"s3:DeleteObject"
|
||||
],
|
||||
"Resource": "arn:aws:s3:::test-bucket/*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": "arn:aws:s3:::test-bucket"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3825,8 +3825,8 @@ Input type: `createContainerProtectionTagRuleInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcreatecontainerprotectiontagruleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerProtectionTagRuleAccessLevel!`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the default minimum access level is `DEVELOPER`. Introduced in GitLab 17.8: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerProtectionTagRuleAccessLevel!`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the default minimum access level is `DEVELOPER`. Introduced in GitLab 17.8: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerProtectionTagRuleAccessLevel!`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can delete tags by default. Introduced in GitLab 17.8: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerProtectionTagRuleAccessLevel!`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can push tags by default. Introduced in GitLab 17.8: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerprotectiontagruleprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project containing the container image tags. |
|
||||
| <a id="mutationcreatecontainerprotectiontagruletagnamepattern"></a>`tagNamePattern` | [`String!`](#string) | The pattern that matches container image tags to protect. For example, `v1.*`. Wildcard character `*` allowed. Introduced in GitLab 17.8: **Status**: Experiment. |
|
||||
|
||||
|
|
@ -19255,6 +19255,8 @@ Extra metadata for AI message.
|
|||
|
||||
### `AiMetrics`
|
||||
|
||||
Requires ClickHouse. Premium and Ultimate with GitLab Duo Pro and Enterprise only.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
|
|
@ -22007,6 +22009,17 @@ A tag expiration policy designed to keep only the images that matter most.
|
|||
| <a id="containerexpirationpolicyolderthan"></a>`olderThan` | [`ContainerExpirationPolicyOlderThanEnum`](#containerexpirationpolicyolderthanenum) | Tags older than the given age will expire. |
|
||||
| <a id="containerexpirationpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the container expiration policy was updated. |
|
||||
|
||||
### `ContainerProtectionAccessLevel`
|
||||
|
||||
Represents the most restrictive permissions for a container image tag.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="containerprotectionaccesslevelminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can delete tags by default. |
|
||||
| <a id="containerprotectionaccesslevelminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can push tags by default. |
|
||||
|
||||
### `ContainerProtectionRepositoryRule`
|
||||
|
||||
A container repository protection rule designed to prevent users with a certain access level or lower from altering the container registry.
|
||||
|
|
@ -22029,8 +22042,8 @@ A container repository tag protection rule designed to prevent users with a cert
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="containerprotectiontagruleid"></a>`id` **{warning-solid}** | [`ContainerRegistryProtectionTagRuleID!`](#containerregistryprotectiontagruleid) | **Introduced** in GitLab 17.8. **Status**: Experiment. ID of the container repository tag protection rule. |
|
||||
| <a id="containerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the default minimum access level is `DEVELOPER`. |
|
||||
| <a id="containerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the default minimum access level is `DEVELOPER`. |
|
||||
| <a id="containerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can delete tags by default. |
|
||||
| <a id="containerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can push tags by default. |
|
||||
| <a id="containerprotectiontagruletagnamepattern"></a>`tagNamePattern` **{warning-solid}** | [`String!`](#string) | **Introduced** in GitLab 17.8. **Status**: Experiment. The pattern that matches container image tags to protect. For example, `v1.*`. Wildcard character `*` allowed. |
|
||||
|
||||
### `ContainerRepository`
|
||||
|
|
@ -22174,6 +22187,7 @@ A tag from a container repository.
|
|||
| <a id="containerrepositorytagmediatype"></a>`mediaType` | [`String`](#string) | Media type of the tag. |
|
||||
| <a id="containerrepositorytagname"></a>`name` | [`String!`](#string) | Name of the tag. |
|
||||
| <a id="containerrepositorytagpath"></a>`path` | [`String!`](#string) | Path of the tag. |
|
||||
| <a id="containerrepositorytagprotection"></a>`protection` **{warning-solid}** | [`ContainerProtectionAccessLevel`](#containerprotectionaccesslevel) | **Introduced** in GitLab 17.9. **Status**: Experiment. Minimum GitLab access level required to push and delete container image tags. If multiple protection rules match an image tag, the highest access levels are applied. |
|
||||
| <a id="containerrepositorytagpublishedat"></a>`publishedAt` | [`Time`](#time) | Timestamp when the tag was published. |
|
||||
| <a id="containerrepositorytagreferrers"></a>`referrers` | [`[ContainerRepositoryReferrer!]`](#containerrepositoryreferrer) | Referrers for the tag. |
|
||||
| <a id="containerrepositorytagrevision"></a>`revision` | [`String`](#string) | Revision of the tag. |
|
||||
|
|
@ -43725,6 +43739,20 @@ One of:
|
|||
|
||||
### Interfaces
|
||||
|
||||
#### `AccessLevelInterface`
|
||||
|
||||
Implementations:
|
||||
|
||||
- [`ContainerProtectionAccessLevel`](#containerprotectionaccesslevel)
|
||||
- [`ContainerProtectionTagRule`](#containerprotectiontagrule)
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="accesslevelinterfaceminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can delete tags by default. |
|
||||
| <a id="accesslevelinterfaceminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` **{warning-solid}** | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no minimum access level is enforced. Users with the Developer role or higher can push tags by default. |
|
||||
|
||||
#### `AlertManagementIntegration`
|
||||
|
||||
Implementations:
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ module Gitlab
|
|||
|
||||
def initialize(project, options = nil)
|
||||
@project = project
|
||||
@project_namespace, _, @project_path = project.full_path.downcase.partition('/')
|
||||
namespace, _, @project_path = project.full_path.partition('/')
|
||||
@project_namespace = namespace.downcase
|
||||
@options = options || {}
|
||||
end
|
||||
|
||||
def pages_url
|
||||
default_url
|
||||
.to_s
|
||||
.downcase
|
||||
end
|
||||
|
||||
def unique_host
|
||||
|
|
@ -34,7 +34,7 @@ module Gitlab
|
|||
# See https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html#user-and-group-website-examples.
|
||||
def is_namespace_homepage? # rubocop:disable Naming/PredicateName -- namespace_homepage is not an
|
||||
# adjective, so adding "is_" improves understandability
|
||||
project_path == "#{project_namespace}.#{instance_pages_domain}"
|
||||
project_path.downcase == "#{project_namespace}.#{instance_pages_domain}"
|
||||
end
|
||||
|
||||
def artifact_url(artifact, job)
|
||||
|
|
|
|||
|
|
@ -3099,6 +3099,9 @@ msgstr ""
|
|||
msgid "Action not allowed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Action required: %{project_name} is read-only"
|
||||
msgstr ""
|
||||
|
||||
msgid "Action to take when receiving an alert. %{docsLink}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -24742,6 +24745,12 @@ msgstr ""
|
|||
msgid "For investigating IT service disruptions or outages"
|
||||
msgstr ""
|
||||
|
||||
msgid "For more information %{support_link_start}contact support%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "For more information contact support: %{support_link}."
|
||||
msgstr ""
|
||||
|
||||
msgid "For more information on how the number of active users is calculated, see the %{self_managed_subscriptions_doc_link} documentation."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -59083,6 +59092,9 @@ msgstr ""
|
|||
msgid "To remove the %{link_start}read-only%{link_end} state and regain write access, you can reduce the number of users in your top-level group to %{free_limit} users or less. You can also upgrade to a paid tier, which do not have user limits. If you need additional time, you can start a free 60-day trial which includes unlimited users."
|
||||
msgstr ""
|
||||
|
||||
msgid "To remove the read-only state, reduce git repository and git LFS storage."
|
||||
msgstr ""
|
||||
|
||||
msgid "To resolve the conflicts, either use interactive mode to select %{use_ours} or %{use_theirs}, or edit the files inline. Commit these changes into %{branch_name}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -65852,6 +65864,12 @@ msgstr ""
|
|||
msgid "You have been unsubscribed from this thread."
|
||||
msgstr ""
|
||||
|
||||
msgid "You have consumed all available storage and you can't push or add large files to %{project_name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "You have consumed all available storage and you can't push or add large files to %{strong_start}%{project_name}%{strong_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "You have declined the invitation to join %{title} %{name}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
"@gitlab/ui": "107.0.1",
|
||||
"@gitlab/vue-router-vue3": "npm:vue-router@4.1.6",
|
||||
"@gitlab/vuex-vue3": "npm:vuex@4.0.0",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20250109231656",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20250110172049",
|
||||
"@gleam-lang/highlight.js-gleam": "^1.5.0",
|
||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
"@rails/actioncable": "7.0.807",
|
||||
|
|
|
|||
|
|
@ -252,14 +252,27 @@ module QA
|
|||
within_vscode_editor do
|
||||
# VSCode eagerly removes the input[type='file'] from click on Upload.
|
||||
# We need to execute a script on the iframe to stub out the iframes body.removeChild to add it back in.
|
||||
page.execute_script("document.body.removeChild = function(){};")
|
||||
page.execute_script(
|
||||
<<~JAVASCRIPT
|
||||
window.__gl_old_remove = HTMLInputElement.prototype.remove;
|
||||
HTMLInputElement.prototype.remove = function(){};
|
||||
JAVASCRIPT
|
||||
)
|
||||
|
||||
# under some conditions the page may not be fully loaded and the right click
|
||||
# context menu can get closed prior to hitting 'upload' leading to failures
|
||||
Support::Retrier.retry_until(retry_on_exception: true, message: "Uploading a file in vscode") do
|
||||
right_click_file_explorer
|
||||
click_upload_menu_item
|
||||
enter_file_input(file_path)
|
||||
begin
|
||||
# under some conditions the page may not be fully loaded and the right click
|
||||
# context menu can get closed prior to hitting 'upload' leading to failures
|
||||
Support::Retrier.retry_until(retry_on_exception: true, message: "Uploading a file in vscode") do
|
||||
right_click_file_explorer
|
||||
click_upload_menu_item
|
||||
enter_file_input(file_path)
|
||||
end
|
||||
ensure
|
||||
page.execute_script(
|
||||
<<~JAVASCRIPT
|
||||
HTMLInputElement.prototype.remove = window.__gl_old_remove;
|
||||
JAVASCRIPT
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -339,6 +352,12 @@ module QA
|
|||
end
|
||||
|
||||
def right_click_file_explorer
|
||||
# NOTE: Web IDE prompts for clipboard permission to open the file explorer context menu
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/177778#note_2295036716
|
||||
# https://gitlab.com/gitlab-org/gitlab-web-ide/-/issues/433
|
||||
page.driver.browser.add_permission("clipboard-read", "granted")
|
||||
page.driver.browser.add_permission("clipboard-write", "granted")
|
||||
|
||||
page.find('.explorer-folders-view', visible: true).right_click
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe GitlabSchema.types['ContainerRepositoryTag'], feature_category: :container_registry do
|
||||
fields = %i[name path location digest revision short_revision
|
||||
total_size created_at user_permissions referrers published_at media_type]
|
||||
total_size created_at user_permissions referrers published_at media_type protection]
|
||||
|
||||
it { expect(described_class.graphql_name).to eq('ContainerRepositoryTag') }
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,12 @@ RSpec.describe Gitlab::Pages::UrlBuilder, feature_category: :pages do
|
|||
it { is_expected.to eq('http://unique-domain.example.com') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project path contains capitals' do
|
||||
let(:full_path) { 'group/MyProject' }
|
||||
|
||||
it { is_expected.to eq('http://group.example.com/MyProject') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace_in_path is true' do
|
||||
|
|
|
|||
|
|
@ -3,29 +3,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::CustomIssueTracker, feature_category: :integrations do
|
||||
describe 'Validations' do
|
||||
context 'when integration is active' do
|
||||
before do
|
||||
subject.active = true
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:project_url) }
|
||||
it { is_expected.to validate_presence_of(:issues_url) }
|
||||
it { is_expected.to validate_presence_of(:new_issue_url) }
|
||||
|
||||
it_behaves_like 'issue tracker integration URL attribute', :project_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :issues_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :new_issue_url
|
||||
end
|
||||
|
||||
context 'when integration is inactive' do
|
||||
before do
|
||||
subject.active = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:project_url) }
|
||||
it { is_expected.not_to validate_presence_of(:issues_url) }
|
||||
it { is_expected.not_to validate_presence_of(:new_issue_url) }
|
||||
end
|
||||
end
|
||||
it_behaves_like Integrations::Base::CustomIssueTracker
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,72 +3,5 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::DiffblueCover, feature_category: :integrations do
|
||||
let_it_be(:project) { build(:project) }
|
||||
|
||||
subject(:integration) { build(:diffblue_cover_integration, project: project) }
|
||||
|
||||
describe 'Validations' do
|
||||
context 'when active' do
|
||||
before do
|
||||
integration.active = true
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:diffblue_license_key) }
|
||||
it { is_expected.to validate_presence_of(:diffblue_access_token_name) }
|
||||
it { is_expected.to validate_presence_of(:diffblue_access_token_secret) }
|
||||
end
|
||||
|
||||
context 'when inactive' do
|
||||
before do
|
||||
integration.active = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:diffblue_license_key) }
|
||||
it { is_expected.not_to validate_presence_of(:diffblue_access_token_name) }
|
||||
it { is_expected.not_to validate_presence_of(:diffblue_access_token_secret) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#avatar_url' do
|
||||
it 'returns the avatar image path' do
|
||||
expect(integration.avatar_url).to eq(ActionController::Base.helpers.image_path(
|
||||
'illustrations/third-party-logos/integrations-logos/diffblue.svg'
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ci-vars' do
|
||||
let(:ci_vars) do
|
||||
[
|
||||
{ key: 'DIFFBLUE_LICENSE_KEY', value: '1234-ABCD-DCBA-4321', public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN_NAME', value: 'Diffblue CI', public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN',
|
||||
value: 'glpat-00112233445566778899', public: false, masked: true } # gitleaks:allow
|
||||
]
|
||||
end
|
||||
|
||||
context 'when active' do
|
||||
before do
|
||||
integration.active = true
|
||||
end
|
||||
|
||||
it 'returns the required pipeline vars' do
|
||||
expect(integration.ci_variables).to match_array(ci_vars)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inactive' do
|
||||
before do
|
||||
integration.active = false
|
||||
end
|
||||
|
||||
it 'does not return the required pipeline vars' do
|
||||
expect(integration.ci_variables).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diffblue_link' do
|
||||
it { expect(described_class.diffblue_link).to include("https://www.diffblue.com/try-cover/gitlab/") }
|
||||
end
|
||||
it_behaves_like Integrations::Base::DiffblueCover
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,56 +2,6 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::Ewm do
|
||||
describe 'Validations' do
|
||||
context 'when integration is active' do
|
||||
before do
|
||||
subject.active = true
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:project_url) }
|
||||
it { is_expected.to validate_presence_of(:issues_url) }
|
||||
it { is_expected.to validate_presence_of(:new_issue_url) }
|
||||
|
||||
it_behaves_like 'issue tracker integration URL attribute', :project_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :issues_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :new_issue_url
|
||||
end
|
||||
|
||||
context 'when integration is inactive' do
|
||||
before do
|
||||
subject.active = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:project_url) }
|
||||
it { is_expected.not_to validate_presence_of(:issues_url) }
|
||||
it { is_expected.not_to validate_presence_of(:new_issue_url) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "ReferencePatternValidation" do
|
||||
it "extracts bug" do
|
||||
expect(subject.reference_pattern.match("This is bug 123")[:issue]).to eq("bug 123")
|
||||
end
|
||||
|
||||
it "extracts task" do
|
||||
expect(subject.reference_pattern.match("This is task 123.")[:issue]).to eq("task 123")
|
||||
end
|
||||
|
||||
it "extracts work item" do
|
||||
expect(subject.reference_pattern.match("This is work item 123 now")[:issue]).to eq("work item 123")
|
||||
end
|
||||
|
||||
it "extracts workitem" do
|
||||
expect(subject.reference_pattern.match("workitem 123 at the beginning")[:issue]).to eq("workitem 123")
|
||||
end
|
||||
|
||||
it "extracts defect" do
|
||||
expect(subject.reference_pattern.match("This is defect 123 defect")[:issue]).to eq("defect 123")
|
||||
end
|
||||
|
||||
it "extracts rtcwi" do
|
||||
expect(subject.reference_pattern.match("This is rtcwi 123")[:issue]).to eq("rtcwi 123")
|
||||
end
|
||||
end
|
||||
RSpec.describe Integrations::Ewm, feature_category: :integrations do
|
||||
it_behaves_like Integrations::Base::Ewm
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::Instance::CustomIssueTracker, feature_category: :integrations do
|
||||
it_behaves_like Integrations::Base::CustomIssueTracker
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::Instance::DiffblueCover, feature_category: :integrations do
|
||||
it_behaves_like Integrations::Base::DiffblueCover
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::Instance::Ewm, feature_category: :integrations do
|
||||
it_behaves_like Integrations::Base::Ewm
|
||||
end
|
||||
|
|
@ -131,8 +131,6 @@ RSpec.describe 'container repository details', feature_category: :container_regi
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning proper responses with different permissions', raw_tags: -> { tags }
|
||||
|
||||
context 'with a giant size tag' do
|
||||
let(:tags) { %w[latest] }
|
||||
let(:giant_size) { 1.terabyte }
|
||||
|
|
@ -217,6 +215,10 @@ RSpec.describe 'container repository details', feature_category: :container_regi
|
|||
GQL
|
||||
end
|
||||
|
||||
before do
|
||||
stub_container_registry_gitlab_api_support(supported: false)
|
||||
end
|
||||
|
||||
it 'sorts the tags', :aggregate_failures do
|
||||
subject
|
||||
|
||||
|
|
@ -385,7 +387,15 @@ RSpec.describe 'container repository details', feature_category: :container_regi
|
|||
|
||||
it_behaves_like 'handling graphql network errors with the container registry'
|
||||
|
||||
context 'when list tags API is enabled' do
|
||||
context 'when the Gitlab API is not supported' do
|
||||
before do
|
||||
stub_container_registry_gitlab_api_support(supported: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning proper responses with different permissions', raw_tags: -> { tags }
|
||||
end
|
||||
|
||||
context 'when the Gitlab API is supported' do
|
||||
before do
|
||||
stub_container_registry_config(enabled: true)
|
||||
allow_next_instances_of(ContainerRegistry::GitlabApiClient, nil) do |client|
|
||||
|
|
@ -648,4 +658,79 @@ RSpec.describe 'container repository details', feature_category: :container_regi
|
|||
expect(migration_state_response).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
context 'protection field' do
|
||||
let(:raw_tags_response) { [{ name: 'latest', digest: 'sha256:123' }] }
|
||||
let(:response_body) { { response_body: ::Gitlab::Json.parse(raw_tags_response.to_json) } }
|
||||
|
||||
let(:query) do
|
||||
<<~GQL
|
||||
query($id: ContainerRepositoryID!) {
|
||||
containerRepository(id: $id) {
|
||||
tags(first: 5) {
|
||||
nodes {
|
||||
protection {
|
||||
minimumAccessLevelForPush
|
||||
minimumAccessLevelForDelete
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GQL
|
||||
end
|
||||
|
||||
let(:tag_permissions_response) do
|
||||
container_repository_details_response.dig('tags', 'nodes')[0]['protection']
|
||||
end
|
||||
|
||||
before_all do
|
||||
create(
|
||||
:container_registry_protection_tag_rule,
|
||||
project: project,
|
||||
tag_name_pattern: 'latest',
|
||||
minimum_access_level_for_push: 'maintainer',
|
||||
minimum_access_level_for_delete: 'owner'
|
||||
)
|
||||
|
||||
create(
|
||||
:container_registry_protection_tag_rule,
|
||||
project: project,
|
||||
tag_name_pattern: '.*',
|
||||
minimum_access_level_for_push: 'owner',
|
||||
minimum_access_level_for_delete: 'maintainer'
|
||||
)
|
||||
|
||||
create(
|
||||
:container_registry_protection_tag_rule,
|
||||
project: project,
|
||||
tag_name_pattern: 'non-matching-pattern',
|
||||
minimum_access_level_for_push: 'admin',
|
||||
minimum_access_level_for_delete: 'admin'
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the maximum access fields from the matching protection rules' do
|
||||
subject
|
||||
|
||||
expect(tag_permissions_response).to eq(
|
||||
{
|
||||
'minimumAccessLevelForPush' => 'OWNER',
|
||||
'minimumAccessLevelForDelete' => 'OWNER'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
context 'when the feature container_registry_protected_tags is disabled' do
|
||||
before do
|
||||
stub_feature_flags(container_registry_protected_tags: false)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
subject
|
||||
|
||||
expect(tag_permissions_response).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,9 +39,7 @@ RSpec.describe 'Creating a todo for the alert', feature_category: :incident_mana
|
|||
|
||||
context 'todo already exists' do
|
||||
before do
|
||||
stub_feature_flags(multiple_todos: false)
|
||||
|
||||
create(:todo, :pending, project: project, user: user, target: alert)
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
end
|
||||
|
||||
it 'surfaces an error' do
|
||||
|
|
|
|||
|
|
@ -388,9 +388,14 @@ RSpec.describe API::Todos, feature_category: :source_code_management do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'an issuable' do |issuable_type|
|
||||
shared_examples 'an issuable' do |param|
|
||||
let(:issuable_type) { param }
|
||||
def create_todo_for_issuable(user, iid = issuable.iid)
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{iid}/todo", user)
|
||||
end
|
||||
|
||||
it 'creates a todo on an issuable' do
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
|
||||
create_todo_for_issuable(john_doe)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['project']).to be_a Hash
|
||||
|
|
@ -406,11 +411,9 @@ RSpec.describe API::Todos, feature_category: :source_code_management do
|
|||
end
|
||||
|
||||
it 'returns 304 there already exist a todo on that issuable' do
|
||||
stub_feature_flags(multiple_todos: false)
|
||||
create_todo_for_issuable(john_doe)
|
||||
|
||||
create(:todo, project: project_1, author: author_1, user: john_doe, target: issuable)
|
||||
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
|
||||
create_todo_for_issuable(john_doe)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_modified)
|
||||
end
|
||||
|
|
@ -418,7 +421,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do
|
|||
it 'returns 404 if the issuable is not found' do
|
||||
unknown_id = 0
|
||||
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{unknown_id}/todo", john_doe)
|
||||
create_todo_for_issuable(john_doe, unknown_id)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
|
@ -427,7 +430,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do
|
|||
guest = create(:user)
|
||||
project_1.add_guest(guest)
|
||||
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", guest)
|
||||
create_todo_for_issuable(guest)
|
||||
|
||||
if issuable_type == 'merge_requests'
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
|
|
|
|||
|
|
@ -58,10 +58,6 @@ RSpec.describe AlertManagement::Alerts::Todo::CreateService, feature_category: :
|
|||
create(:todo, :pending, **todo_params)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(multiple_todos: false)
|
||||
end
|
||||
|
||||
it 'does not create a todo' do
|
||||
expect { result }.not_to change { Todo.count }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples Integrations::Base::CustomIssueTracker do
|
||||
describe 'Validations' do
|
||||
context 'when integration is active' do
|
||||
before do
|
||||
subject.active = true
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:project_url) }
|
||||
it { is_expected.to validate_presence_of(:issues_url) }
|
||||
it { is_expected.to validate_presence_of(:new_issue_url) }
|
||||
|
||||
it_behaves_like 'issue tracker integration URL attribute', :project_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :issues_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :new_issue_url
|
||||
end
|
||||
|
||||
context 'when integration is inactive' do
|
||||
before do
|
||||
subject.active = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:project_url) }
|
||||
it { is_expected.not_to validate_presence_of(:issues_url) }
|
||||
it { is_expected.not_to validate_presence_of(:new_issue_url) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples Integrations::Base::DiffblueCover do
|
||||
let_it_be(:project) { build(:project) }
|
||||
|
||||
subject(:integration) { build(:diffblue_cover_integration, project: project) }
|
||||
|
||||
describe 'Validations' do
|
||||
context 'when active' do
|
||||
before do
|
||||
integration.active = true
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:diffblue_license_key) }
|
||||
it { is_expected.to validate_presence_of(:diffblue_access_token_name) }
|
||||
it { is_expected.to validate_presence_of(:diffblue_access_token_secret) }
|
||||
end
|
||||
|
||||
context 'when inactive' do
|
||||
before do
|
||||
integration.active = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:diffblue_license_key) }
|
||||
it { is_expected.not_to validate_presence_of(:diffblue_access_token_name) }
|
||||
it { is_expected.not_to validate_presence_of(:diffblue_access_token_secret) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#avatar_url' do
|
||||
it 'returns the avatar image path' do
|
||||
expect(integration.avatar_url).to eq(ActionController::Base.helpers.image_path(
|
||||
'illustrations/third-party-logos/integrations-logos/diffblue.svg'
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ci-vars' do
|
||||
let(:ci_vars) do
|
||||
[
|
||||
{ key: 'DIFFBLUE_LICENSE_KEY', value: '1234-ABCD-DCBA-4321', public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN_NAME', value: 'Diffblue CI', public: false, masked: true },
|
||||
{ key: 'DIFFBLUE_ACCESS_TOKEN',
|
||||
value: 'glpat-00112233445566778899', public: false, masked: true } # gitleaks:allow
|
||||
]
|
||||
end
|
||||
|
||||
context 'when active' do
|
||||
before do
|
||||
integration.active = true
|
||||
end
|
||||
|
||||
it 'returns the required pipeline vars' do
|
||||
expect(integration.ci_variables).to match_array(ci_vars)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inactive' do
|
||||
before do
|
||||
integration.active = false
|
||||
end
|
||||
|
||||
it 'does not return the required pipeline vars' do
|
||||
expect(integration.ci_variables).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diffblue_link' do
|
||||
it { expect(described_class.diffblue_link).to include("https://www.diffblue.com/try-cover/gitlab/") }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples Integrations::Base::Ewm do
|
||||
describe 'Validations' do
|
||||
context 'when integration is active' do
|
||||
before do
|
||||
subject.active = true
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:project_url) }
|
||||
it { is_expected.to validate_presence_of(:issues_url) }
|
||||
it { is_expected.to validate_presence_of(:new_issue_url) }
|
||||
|
||||
it_behaves_like 'issue tracker integration URL attribute', :project_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :issues_url
|
||||
it_behaves_like 'issue tracker integration URL attribute', :new_issue_url
|
||||
end
|
||||
|
||||
context 'when integration is inactive' do
|
||||
before do
|
||||
subject.active = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:project_url) }
|
||||
it { is_expected.not_to validate_presence_of(:issues_url) }
|
||||
it { is_expected.not_to validate_presence_of(:new_issue_url) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "ReferencePatternValidation" do
|
||||
it "extracts bug" do
|
||||
expect(subject.reference_pattern.match("This is bug 123")[:issue]).to eq("bug 123")
|
||||
end
|
||||
|
||||
it "extracts task" do
|
||||
expect(subject.reference_pattern.match("This is task 123.")[:issue]).to eq("task 123")
|
||||
end
|
||||
|
||||
it "extracts work item" do
|
||||
expect(subject.reference_pattern.match("This is work item 123 now")[:issue]).to eq("work item 123")
|
||||
end
|
||||
|
||||
it "extracts work item" do
|
||||
expect(subject.reference_pattern.match("workitem 123 at the beginning")[:issue]).to eq("workitem 123")
|
||||
end
|
||||
|
||||
it "extracts defect" do
|
||||
expect(subject.reference_pattern.match("This is defect 123 defect")[:issue]).to eq("defect 123")
|
||||
end
|
||||
|
||||
it "extracts rtcwi" do
|
||||
expect(subject.reference_pattern.match("This is rtcwi 123")[:issue]).to eq("rtcwi 123")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1463,10 +1463,10 @@
|
|||
resolved "https://registry.yarnpkg.com/vuex/-/vuex-4.0.0.tgz#ac877aa76a9c45368c979471e461b520d38e6cf5"
|
||||
integrity sha512-56VPujlHscP5q/e7Jlpqc40sja4vOhC4uJD1llBCWolVI8ND4+VzisDVkUMl+z5y0MpIImW6HjhNc+ZvuizgOw==
|
||||
|
||||
"@gitlab/web-ide@^0.0.1-dev-20250109231656":
|
||||
version "0.0.1-dev-20250109231656"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20250109231656.tgz#dd8afb853ae04dae09e53b37710869975092bea7"
|
||||
integrity sha512-AO6uo8fKkmlavWfqHgYNgzMz6U+ppl1uK23uiWEy3aU16xL1/MIGda4LVxzmWLVnxz3BGg0xdqA/Ipsd4VcxVw==
|
||||
"@gitlab/web-ide@^0.0.1-dev-20250110172049":
|
||||
version "0.0.1-dev-20250110172049"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20250110172049.tgz#c10d85535f272039613866082d9aa6a9068f63b1"
|
||||
integrity sha512-3duzCxQGRqSoPnOMG8dtepIxHlE2KvysSVrOGDMijajDpi6N68loFaUSM2E/shy+NtSPKQgltYiG1JXPxZlWaw==
|
||||
|
||||
"@gleam-lang/highlight.js-gleam@^1.5.0":
|
||||
version "1.5.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue