Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
191fe0b178
commit
f01ede3183
|
|
@ -141,7 +141,6 @@ Gitlab/BoundedContexts:
|
|||
- 'app/graphql/mutations/alert_management/http_integration/reset_token.rb'
|
||||
- 'app/graphql/mutations/alert_management/http_integration/update.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/create.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/update.rb'
|
||||
- 'app/graphql/mutations/alert_management/update_alert_status.rb'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ GraphQL/GraphqlName:
|
|||
Exclude:
|
||||
- 'app/graphql/mutations/alert_management/base.rb'
|
||||
- 'app/graphql/mutations/alert_management/http_integration/http_integration_base.rb'
|
||||
- 'app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb'
|
||||
- 'app/graphql/mutations/award_emojis/base.rb'
|
||||
- 'app/graphql/mutations/base_mutation.rb'
|
||||
- 'app/graphql/mutations/boards/lists/base_create.rb'
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -565,7 +565,7 @@ group :development, :test, :coverage do
|
|||
gem 'simplecov', '~> 0.22', require: false, feature_category: :tooling
|
||||
gem 'simplecov-lcov', '~> 0.8.0', require: false, feature_category: :tooling
|
||||
gem 'simplecov-cobertura', '~> 2.1.0', require: false, feature_category: :tooling
|
||||
gem 'undercover', '~> 0.6.0', require: false, feature_category: :tooling
|
||||
gem 'undercover', '~> 0.7.0', require: false, feature_category: :tooling
|
||||
end
|
||||
|
||||
# Gems required in omnibus-gitlab pipeline
|
||||
|
|
|
|||
|
|
@ -773,7 +773,7 @@
|
|||
{"name":"typhoeus","version":"1.4.1","platform":"ruby","checksum":"1c17db8364bd45ab302dc61e460173c3e69835896be88a3df07c206d5c55ef7c"},
|
||||
{"name":"tzinfo","version":"2.0.6","platform":"ruby","checksum":"8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b"},
|
||||
{"name":"uber","version":"0.1.0","platform":"ruby","checksum":"5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc"},
|
||||
{"name":"undercover","version":"0.6.4","platform":"ruby","checksum":"3c34fcf129b52a4993065c52612a65e5e05e77f0cac3f4f8f388114fb129ec1a"},
|
||||
{"name":"undercover","version":"0.7.0","platform":"ruby","checksum":"1aa906581bd4d91b093a313496b8ef522e41a50b6c6689e228fd2587d3cca74d"},
|
||||
{"name":"unf","version":"0.1.4","platform":"java","checksum":"49a5972ec0b3d091d3b0b2e00113f2f342b9b212f0db855eb30a629637f6d302"},
|
||||
{"name":"unf","version":"0.1.4","platform":"ruby","checksum":"4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e"},
|
||||
{"name":"unf_ext","version":"0.0.8.2","platform":"ruby","checksum":"90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa"},
|
||||
|
|
|
|||
|
|
@ -1968,12 +1968,14 @@ GEM
|
|||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
undercover (0.6.4)
|
||||
undercover (0.7.0)
|
||||
base64
|
||||
bigdecimal
|
||||
imagen (>= 0.2.0)
|
||||
rainbow (>= 2.1, < 4.0)
|
||||
rugged (>= 0.27, < 1.10)
|
||||
simplecov
|
||||
simplecov_json_formatter
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
|
|
@ -2412,7 +2414,7 @@ DEPENDENCIES
|
|||
truncato (~> 0.7.13)
|
||||
tty-prompt (~> 0.23)
|
||||
typhoeus (~> 1.4.0)
|
||||
undercover (~> 0.6.0)
|
||||
undercover (~> 0.7.0)
|
||||
unicode-emoji (~> 4.0)
|
||||
unleash (~> 3.2.2)
|
||||
uri (= 0.13.2)
|
||||
|
|
|
|||
|
|
@ -773,7 +773,7 @@
|
|||
{"name":"typhoeus","version":"1.4.1","platform":"ruby","checksum":"1c17db8364bd45ab302dc61e460173c3e69835896be88a3df07c206d5c55ef7c"},
|
||||
{"name":"tzinfo","version":"2.0.6","platform":"ruby","checksum":"8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b"},
|
||||
{"name":"uber","version":"0.1.0","platform":"ruby","checksum":"5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc"},
|
||||
{"name":"undercover","version":"0.6.4","platform":"ruby","checksum":"3c34fcf129b52a4993065c52612a65e5e05e77f0cac3f4f8f388114fb129ec1a"},
|
||||
{"name":"undercover","version":"0.7.0","platform":"ruby","checksum":"1aa906581bd4d91b093a313496b8ef522e41a50b6c6689e228fd2587d3cca74d"},
|
||||
{"name":"unf","version":"0.1.4","platform":"java","checksum":"49a5972ec0b3d091d3b0b2e00113f2f342b9b212f0db855eb30a629637f6d302"},
|
||||
{"name":"unf","version":"0.1.4","platform":"ruby","checksum":"4999517a531f2a955750f8831941891f6158498ec9b6cb1c81ce89388e63022e"},
|
||||
{"name":"unf_ext","version":"0.0.8.2","platform":"ruby","checksum":"90b9623ee359cc4878461c5d2eab7d3d3ce5801a680a9e7ac83b8040c5b742fa"},
|
||||
|
|
|
|||
|
|
@ -1962,12 +1962,14 @@ GEM
|
|||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
undercover (0.6.4)
|
||||
undercover (0.7.0)
|
||||
base64
|
||||
bigdecimal
|
||||
imagen (>= 0.2.0)
|
||||
rainbow (>= 2.1, < 4.0)
|
||||
rugged (>= 0.27, < 1.10)
|
||||
simplecov
|
||||
simplecov_json_formatter
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
|
|
@ -2407,7 +2409,7 @@ DEPENDENCIES
|
|||
truncato (~> 0.7.13)
|
||||
tty-prompt (~> 0.23)
|
||||
typhoeus (~> 1.4.0)
|
||||
undercover (~> 0.6.0)
|
||||
undercover (~> 0.7.0)
|
||||
unicode-emoji (~> 4.0)
|
||||
unleash (~> 3.2.2)
|
||||
uri (= 0.13.2)
|
||||
|
|
|
|||
|
|
@ -1,44 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Deprecated:
|
||||
# Remove from MutationType during any major release.
|
||||
module Mutations
|
||||
module AlertManagement
|
||||
module PrometheusIntegration
|
||||
class Create < PrometheusIntegrationBase
|
||||
class Create < HttpIntegration::Create
|
||||
graphql_name 'PrometheusIntegrationCreate'
|
||||
|
||||
include FindsProject
|
||||
field :integration,
|
||||
Types::AlertManagement::PrometheusIntegrationType,
|
||||
null: true,
|
||||
description: "Newly created integration."
|
||||
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'Project to create the integration in.'
|
||||
|
||||
argument :active, GraphQL::Types::Boolean,
|
||||
required: true,
|
||||
description: 'Whether the integration is receiving alerts.'
|
||||
argument :name, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Name of the integration.',
|
||||
default_value: 'Prometheus'
|
||||
|
||||
argument :api_url, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Endpoint at which Prometheus can be queried.'
|
||||
description: 'Endpoint at which Prometheus can be queried.',
|
||||
deprecated: { reason: 'Feature removed in 16.0', milestone: '18.2' }
|
||||
|
||||
def resolve(args)
|
||||
project = authorized_find!(args[:project_path])
|
||||
|
||||
return integration_exists if project.prometheus_integration
|
||||
|
||||
result = ::Projects::Operations::UpdateService.new(
|
||||
project,
|
||||
current_user,
|
||||
**integration_attributes(args),
|
||||
**token_attributes
|
||||
).execute
|
||||
|
||||
response(project.prometheus_integration, result)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def integration_exists
|
||||
response(nil, message: _('Multiple Prometheus integrations are not supported'))
|
||||
super(args.merge(name: 'Prometheus', type_identifier: :prometheus))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module AlertManagement
|
||||
module PrometheusIntegration
|
||||
class PrometheusIntegrationBase < BaseMutation
|
||||
field :integration,
|
||||
Types::AlertManagement::PrometheusIntegrationType,
|
||||
null: true,
|
||||
description: "Newly created integration."
|
||||
|
||||
authorize :admin_project
|
||||
|
||||
private
|
||||
|
||||
def response(integration, result)
|
||||
{
|
||||
integration: integration,
|
||||
errors: Array(result[:message])
|
||||
}
|
||||
end
|
||||
|
||||
def integration_attributes(args)
|
||||
{
|
||||
prometheus_integration_attributes: {
|
||||
manual_configuration: args[:active],
|
||||
api_url: args[:api_url]
|
||||
}.compact
|
||||
}
|
||||
end
|
||||
|
||||
def token_attributes
|
||||
{ alerting_setting_attributes: { regenerate_token: true } }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,25 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Deprecated:
|
||||
# Remove from MutationType during any major release.
|
||||
module Mutations
|
||||
module AlertManagement
|
||||
module PrometheusIntegration
|
||||
class ResetToken < PrometheusIntegrationBase
|
||||
class ResetToken < HttpIntegration::ResetToken
|
||||
graphql_name 'PrometheusIntegrationResetToken'
|
||||
|
||||
field :integration,
|
||||
Types::AlertManagement::PrometheusIntegrationType,
|
||||
null: true,
|
||||
description: "Updated integration."
|
||||
|
||||
argument :id, Types::GlobalIDType[::Integrations::Prometheus],
|
||||
required: true,
|
||||
description: "ID of the integration to mutate."
|
||||
|
||||
def resolve(id:)
|
||||
integration = authorized_find!(id: id)
|
||||
def authorized_find!(**)
|
||||
integration = super&.project
|
||||
&.alert_management_http_integrations
|
||||
&.for_endpoint_identifier('legacy-prometheus')
|
||||
&.take
|
||||
|
||||
result = ::Projects::Operations::UpdateService.new(
|
||||
integration.project,
|
||||
current_user,
|
||||
token_attributes
|
||||
).execute
|
||||
|
||||
response integration, result
|
||||
integration || raise_resource_not_available_error!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,33 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Deprecated:
|
||||
# Remove from MutationType during any major release.
|
||||
module Mutations
|
||||
module AlertManagement
|
||||
module PrometheusIntegration
|
||||
class Update < PrometheusIntegrationBase
|
||||
class Update < HttpIntegration::Update
|
||||
graphql_name 'PrometheusIntegrationUpdate'
|
||||
|
||||
field :integration,
|
||||
Types::AlertManagement::PrometheusIntegrationType,
|
||||
null: true,
|
||||
description: "Updated integration."
|
||||
|
||||
argument :id, Types::GlobalIDType[::Integrations::Prometheus],
|
||||
required: true,
|
||||
description: "ID of the integration to mutate."
|
||||
|
||||
argument :active, GraphQL::Types::Boolean,
|
||||
required: false,
|
||||
description: "Whether the integration is receiving alerts."
|
||||
|
||||
argument :api_url, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: "Endpoint at which Prometheus can be queried."
|
||||
description: "Endpoint at which Prometheus can be queried.",
|
||||
deprecated: { reason: 'Feature removed in 16.0', milestone: '18.2' }
|
||||
|
||||
def resolve(args)
|
||||
integration = authorized_find!(id: args[:id])
|
||||
super(args.except(:name))
|
||||
end
|
||||
|
||||
result = ::Projects::Operations::UpdateService.new(
|
||||
integration.project,
|
||||
current_user,
|
||||
integration_attributes(args)
|
||||
).execute
|
||||
def authorized_find!(**)
|
||||
integration = super&.project
|
||||
&.alert_management_http_integrations
|
||||
&.for_endpoint_identifier('legacy-prometheus')
|
||||
&.take
|
||||
|
||||
response integration.reset, result
|
||||
integration || raise_resource_not_available_error!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,48 +14,37 @@ module Resolvers
|
|||
type Types::AlertManagement::IntegrationType.connection_type, null: true
|
||||
|
||||
def resolve(id: nil)
|
||||
return [] unless Ability.allowed?(current_user, :admin_operations, project)
|
||||
|
||||
if id
|
||||
integrations_by(gid: id)
|
||||
else
|
||||
http_integrations + prometheus_integrations
|
||||
http_integrations
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def integrations_by(gid:)
|
||||
object = GitlabSchema.object_from_id(gid, expected_type: expected_integration_types)
|
||||
object = GitlabSchema.object_from_id(gid, expected_type: [
|
||||
::AlertManagement::HttpIntegration,
|
||||
::Integrations::Prometheus
|
||||
])
|
||||
|
||||
defer { object }.then do |integration|
|
||||
ret = integration if project == integration&.project
|
||||
Array.wrap(ret)
|
||||
next [] unless integration&.project == project
|
||||
|
||||
if integration.is_a?(::Integrations::Prometheus)
|
||||
project.alert_management_http_integrations
|
||||
.for_endpoint_identifier('legacy-prometheus').to_a
|
||||
else
|
||||
[integration]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def prometheus_integrations
|
||||
return [] unless prometheus_integrations_allowed?
|
||||
|
||||
Array(project.prometheus_integration)
|
||||
end
|
||||
|
||||
def http_integrations
|
||||
return [] unless http_integrations_allowed?
|
||||
|
||||
::AlertManagement::HttpIntegrationsFinder.new(project, { type_identifier: :http }).execute
|
||||
end
|
||||
|
||||
def prometheus_integrations_allowed?
|
||||
Ability.allowed?(current_user, :admin_project, project)
|
||||
end
|
||||
|
||||
def http_integrations_allowed?
|
||||
Ability.allowed?(current_user, :admin_operations, project)
|
||||
end
|
||||
|
||||
def expected_integration_types
|
||||
[].tap do |types|
|
||||
types << ::AlertManagement::HttpIntegration if http_integrations_allowed?
|
||||
types << ::Integrations::Prometheus if prometheus_integrations_allowed?
|
||||
end
|
||||
::AlertManagement::HttpIntegrationsFinder.new(project).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,14 +9,6 @@ module Types
|
|||
implements Types::AlertManagement::IntegrationType
|
||||
|
||||
authorize :admin_operations
|
||||
|
||||
def type
|
||||
object.type_identifier.to_sym
|
||||
end
|
||||
|
||||
def api_url
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,11 +39,20 @@ module Types
|
|||
field :api_url,
|
||||
GraphQL::Types::String,
|
||||
null: true,
|
||||
description: 'URL at which Prometheus metrics can be queried to populate the metrics dashboard.'
|
||||
description: 'URL at which Prometheus metrics can be queried to populate the metrics dashboard.',
|
||||
deprecated: { reason: 'Feature removed in 16.0', milestone: '18.2' }
|
||||
|
||||
def type
|
||||
object.type_identifier.to_sym
|
||||
end
|
||||
|
||||
def api_url
|
||||
nil
|
||||
end
|
||||
|
||||
definition_methods do
|
||||
def resolve_type(object, context)
|
||||
if object.is_a?(::Integrations::Prometheus)
|
||||
if object.type_identifier == 'prometheus'
|
||||
Types::AlertManagement::PrometheusIntegrationType
|
||||
else
|
||||
Types::AlertManagement::HttpIntegrationType
|
||||
|
|
|
|||
|
|
@ -1,38 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Deprecated:
|
||||
# Remove with PrometheusIntegration mutations during any major release.
|
||||
module Types
|
||||
module AlertManagement
|
||||
class PrometheusIntegrationType < ::Types::BaseObject
|
||||
graphql_name 'AlertManagementPrometheusIntegration'
|
||||
description 'An endpoint and credentials used to accept Prometheus alerts for a project'
|
||||
|
||||
include ::Gitlab::Routing
|
||||
description '**DEPRECATED - Use AlertManagementHttpIntegration directly** An endpoint and credentials used to accept Prometheus alerts for a project'
|
||||
|
||||
implements Types::AlertManagement::IntegrationType
|
||||
|
||||
authorize :admin_project
|
||||
|
||||
alias_method :prometheus_integration, :object
|
||||
|
||||
def name
|
||||
prometheus_integration.title
|
||||
end
|
||||
|
||||
def type
|
||||
:prometheus
|
||||
end
|
||||
|
||||
def token
|
||||
prometheus_integration.project&.alerting_setting&.token
|
||||
end
|
||||
|
||||
def url
|
||||
prometheus_integration.project && notify_project_prometheus_alerts_url(prometheus_integration.project, format: :json)
|
||||
end
|
||||
|
||||
def active
|
||||
prometheus_integration.manual_configuration?
|
||||
end
|
||||
authorize :admin_operations
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,9 +26,15 @@ module Types
|
|||
mount_mutation Mutations::Security::CiConfiguration::ConfigureSast
|
||||
mount_mutation Mutations::Security::CiConfiguration::ConfigureSastIac
|
||||
mount_mutation Mutations::Security::CiConfiguration::ConfigureSecretDetection
|
||||
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create
|
||||
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update
|
||||
mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken
|
||||
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create, deprecated: {
|
||||
reason: 'Use HttpIntegrationCreate', milestone: '18.2'
|
||||
}
|
||||
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update, deprecated: {
|
||||
reason: 'Use HttpIntegrationUpdate', milestone: '18.2'
|
||||
}
|
||||
mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken, deprecated: {
|
||||
reason: 'Use HttpIntegrationResetToken', milestone: '18.2'
|
||||
}
|
||||
mount_mutation Mutations::AwardEmojis::Add
|
||||
mount_mutation Mutations::AwardEmojis::Remove
|
||||
mount_mutation Mutations::AwardEmojis::Toggle
|
||||
|
|
|
|||
|
|
@ -537,7 +537,8 @@ module Types
|
|||
field :alert_management_integrations, Types::AlertManagement::IntegrationType.connection_type,
|
||||
null: true,
|
||||
description: 'Integrations which can receive alerts for the project.',
|
||||
resolver: Resolvers::AlertManagement::IntegrationsResolver
|
||||
resolver: Resolvers::AlertManagement::IntegrationsResolver,
|
||||
deprecated: { reason: 'Use `alertManagementHttpIntegrations`', milestone: '18.2' }
|
||||
|
||||
field :alert_management_http_integrations, Types::AlertManagement::HttpIntegrationType.connection_type,
|
||||
null: true,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ module TriggerableHooks
|
|||
job_hooks: :job_events,
|
||||
member_hooks: :member_events,
|
||||
merge_request_hooks: :merge_requests_events,
|
||||
milestone_hooks: :milestone_events,
|
||||
note_hooks: :note_events,
|
||||
pipeline_hooks: :pipeline_events,
|
||||
project_hooks: :project_events,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class ProjectHook < WebHook
|
|||
:issue_hooks,
|
||||
:job_hooks,
|
||||
:merge_request_hooks,
|
||||
:milestone_hooks,
|
||||
:note_hooks,
|
||||
:pipeline_hooks,
|
||||
:push_hooks,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@ class Milestone < ApplicationRecord
|
|||
state :active
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
Gitlab::HookData::MilestoneBuilder.new(self).build
|
||||
end
|
||||
|
||||
# Searches for timeboxes with a matching title.
|
||||
#
|
||||
# This method uses ILIKE on PostgreSQL
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ module Integrations
|
|||
release.to_hook_data('create')
|
||||
end
|
||||
|
||||
def milestone_events_data
|
||||
Gitlab::DataBuilder::Milestone.build_sample(project)
|
||||
end
|
||||
|
||||
def emoji_events_data
|
||||
no_data_error(s_('TestHooks|Ensure the project has notes.')) unless project.notes.any?
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module Integrations
|
|||
|
||||
private
|
||||
|
||||
def data
|
||||
def data # rubocop:disable Metrics/CyclomaticComplexity -- despite a high count of cases, this isn't that complex
|
||||
strong_memoize(:data) do
|
||||
case event || integration.default_test_event
|
||||
when 'push', 'tag_push'
|
||||
|
|
@ -35,6 +35,8 @@ module Integrations
|
|||
deployment_events_data
|
||||
when 'release'
|
||||
releases_events_data
|
||||
when 'milestone'
|
||||
milestone_events_data
|
||||
when 'award_emoji'
|
||||
emoji_events_data
|
||||
when 'current_user'
|
||||
|
|
|
|||
|
|
@ -2,14 +2,26 @@
|
|||
|
||||
module Milestones
|
||||
class BaseService < ::BaseService
|
||||
# Parent can either a group or a project
|
||||
attr_accessor :parent, :current_user, :params
|
||||
|
||||
def initialize(parent, user, params = {})
|
||||
# Parent can either a group or a project
|
||||
@parent = parent
|
||||
@current_user = user
|
||||
@params = params.dup
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_hooks(milestone, action)
|
||||
# At the moment, only project milestones support webhooks, not group milestones
|
||||
return unless milestone.project_milestone?
|
||||
return unless milestone.parent.has_active_hooks?(:milestone_hooks)
|
||||
|
||||
payload = Gitlab::DataBuilder::Milestone.build(milestone, action)
|
||||
milestone.parent.execute_hooks(payload, :milestone_hooks)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module Milestones
|
|||
def execute(milestone)
|
||||
if milestone.close && milestone.project_milestone?
|
||||
event_service.close_milestone(milestone, current_user)
|
||||
execute_hooks(milestone, 'close')
|
||||
end
|
||||
|
||||
milestone
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module Milestones
|
|||
|
||||
if milestone.save && milestone.project_milestone?
|
||||
event_service.open_milestone(milestone, current_user)
|
||||
execute_hooks(milestone, 'create')
|
||||
end
|
||||
|
||||
milestone
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ module Milestones
|
|||
|
||||
return unless milestone.destroyed?
|
||||
|
||||
execute_hooks(milestone, 'delete') if milestone.project_milestone?
|
||||
milestone
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module Milestones
|
|||
def execute(milestone)
|
||||
if milestone.activate && milestone.project_milestone?
|
||||
event_service.reopen_milestone(milestone, current_user)
|
||||
execute_hooks(milestone, 'reopen')
|
||||
end
|
||||
|
||||
milestone
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ module TestHooks
|
|||
wiki_page_events_data
|
||||
when 'releases_events'
|
||||
releases_events_data
|
||||
when 'milestone_events'
|
||||
milestone_events_data
|
||||
when 'emoji_events'
|
||||
emoji_events_data
|
||||
when 'resource_access_token_events'
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@
|
|||
= form.gitlab_ui_checkbox_component :releases_events,
|
||||
integration_webhook_event_human_name(:releases_events),
|
||||
help_text: s_('Webhooks|A release is created, updated, or deleted.')
|
||||
%li.gl-pb-3
|
||||
= form.gitlab_ui_checkbox_component :milestone_events,
|
||||
integration_webhook_event_human_name(:milestone_events),
|
||||
help_text: s_('Webhooks|A milestone is created, closed, reopened, or deleted.')
|
||||
%li.gl-pb-3
|
||||
- emoji_help_link = link_to s_('Which emoji events trigger webhooks'), help_page_path('user/project/integrations/webhook_events.md', anchor: 'emoji-events')
|
||||
= form.gitlab_ui_checkbox_component :emoji_events,
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/547584
|
|||
milestone: '18.1'
|
||||
group: group::source code
|
||||
type: beta
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddMilestoneEventsToWebHooks < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.2'
|
||||
|
||||
def change
|
||||
add_column :web_hooks, :milestone_events, :boolean, null: false, default: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
ecd7624a83f30d66eb361cdfa56d857162c5ff00a2f9ae3ce256013f003f4d1b
|
||||
|
|
@ -26012,6 +26012,7 @@ CREATE TABLE web_hooks (
|
|||
project_events boolean DEFAULT false NOT NULL,
|
||||
vulnerability_events boolean DEFAULT false NOT NULL,
|
||||
member_approval_events boolean DEFAULT false NOT NULL,
|
||||
milestone_events boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT check_1e4d5cbdc5 CHECK ((char_length(name) <= 255)),
|
||||
CONSTRAINT check_23a96ad211 CHECK ((char_length(description) <= 2048)),
|
||||
CONSTRAINT check_69ef76ee0c CHECK ((char_length(custom_webhook_template) <= 4096))
|
||||
|
|
|
|||
|
|
@ -552,7 +552,8 @@ gitlab-rake gitlab:db:schema_checker:run
|
|||
{{< /history >}}
|
||||
|
||||
The `gitlab:db:sos` command gathers configuration, performance, and diagnostic data about your GitLab
|
||||
database to help you troubleshoot issues. Where you run this command depends on your configuration:
|
||||
database to help you troubleshoot issues. Where you run this command depends on your configuration. Make sure
|
||||
to run this command relative to where GitLab is installed `(/gitlab)`.
|
||||
|
||||
- **Scaled GitLab**: on your Puma or Sidekiq server.
|
||||
- **Cloud native install**: on the toolbox pod.
|
||||
|
|
@ -561,9 +562,9 @@ database to help you troubleshoot issues. Where you run this command depends on
|
|||
Modify the command as needed:
|
||||
|
||||
- **Default path** - To run the command with the default file path (`/var/opt/gitlab/gitlab-rails/tmp/sos.zip`), run `gitlab-rake gitlab:db:sos`.
|
||||
- **Custom path** - To change the file path, run `gitlab-rake gitlab:db:sos["custom/path/to/file.zip"]`.
|
||||
- **Custom path** - To change the file path, run `gitlab-rake gitlab:db:sos["/absolute/custom/path/to/file.zip"]`.
|
||||
- **Zsh users** - If you have not modified your Zsh configuration, you must add quotation marks
|
||||
around the entire command, like this: `gitlab-rake "gitlab:db:sos[custom/path/to/file.zip]"`
|
||||
around the entire command, like this: `gitlab-rake "gitlab:db:sos[/absolute/custom/path/to/file.zip]"`
|
||||
|
||||
The Rake task runs for five minutes. It creates a compressed folder in the path you specify.
|
||||
The compressed folder contains a large number of files.
|
||||
|
|
|
|||
|
|
@ -2614,6 +2614,7 @@ Input type: `AuditEventsAmazonS3ConfigurationUpdateInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationauditeventsamazons3configurationupdateaccesskeyxid"></a>`accessKeyXid` | [`String`](#string) | Access key ID of the Amazon S3 account. |
|
||||
| <a id="mutationauditeventsamazons3configurationupdateactive"></a>`active` | [`Boolean`](#boolean) | Active status of the destination. |
|
||||
| <a id="mutationauditeventsamazons3configurationupdateawsregion"></a>`awsRegion` | [`String`](#string) | AWS region where the bucket is created. |
|
||||
| <a id="mutationauditeventsamazons3configurationupdatebucketname"></a>`bucketName` | [`String`](#string) | Name of the bucket where the audit events would be logged. |
|
||||
| <a id="mutationauditeventsamazons3configurationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
|
|
@ -2776,6 +2777,7 @@ Input type: `AuditEventsInstanceAmazonS3ConfigurationUpdateInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationupdateaccesskeyxid"></a>`accessKeyXid` | [`String`](#string) | Access key ID of the Amazon S3 account. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationupdateactive"></a>`active` | [`Boolean`](#boolean) | Active status of the destination. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationupdateawsregion"></a>`awsRegion` | [`String`](#string) | AWS region where the bucket is created. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationupdatebucketname"></a>`bucketName` | [`String`](#string) | Name of the bucket where the audit events would be logged. |
|
||||
| <a id="mutationauditeventsinstanceamazons3configurationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
|
|
@ -10014,6 +10016,11 @@ Input type: `ProjectUpdateComplianceFrameworksInput`
|
|||
|
||||
### `Mutation.prometheusIntegrationCreate`
|
||||
|
||||
{{< details >}}
|
||||
**Deprecated** in GitLab 18.2.
|
||||
Use HttpIntegrationCreate.
|
||||
{{< /details >}}
|
||||
|
||||
Input type: `PrometheusIntegrationCreateInput`
|
||||
|
||||
#### Arguments
|
||||
|
|
@ -10021,9 +10028,13 @@ Input type: `PrometheusIntegrationCreateInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationcreateactive"></a>`active` | [`Boolean!`](#boolean) | Whether the integration is receiving alerts. |
|
||||
| <a id="mutationprometheusintegrationcreateapiurl"></a>`apiUrl` | [`String`](#string) | Endpoint at which Prometheus can be queried. |
|
||||
| <a id="mutationprometheusintegrationcreateapiurl"></a>`apiUrl` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated**: Feature removed in 16.0. Deprecated in GitLab 18.2. |
|
||||
| <a id="mutationprometheusintegrationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationcreatename"></a>`name` | [`String`](#string) | Name of the integration. |
|
||||
| <a id="mutationprometheusintegrationcreatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | Custom mapping of GitLab alert attributes to fields from the payload example. |
|
||||
| <a id="mutationprometheusintegrationcreatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | Example of an alert payload. |
|
||||
| <a id="mutationprometheusintegrationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to create the integration in. |
|
||||
| <a id="mutationprometheusintegrationcreatetype"></a>`type` | [`AlertManagementIntegrationType`](#alertmanagementintegrationtype) | Type of integration to create. Cannot be changed after creation. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
@ -10035,6 +10046,11 @@ Input type: `PrometheusIntegrationCreateInput`
|
|||
|
||||
### `Mutation.prometheusIntegrationResetToken`
|
||||
|
||||
{{< details >}}
|
||||
**Deprecated** in GitLab 18.2.
|
||||
Use HttpIntegrationResetToken.
|
||||
{{< /details >}}
|
||||
|
||||
Input type: `PrometheusIntegrationResetTokenInput`
|
||||
|
||||
#### Arguments
|
||||
|
|
@ -10050,10 +10066,15 @@ Input type: `PrometheusIntegrationResetTokenInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationresettokenclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationresettokenerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
|
||||
| <a id="mutationprometheusintegrationresettokenintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | Newly created integration. |
|
||||
| <a id="mutationprometheusintegrationresettokenintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | Updated integration. |
|
||||
|
||||
### `Mutation.prometheusIntegrationUpdate`
|
||||
|
||||
{{< details >}}
|
||||
**Deprecated** in GitLab 18.2.
|
||||
Use HttpIntegrationUpdate.
|
||||
{{< /details >}}
|
||||
|
||||
Input type: `PrometheusIntegrationUpdateInput`
|
||||
|
||||
#### Arguments
|
||||
|
|
@ -10061,9 +10082,12 @@ Input type: `PrometheusIntegrationUpdateInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationupdateactive"></a>`active` | [`Boolean`](#boolean) | Whether the integration is receiving alerts. |
|
||||
| <a id="mutationprometheusintegrationupdateapiurl"></a>`apiUrl` | [`String`](#string) | Endpoint at which Prometheus can be queried. |
|
||||
| <a id="mutationprometheusintegrationupdateapiurl"></a>`apiUrl` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated**: Feature removed in 16.0. Deprecated in GitLab 18.2. |
|
||||
| <a id="mutationprometheusintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationupdateid"></a>`id` | [`IntegrationsPrometheusID!`](#integrationsprometheusid) | ID of the integration to mutate. |
|
||||
| <a id="mutationprometheusintegrationupdatename"></a>`name` | [`String`](#string) | Name of the integration. |
|
||||
| <a id="mutationprometheusintegrationupdatepayloadattributemappings"></a>`payloadAttributeMappings` | [`[AlertManagementPayloadAlertFieldInput!]`](#alertmanagementpayloadalertfieldinput) | Custom mapping of GitLab alert attributes to fields from the payload example. |
|
||||
| <a id="mutationprometheusintegrationupdatepayloadexample"></a>`payloadExample` | [`JsonString`](#jsonstring) | Example of an alert payload. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
@ -10071,7 +10095,7 @@ Input type: `PrometheusIntegrationUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationprometheusintegrationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationprometheusintegrationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
|
||||
| <a id="mutationprometheusintegrationupdateintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | Newly created integration. |
|
||||
| <a id="mutationprometheusintegrationupdateintegration"></a>`integration` | [`AlertManagementPrometheusIntegration`](#alertmanagementprometheusintegration) | Updated integration. |
|
||||
|
||||
### `Mutation.promoteToEpic`
|
||||
|
||||
|
|
@ -22006,7 +22030,7 @@ An endpoint and credentials used to accept alerts for a project.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="alertmanagementhttpintegrationactive"></a>`active` | [`Boolean`](#boolean) | Whether the endpoint is currently accepting alerts. |
|
||||
| <a id="alertmanagementhttpintegrationapiurl"></a>`apiUrl` | [`String`](#string) | URL at which Prometheus metrics can be queried to populate the metrics dashboard. |
|
||||
| <a id="alertmanagementhttpintegrationapiurl"></a>`apiUrl` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 18.2. Feature removed in 16.0. |
|
||||
| <a id="alertmanagementhttpintegrationid"></a>`id` | [`ID!`](#id) | ID of the integration. |
|
||||
| <a id="alertmanagementhttpintegrationname"></a>`name` | [`String`](#string) | Name of the integration. |
|
||||
| <a id="alertmanagementhttpintegrationpayloadalertfields"></a>`payloadAlertFields` | [`[AlertManagementPayloadAlertField!]`](#alertmanagementpayloadalertfield) | Extract alert fields from payload example for custom mapping. |
|
||||
|
|
@ -22043,14 +22067,14 @@ Parsed field (with its name) from an alert used for custom mappings.
|
|||
|
||||
### `AlertManagementPrometheusIntegration`
|
||||
|
||||
An endpoint and credentials used to accept Prometheus alerts for a project.
|
||||
**DEPRECATED - Use AlertManagementHttpIntegration directly** An endpoint and credentials used to accept Prometheus alerts for a project.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="alertmanagementprometheusintegrationactive"></a>`active` | [`Boolean`](#boolean) | Whether the endpoint is currently accepting alerts. |
|
||||
| <a id="alertmanagementprometheusintegrationapiurl"></a>`apiUrl` | [`String`](#string) | URL at which Prometheus metrics can be queried to populate the metrics dashboard. |
|
||||
| <a id="alertmanagementprometheusintegrationapiurl"></a>`apiUrl` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 18.2. Feature removed in 16.0. |
|
||||
| <a id="alertmanagementprometheusintegrationid"></a>`id` | [`ID!`](#id) | ID of the integration. |
|
||||
| <a id="alertmanagementprometheusintegrationname"></a>`name` | [`String`](#string) | Name of the integration. |
|
||||
| <a id="alertmanagementprometheusintegrationtoken"></a>`token` | [`String`](#string) | Token used to authenticate alert notification requests. |
|
||||
|
|
@ -22066,6 +22090,7 @@ Stores Amazon S3 configurations for audit event streaming.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="amazons3configurationtypeaccesskeyxid"></a>`accessKeyXid` | [`String!`](#string) | Access key ID of the Amazon S3 account. |
|
||||
| <a id="amazons3configurationtypeactive"></a>`active` | [`Boolean!`](#boolean) | Active status of the destination. |
|
||||
| <a id="amazons3configurationtypeawsregion"></a>`awsRegion` | [`String!`](#string) | AWS region where the bucket is created. |
|
||||
| <a id="amazons3configurationtypebucketname"></a>`bucketName` | [`String!`](#string) | Name of the bucket where the audit events would be logged. |
|
||||
| <a id="amazons3configurationtypegroup"></a>`group` | [`Group!`](#group) | Group the configuration belongs to. |
|
||||
|
|
@ -31021,6 +31046,7 @@ Stores instance level Amazon S3 configurations for audit event streaming.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="instanceamazons3configurationtypeaccesskeyxid"></a>`accessKeyXid` | [`String!`](#string) | Access key ID of the Amazon S3 account. |
|
||||
| <a id="instanceamazons3configurationtypeactive"></a>`active` | [`Boolean!`](#boolean) | Active status of the destination. |
|
||||
| <a id="instanceamazons3configurationtypeawsregion"></a>`awsRegion` | [`String!`](#string) | AWS region where the bucket is created. |
|
||||
| <a id="instanceamazons3configurationtypebucketname"></a>`bucketName` | [`String!`](#string) | Name of the bucket where the audit events would be logged. |
|
||||
| <a id="instanceamazons3configurationtypeid"></a>`id` | [`ID!`](#id) | ID of the configuration. |
|
||||
|
|
@ -36584,6 +36610,11 @@ four standard [pagination arguments](#pagination-arguments):
|
|||
|
||||
Integrations which can receive alerts for the project.
|
||||
|
||||
{{< details >}}
|
||||
**Deprecated** in GitLab 18.2.
|
||||
Use `alertManagementHttpIntegrations`.
|
||||
{{< /details >}}
|
||||
|
||||
Returns [`AlertManagementIntegrationConnection`](#alertmanagementintegrationconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
|
|
@ -49567,7 +49598,7 @@ Implementations:
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="alertmanagementintegrationactive"></a>`active` | [`Boolean`](#boolean) | Whether the endpoint is currently accepting alerts. |
|
||||
| <a id="alertmanagementintegrationapiurl"></a>`apiUrl` | [`String`](#string) | URL at which Prometheus metrics can be queried to populate the metrics dashboard. |
|
||||
| <a id="alertmanagementintegrationapiurl"></a>`apiUrl` {{< icon name="warning-solid" >}} | [`String`](#string) | **Deprecated** in GitLab 18.2. Feature removed in 16.0. |
|
||||
| <a id="alertmanagementintegrationid"></a>`id` | [`ID!`](#id) | ID of the integration. |
|
||||
| <a id="alertmanagementintegrationname"></a>`name` | [`String`](#string) | Name of the integration. |
|
||||
| <a id="alertmanagementintegrationtoken"></a>`token` | [`String`](#string) | Token used to authenticate alert notification requests. |
|
||||
|
|
@ -49586,6 +49617,7 @@ Implementations:
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="amazons3configurationinterfaceaccesskeyxid"></a>`accessKeyXid` | [`String!`](#string) | Access key ID of the Amazon S3 account. |
|
||||
| <a id="amazons3configurationinterfaceactive"></a>`active` | [`Boolean!`](#boolean) | Active status of the destination. |
|
||||
| <a id="amazons3configurationinterfaceawsregion"></a>`awsRegion` | [`String!`](#string) | AWS region where the bucket is created. |
|
||||
| <a id="amazons3configurationinterfacebucketname"></a>`bucketName` | [`String!`](#string) | Name of the bucket where the audit events would be logged. |
|
||||
| <a id="amazons3configurationinterfaceid"></a>`id` | [`ID!`](#id) | ID of the configuration. |
|
||||
|
|
|
|||
|
|
@ -1386,7 +1386,7 @@ Supported attributes:
|
|||
|---------------------|----------------|----------|-------------|
|
||||
| `id` | integer or string | Yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) owned by the authenticated user. |
|
||||
| `merge_request_iid` | integer | Yes | The internal ID of the merge request. |
|
||||
| `block_id` | integer | Yes | The internal ID of the block. |
|
||||
| `block_id` | integer | Yes | The ID of the block. |
|
||||
|
||||
Example request:
|
||||
|
||||
|
|
|
|||
|
|
@ -29125,6 +29125,7 @@ paths:
|
|||
- issues_events
|
||||
- job_events
|
||||
- merge_requests_events
|
||||
- milestone_events
|
||||
- note_events
|
||||
- pipeline_events
|
||||
- push_events
|
||||
|
|
@ -59246,6 +59247,8 @@ definitions:
|
|||
type: boolean
|
||||
releases_events:
|
||||
type: boolean
|
||||
milestone_events:
|
||||
type: boolean
|
||||
emoji_events:
|
||||
type: boolean
|
||||
resource_access_token_events:
|
||||
|
|
@ -59304,6 +59307,9 @@ definitions:
|
|||
releases_events:
|
||||
type: boolean
|
||||
description: Trigger hook on release events
|
||||
milestone_events:
|
||||
type: boolean
|
||||
description: Trigger hook on milestone events
|
||||
emoji_events:
|
||||
type: boolean
|
||||
description: Trigger hook on emoji events
|
||||
|
|
@ -59421,6 +59427,9 @@ definitions:
|
|||
releases_events:
|
||||
type: boolean
|
||||
description: Trigger hook on release events
|
||||
milestone_events:
|
||||
type: boolean
|
||||
description: Trigger hook on milestone events
|
||||
emoji_events:
|
||||
type: boolean
|
||||
description: Trigger hook on emoji events
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ Example response:
|
|||
"wiki_page_events": true,
|
||||
"deployment_events": true,
|
||||
"releases_events": true,
|
||||
"milestone_events": true,
|
||||
"feature_flag_events": true,
|
||||
"enable_ssl_verification": true,
|
||||
"repository_update_events": false,
|
||||
|
|
@ -431,6 +432,7 @@ Supported attributes:
|
|||
| `branch_filter_strategy` | string | No | Filter push events by branch. Possible values are `wildcard` (default), `regex`, and `all_branches`. |
|
||||
| `push_events` | boolean | No | Trigger project webhook on push events. |
|
||||
| `releases_events` | boolean | No | Trigger project webhook on release events. |
|
||||
| `milestone_events` | boolean | No | Trigger project webhook on milestone events. |
|
||||
| `tag_push_events` | boolean | No | Trigger project webhook on tag push events. |
|
||||
| `token` | string | No | Secret token to validate received payloads; the token isn't returned in the response. |
|
||||
| `wiki_page_events` | boolean | No | Trigger project webhook on wiki events. |
|
||||
|
|
@ -475,6 +477,7 @@ Supported attributes:
|
|||
| `branch_filter_strategy` | string | No | Filter push events by branch. Possible values are `wildcard` (default), `regex`, and `all_branches`. |
|
||||
| `push_events` | boolean | No | Trigger project webhook on push events. |
|
||||
| `releases_events` | boolean | No | Trigger project webhook on release events. |
|
||||
| `milestone_events` | boolean | No | Trigger project webhook on milestone events. |
|
||||
| `tag_push_events` | boolean | No | Trigger project webhook on tag push events. |
|
||||
| `token` | string | No | Secret token to validate received payloads. Not returned in the response. When you change the webhook URL, the secret token is reset and not retained. |
|
||||
| `wiki_page_events` | boolean | No | Trigger project webhook on wiki page events. |
|
||||
|
|
@ -531,7 +534,7 @@ Supported attributes:
|
|||
|:----------|:------------------|:---------|:------------|
|
||||
| `hook_id` | integer | Yes | ID of the project webhook. |
|
||||
| `id` | integer or string | Yes | ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
|
||||
| `trigger` | string | Yes | One of `push_events`, `tag_push_events`, `issues_events`, `confidential_issues_events`, `note_events`, `merge_requests_events`, `job_events`, `pipeline_events`, `wiki_page_events`, `releases_events`, `emoji_events`, or `resource_access_token_events`. |
|
||||
| `trigger` | string | Yes | One of `push_events`, `tag_push_events`, `issues_events`, `confidential_issues_events`, `note_events`, `merge_requests_events`, `job_events`, `pipeline_events`, `wiki_page_events`, `releases_events`, `milestone_events`, `emoji_events`, or `resource_access_token_events`. |
|
||||
|
||||
Example response:
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ Event type | Trigger
|
|||
[Deployment event](#deployment-events) | A deployment starts, succeeds, fails, or is canceled.
|
||||
[Feature flag event](#feature-flag-events) | A feature flag is turned on or off.
|
||||
[Release event](#release-events) | A release is created, edited, or deleted.
|
||||
[Milestone event](#milestone-events) | A milestone is created, closed, reopened, or deleted.
|
||||
[Emoji event](#emoji-events) | An emoji reaction is added or removed.
|
||||
[Project or group access token event](#project-and-group-access-token-events) | A project or group access token will expire in seven days.
|
||||
[Vulnerability event](#vulnerability-events) | A vulnerability is created or updated.
|
||||
|
|
@ -2137,6 +2138,69 @@ Payload example:
|
|||
}
|
||||
```
|
||||
|
||||
## Milestone events
|
||||
|
||||
{{< history >}}
|
||||
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14213) in GitLab 18.2.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
Milestone events are triggered when a milestone is created, closed, reopened, or deleted.
|
||||
|
||||
The available values for `object_attributes.action` in the payload are:
|
||||
|
||||
- `create`
|
||||
- `close`
|
||||
- `reopen`
|
||||
|
||||
Request header:
|
||||
|
||||
```plaintext
|
||||
X-Gitlab-Event: Milestone Hook
|
||||
```
|
||||
|
||||
Payload example:
|
||||
|
||||
```json
|
||||
{
|
||||
"object_kind": "milestone",
|
||||
"event_type": "milestone",
|
||||
"project": {
|
||||
"id": 1,
|
||||
"name": "Gitlab Test",
|
||||
"description": "Aut reprehenderit ut est.",
|
||||
"web_url": "http://example.com/gitlabhq/gitlab-test",
|
||||
"avatar_url": null,
|
||||
"git_ssh_url": "git@example.com:gitlabhq/gitlab-test.git",
|
||||
"git_http_url": "http://example.com/gitlabhq/gitlab-test.git",
|
||||
"namespace": "GitlabHQ",
|
||||
"visibility_level": 20,
|
||||
"path_with_namespace": "gitlabhq/gitlab-test",
|
||||
"default_branch": "master",
|
||||
"ci_config_path": null,
|
||||
"homepage": "http://example.com/gitlabhq/gitlab-test",
|
||||
"url": "http://example.com/gitlabhq/gitlab-test.git",
|
||||
"ssh_url": "git@example.com:gitlabhq/gitlab-test.git",
|
||||
"http_url": "http://example.com/gitlabhq/gitlab-test.git"
|
||||
},
|
||||
"object_attributes": {
|
||||
"id": 61,
|
||||
"iid": 10,
|
||||
"title": "v1.0",
|
||||
"description": "First stable release",
|
||||
"state": "active",
|
||||
"created_at": "2025-06-16 14:10:57 UTC",
|
||||
"updated_at": "2025-06-16 14:10:57 UTC",
|
||||
"due_date": "2025-06-30",
|
||||
"start_date": "2025-06-16",
|
||||
"group_id": null,
|
||||
"project_id": 1
|
||||
},
|
||||
"action": "create"
|
||||
}
|
||||
```
|
||||
|
||||
## Emoji events
|
||||
|
||||
{{< history >}}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ module API
|
|||
expose :feature_flag_events, documentation: { type: 'boolean' }
|
||||
expose :job_events, documentation: { type: 'boolean' }
|
||||
expose :releases_events, documentation: { type: 'boolean' }
|
||||
expose :milestone_events, documentation: { type: 'boolean' }
|
||||
expose :emoji_events, documentation: { type: 'boolean' }
|
||||
expose :resource_access_token_events, documentation: { type: 'boolean' }
|
||||
expose :vulnerability_events, documentation: { type: 'boolean' }
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ module API
|
|||
optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events"
|
||||
optional :feature_flag_events, type: Boolean, desc: "Trigger hook on feature flag events"
|
||||
optional :releases_events, type: Boolean, desc: "Trigger hook on release events"
|
||||
optional :milestone_events, type: Boolean, desc: "Trigger hook on milestone events"
|
||||
optional :emoji_events, type: Boolean, desc: "Trigger hook on emoji events"
|
||||
optional :resource_access_token_events, type: Boolean, desc: "Trigger hook on project access token expiry events"
|
||||
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module DataBuilder
|
||||
module Milestone
|
||||
extend self
|
||||
|
||||
SAMPLE_DATA = {
|
||||
id: 1,
|
||||
iid: 1,
|
||||
title: 'Sample milestone',
|
||||
description: 'Sample milestone description',
|
||||
state: 'active',
|
||||
created_at: Time.current,
|
||||
updated_at: Time.current,
|
||||
due_date: 1.week.from_now,
|
||||
start_date: Time.current
|
||||
}.freeze
|
||||
|
||||
def build(milestone, action)
|
||||
{
|
||||
object_kind: 'milestone',
|
||||
event_type: 'milestone',
|
||||
project: milestone.project&.hook_attrs,
|
||||
object_attributes: milestone.hook_attrs,
|
||||
action: action
|
||||
}
|
||||
end
|
||||
|
||||
def build_sample(project)
|
||||
milestone = project.milestones.first || ::Milestone.new(SAMPLE_DATA.merge(project: project))
|
||||
build(milestone, 'create')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module HookData
|
||||
class MilestoneBuilder < BaseBuilder
|
||||
SAFE_HOOK_ATTRIBUTES = %i[
|
||||
id
|
||||
iid
|
||||
title
|
||||
description
|
||||
state
|
||||
created_at
|
||||
updated_at
|
||||
due_date
|
||||
start_date
|
||||
project_id
|
||||
].freeze
|
||||
|
||||
alias_method :milestone, :object
|
||||
|
||||
def build
|
||||
milestone
|
||||
.attributes
|
||||
.with_indifferent_access
|
||||
.slice(*SAFE_HOOK_ATTRIBUTES)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2433,9 +2433,6 @@ msgstr ""
|
|||
msgid "AICatalog|Provide default instructions or context that will be included with every user interaction."
|
||||
msgstr ""
|
||||
|
||||
msgid "AICatalog|Released %{fullDate}"
|
||||
msgstr ""
|
||||
|
||||
msgid "AICatalog|Run"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17002,21 +16999,33 @@ msgstr ""
|
|||
msgid "ComplianceViolation|Audit event captured"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Control"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Detected"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Dismissed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Framework"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|In review"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Registered event IP"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Requirement"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Resolved"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceViolation|Violation created based on associated framework"
|
||||
msgstr ""
|
||||
|
||||
msgid "Compliance|Framework deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -28163,6 +28172,9 @@ msgstr ""
|
|||
msgid "Geo|Replication Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Replication failure"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Replication information"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -28382,6 +28394,9 @@ msgstr ""
|
|||
msgid "Geo|Verification concurrency limit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Verification failure"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Verification information"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31536,6 +31551,9 @@ msgstr ""
|
|||
msgid "Hide details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide file browser"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40370,9 +40388,6 @@ msgstr ""
|
|||
msgid "Multiple IP address ranges are supported. Does not affect access to the group's settings."
|
||||
msgstr ""
|
||||
|
||||
msgid "Multiple Prometheus integrations are not supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multiple components '%{name}' have '%{attribute}' attribute"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -59015,6 +59030,9 @@ msgstr ""
|
|||
msgid "Show epics in global search results"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show file browser"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -69486,6 +69504,9 @@ msgstr ""
|
|||
msgid "Webhooks|A merge request is created, updated, or merged."
|
||||
msgstr ""
|
||||
|
||||
msgid "Webhooks|A milestone is created, closed, reopened, or deleted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Webhooks|A new tag is pushed to the repository."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -73049,6 +73070,9 @@ msgstr ""
|
|||
msgid "added a Zoom call to this issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "agents"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,15 @@ FactoryBot.define do
|
|||
|
||||
initialize_with { new(**attributes) }
|
||||
|
||||
trait :prometheus do
|
||||
type_identifier { :prometheus }
|
||||
end
|
||||
|
||||
factory :alert_management_prometheus_integration, traits: [:prometheus] do
|
||||
type_identifier { :prometheus }
|
||||
|
||||
trait :legacy do
|
||||
name { 'Prometheus' }
|
||||
endpoint_identifier { 'legacy-prometheus' }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,9 +7,4 @@ FactoryBot.define do
|
|||
minimum_access_level_for_delete { :maintainer }
|
||||
minimum_access_level_for_push { :maintainer }
|
||||
end
|
||||
|
||||
trait :immutable do
|
||||
minimum_access_level_for_delete { nil }
|
||||
minimum_access_level_for_push { nil }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ FactoryBot.define do
|
|||
deployment_events { true }
|
||||
feature_flag_events { true }
|
||||
releases_events { true }
|
||||
milestone_events { true }
|
||||
emoji_events { true }
|
||||
vulnerability_events { true }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ RSpec.describe 'Projects > Settings > Webhook Settings', feature_category: :webh
|
|||
expect(page).to have_content('Comment')
|
||||
expect(page).to have_content('Merge request events')
|
||||
expect(page).to have_content('Pipeline events')
|
||||
expect(page).to have_content('Milestone events')
|
||||
expect(page).to have_content('Wiki page events')
|
||||
expect(page).to have_content('Releases events')
|
||||
expect(page).to have_content('Emoji events')
|
||||
|
|
|
|||
|
|
@ -117,6 +117,9 @@
|
|||
"releases_events": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"milestone_events": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"emoji_events": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create, feature_category: :api do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:api_url) { 'http://prometheus.com/' }
|
||||
let(:args) { { project_path: project.full_path, active: true, api_url: api_url } }
|
||||
let(:args) { { project_path: project.full_path, active: true, api_url: 'http://prometheus.com/' } }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
|
||||
|
||||
describe '#resolve' do
|
||||
subject(:resolve) { mutation_for(project, current_user).resolve(args) }
|
||||
|
|
@ -20,49 +20,31 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Create, featur
|
|||
project.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
context 'when Prometheus Integration already exists' do
|
||||
let_it_be(:existing_integration) { create(:prometheus_integration, project: project) }
|
||||
|
||||
it 'returns errors' do
|
||||
expect(resolve).to eq(
|
||||
integration: nil,
|
||||
errors: ['Multiple Prometheus integrations are not supported']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when api_url is nil' do
|
||||
let(:api_url) { nil }
|
||||
|
||||
it 'creates the integration' do
|
||||
expect { resolve }.to change(::Alerting::ProjectAlertingSetting, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when UpdateService responds with success' do
|
||||
context 'when HttpIntegrations::CreateService responds with success' do
|
||||
it 'returns the integration with no errors' do
|
||||
expect(resolve).to eq(
|
||||
integration: ::Integrations::Prometheus.last!,
|
||||
integration: ::AlertManagement::HttpIntegration.last!,
|
||||
errors: []
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates a corresponding token' do
|
||||
expect { resolve }.to change(::Integrations::Prometheus, :count).by(1)
|
||||
expect(resolve[:integration]).to have_attributes(
|
||||
active: true,
|
||||
name: 'Prometheus',
|
||||
type_identifier: 'prometheus'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when UpdateService responds with an error' do
|
||||
context 'when HttpIntegrations::CreateService responds with an error' do
|
||||
before do
|
||||
allow_any_instance_of(::Projects::Operations::UpdateService)
|
||||
allow_any_instance_of(::AlertManagement::HttpIntegrations::CreateService)
|
||||
.to receive(:execute)
|
||||
.and_return({ status: :error, message: 'An error occurred' })
|
||||
.and_return(ServiceResponse.error(payload: { integration: nil }, message: 'An integration already exists'))
|
||||
end
|
||||
|
||||
it 'returns errors' do
|
||||
expect(resolve).to eq(
|
||||
integration: nil,
|
||||
errors: ['An error occurred']
|
||||
errors: ['An integration already exists']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken, fe
|
|||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be_with_reload(:integration) { create(:alert_management_prometheus_integration, :legacy, project: project) }
|
||||
let_it_be_with_reload(:old_integration) { create(:prometheus_integration, project: project) }
|
||||
|
||||
let(:args) { { id: GitlabSchema.id_from_object(integration) } }
|
||||
let(:args) { { id: GitlabSchema.id_from_object(old_integration) } }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
|
||||
|
||||
describe '#resolve' do
|
||||
subject(:resolve) { mutation_for(project, current_user).resolve(**args) }
|
||||
|
|
@ -21,8 +22,10 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken, fe
|
|||
project.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
context 'when ::Projects::Operations::UpdateService responds with success' do
|
||||
context 'when ::AlertManagement::HttpIntegrations::UpdateService responds with success' do
|
||||
it 'returns the integration with no errors' do
|
||||
expect { resolve }.to change { integration.reload.token }
|
||||
|
||||
expect(resolve).to eq(
|
||||
integration: integration,
|
||||
errors: []
|
||||
|
|
@ -30,20 +33,42 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::ResetToken, fe
|
|||
end
|
||||
end
|
||||
|
||||
context 'when ::Projects::Operations::UpdateService responds with an error' do
|
||||
context 'when ::AlertManagement::HttpIntegrations::UpdateService responds with an error' do
|
||||
before do
|
||||
allow_any_instance_of(::Projects::Operations::UpdateService)
|
||||
allow_any_instance_of(::AlertManagement::HttpIntegrations::UpdateService)
|
||||
.to receive(:execute)
|
||||
.and_return({ status: :error, message: 'An error occurred' })
|
||||
.and_return(ServiceResponse.error(payload: { integration: nil }, message: 'An error occurred'))
|
||||
end
|
||||
|
||||
it 'returns errors' do
|
||||
expect(resolve).to eq(
|
||||
integration: integration,
|
||||
integration: nil,
|
||||
errors: ['An error occurred']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when prometheus_integration does not exist' do
|
||||
before do
|
||||
old_integration.destroy!
|
||||
end
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect(args[:id]).to be_present
|
||||
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when prometheus_integration does not have corresponding AlertManagement::HttpIntegration' do
|
||||
before do
|
||||
integration.destroy!
|
||||
end
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when resource is not accessible to the user' do
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Update, featur
|
|||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be_with_reload(:old_integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be_with_reload(:integration) { create(:alert_management_prometheus_integration, :legacy, project: project) }
|
||||
|
||||
let(:args) { { id: GitlabSchema.id_from_object(integration), active: false, api_url: 'http://new-url.com' } }
|
||||
let(:args) { { id: GitlabSchema.id_from_object(old_integration), active: false, api_url: 'http://new-url.com' } }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
|
||||
|
||||
describe '#resolve' do
|
||||
subject(:resolve) { mutation_for(project, current_user).resolve(args) }
|
||||
|
|
@ -21,29 +22,52 @@ RSpec.describe Mutations::AlertManagement::PrometheusIntegration::Update, featur
|
|||
project.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
context 'when ::Projects::Operations::UpdateService responds with success' do
|
||||
context 'when ::AlertManagement::HttpIntegrations::UpdateService responds with success' do
|
||||
it 'returns the integration with no errors' do
|
||||
expect(resolve).to eq(
|
||||
integration: integration,
|
||||
errors: []
|
||||
)
|
||||
expect(integration.reload.active?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ::Projects::Operations::UpdateService responds with an error' do
|
||||
context 'when ::AlertManagement::HttpIntegrations::UpdateService responds with an error' do
|
||||
before do
|
||||
allow_any_instance_of(::Projects::Operations::UpdateService)
|
||||
allow_any_instance_of(::AlertManagement::HttpIntegrations::UpdateService)
|
||||
.to receive(:execute)
|
||||
.and_return({ status: :error, message: 'An error occurred' })
|
||||
.and_return(ServiceResponse.error(payload: { integration: nil }, message: 'An error occurred'))
|
||||
end
|
||||
|
||||
it 'returns errors' do
|
||||
expect(resolve).to eq(
|
||||
integration: integration,
|
||||
integration: nil,
|
||||
errors: ['An error occurred']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when prometheus_integration does not exist' do
|
||||
before do
|
||||
old_integration.destroy!
|
||||
end
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect(args[:id]).to be_present
|
||||
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when prometheus_integration does not have corresponding AlertManagement::HttpIntegration' do
|
||||
before do
|
||||
integration.destroy!
|
||||
end
|
||||
|
||||
it 'raises an error if the resource is not accessible to the user' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when resource is not accessible to the user' do
|
||||
|
|
|
|||
|
|
@ -8,13 +8,17 @@ RSpec.describe Resolvers::AlertManagement::IntegrationsResolver, feature_categor
|
|||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:project2) { create(:project) }
|
||||
let_it_be(:prometheus_integration) { create(:prometheus_integration, project: project) }
|
||||
|
||||
let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) }
|
||||
let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) }
|
||||
let_it_be(:other_proj_integration) { create(:alert_management_http_integration, project: project2) }
|
||||
let_it_be(:other_proj_prometheus_integration) { create(:prometheus_integration, project: project2) }
|
||||
|
||||
let_it_be(:prometheus_integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be(:migrated_integration) { create(:alert_management_prometheus_integration, :legacy, project: project) }
|
||||
|
||||
let_it_be(:alt_prometheus_integration) { create(:prometheus_integration, project: project2) }
|
||||
let_it_be(:alt_migrated_integration) { create(:alert_management_prometheus_integration, :legacy, project: project2) }
|
||||
|
||||
let(:params) { {} }
|
||||
|
||||
subject { sync(resolve_http_integrations(params)) }
|
||||
|
|
@ -33,7 +37,7 @@ RSpec.describe Resolvers::AlertManagement::IntegrationsResolver, feature_categor
|
|||
project2.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(active_http_integration, prometheus_integration) }
|
||||
it { is_expected.to contain_exactly(active_http_integration, migrated_integration) }
|
||||
|
||||
context 'when HTTP Integration ID is given' do
|
||||
context 'when integration is from the current project' do
|
||||
|
|
@ -53,11 +57,11 @@ RSpec.describe Resolvers::AlertManagement::IntegrationsResolver, feature_categor
|
|||
context 'when integration is from the current project' do
|
||||
let(:params) { { id: global_id_of(prometheus_integration) } }
|
||||
|
||||
it { is_expected.to contain_exactly(prometheus_integration) }
|
||||
it { is_expected.to contain_exactly(migrated_integration) }
|
||||
end
|
||||
|
||||
context 'when integration is from other project' do
|
||||
let(:params) { { id: global_id_of(other_proj_prometheus_integration) } }
|
||||
let(:params) { { id: global_id_of(alt_prometheus_integration) } }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do
|
||||
RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'], feature_category: :incident_management do
|
||||
include GraphqlHelpers
|
||||
|
||||
specify { expect(described_class.graphql_name).to eq('AlertManagementPrometheusIntegration') }
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_project) }
|
||||
specify { expect(described_class).to require_graphql_authorizations(:admin_operations) }
|
||||
|
||||
describe 'resolvers' do
|
||||
shared_examples_for 'has field with value' do |field_name|
|
||||
|
|
@ -17,11 +17,11 @@ RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do
|
|||
end
|
||||
end
|
||||
|
||||
let_it_be_with_reload(:integration) { create(:prometheus_integration) }
|
||||
let_it_be_with_reload(:integration) { create(:alert_management_prometheus_integration, :legacy) }
|
||||
let_it_be(:user) { create(:user, maintainer_of: integration.project) }
|
||||
|
||||
it_behaves_like 'has field with value', 'name' do
|
||||
let(:value) { integration.title }
|
||||
let(:value) { integration.name }
|
||||
end
|
||||
|
||||
it_behaves_like 'has field with value', 'type' do
|
||||
|
|
@ -29,7 +29,7 @@ RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do
|
|||
end
|
||||
|
||||
it_behaves_like 'has field with value', 'token' do
|
||||
let(:value) { nil }
|
||||
let(:value) { integration.token }
|
||||
end
|
||||
|
||||
it_behaves_like 'has field with value', 'url' do
|
||||
|
|
@ -37,33 +37,7 @@ RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do
|
|||
end
|
||||
|
||||
it_behaves_like 'has field with value', 'active' do
|
||||
let(:value) { integration.manual_configuration? }
|
||||
end
|
||||
|
||||
context 'with alerting setting' do
|
||||
let_it_be(:alerting_setting) { create(:project_alerting_setting, project: integration.project) }
|
||||
|
||||
it_behaves_like 'has field with value', 'token' do
|
||||
let(:value) { alerting_setting.token }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'a group integration' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:integration) { create(:prometheus_integration, :group, group: group) }
|
||||
|
||||
# Since it is impossible to authorize the parent here, given that the
|
||||
# project is nil, all fields should be redacted:
|
||||
|
||||
described_class.fields.each_key do |field_name|
|
||||
context "field: #{field_name}" do
|
||||
it 'is redacted' do
|
||||
expect do
|
||||
resolve_field(field_name, integration, current_user: user)
|
||||
end.to raise_error(GraphqlHelpers::UnauthorizedObject)
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:value) { integration.active }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::DataBuilder::Milestone, feature_category: :team_planning do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
shared_examples 'builds milestone hook data' do
|
||||
it { expect(data).to be_a(Hash) }
|
||||
|
||||
it 'includes the correct structure' do
|
||||
expect(data[:object_kind]).to eq('milestone')
|
||||
expect(data[:event_type]).to eq('milestone')
|
||||
expect(data[:action]).to eq(action)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.build' do
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
let(:action) { 'create' }
|
||||
|
||||
subject(:data) { described_class.build(milestone, action) }
|
||||
|
||||
it_behaves_like 'builds milestone hook data'
|
||||
|
||||
it 'includes project data' do
|
||||
expect(data[:project]).to eq(milestone.project.hook_attrs)
|
||||
end
|
||||
|
||||
it 'includes milestone attributes' do
|
||||
object_attributes = data[:object_attributes]
|
||||
|
||||
expect(object_attributes[:id]).to eq(milestone.id)
|
||||
expect(object_attributes[:iid]).to eq(milestone.iid)
|
||||
expect(object_attributes[:title]).to eq(milestone.title)
|
||||
expect(object_attributes[:description]).to eq(milestone.description)
|
||||
expect(object_attributes[:state]).to eq(milestone.state)
|
||||
expect(object_attributes[:created_at]).to eq(milestone.created_at)
|
||||
expect(object_attributes[:updated_at]).to eq(milestone.updated_at)
|
||||
expect(object_attributes[:due_date]).to eq(milestone.due_date)
|
||||
expect(object_attributes[:start_date]).to eq(milestone.start_date)
|
||||
expect(object_attributes[:project_id]).to eq(milestone.project_id)
|
||||
end
|
||||
|
||||
context 'with different actions' do
|
||||
%w[create close reopen].each do |test_action|
|
||||
context "when action is #{test_action}" do
|
||||
let(:action) { test_action }
|
||||
|
||||
it "sets the action to #{test_action}" do
|
||||
expect(data[:action]).to eq(test_action)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with milestone having dates' do
|
||||
let(:milestone) { create(:milestone, project: project, due_date: 1.week.from_now, start_date: 1.day.ago) }
|
||||
|
||||
it 'includes the date information' do
|
||||
expect(data[:object_attributes][:due_date]).to eq(milestone.due_date)
|
||||
expect(data[:object_attributes][:start_date]).to eq(milestone.start_date)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with milestone having no dates' do
|
||||
let(:milestone) { create(:milestone, project: project, due_date: nil, start_date: nil) }
|
||||
|
||||
it 'includes nil date information' do
|
||||
expect(data[:object_attributes][:due_date]).to be_nil
|
||||
expect(data[:object_attributes][:start_date]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with closed milestone' do
|
||||
let(:milestone) { create(:milestone, :closed, project: project) }
|
||||
|
||||
it 'includes the correct state' do
|
||||
expect(data[:object_attributes][:state]).to eq('closed')
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'project hook data'
|
||||
end
|
||||
|
||||
describe '.build_sample' do
|
||||
let(:action) { 'create' }
|
||||
|
||||
context 'when project has existing milestones' do
|
||||
subject(:data) { described_class.build_sample(project) }
|
||||
|
||||
let_it_be(:existing_milestone) { create(:milestone, project: project) }
|
||||
|
||||
it_behaves_like 'builds milestone hook data'
|
||||
|
||||
it 'includes project data' do
|
||||
expect(data[:project]).to eq(project.hook_attrs)
|
||||
end
|
||||
|
||||
it 'uses the first existing milestone' do
|
||||
expect(data[:object_attributes][:id]).to eq(existing_milestone.id)
|
||||
expect(data[:object_attributes][:title]).to eq(existing_milestone.title)
|
||||
end
|
||||
|
||||
include_examples 'project hook data'
|
||||
end
|
||||
|
||||
context 'when project has no milestones' do
|
||||
subject(:data) { described_class.build_sample(clean_project) }
|
||||
|
||||
let_it_be(:clean_project) { create(:project) }
|
||||
|
||||
it_behaves_like 'builds milestone hook data'
|
||||
|
||||
it 'includes project data' do
|
||||
expect(data[:project]).to eq(clean_project.hook_attrs)
|
||||
end
|
||||
|
||||
it 'creates a sample milestone with predefined data' do
|
||||
expect(data[:object_attributes][:title]).to eq('Sample milestone')
|
||||
expect(data[:object_attributes][:description]).to eq('Sample milestone description')
|
||||
expect(data[:object_attributes][:state]).to eq('active')
|
||||
end
|
||||
|
||||
include_examples 'project hook data' do
|
||||
let(:project) { clean_project }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::HookData::MilestoneBuilder, feature_category: :webhooks do
|
||||
let_it_be(:milestone) { create(:milestone) }
|
||||
|
||||
let(:builder) { described_class.new(milestone) }
|
||||
|
||||
describe '#build' do
|
||||
subject(:data) { builder.build }
|
||||
|
||||
it 'includes safe attributes' do
|
||||
expect(data.keys).to match_array(described_class::SAFE_HOOK_ATTRIBUTES.map(&:to_s))
|
||||
end
|
||||
|
||||
it 'returns indifferent access hash' do
|
||||
expect(data).to be_a(ActiveSupport::HashWithIndifferentAccess)
|
||||
end
|
||||
|
||||
it 'includes correct milestone data' do
|
||||
expect(data['id']).to eq(milestone.id)
|
||||
expect(data['iid']).to eq(milestone.iid)
|
||||
expect(data['title']).to eq(milestone.title)
|
||||
expect(data['state']).to eq(milestone.state)
|
||||
expect(data['project_id']).to eq(milestone.project_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -67,6 +67,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_
|
|||
'job_events' => false,
|
||||
'wiki_page_events' => true,
|
||||
'releases_events' => false,
|
||||
'milestone_events' => false,
|
||||
'emoji_events' => false,
|
||||
'resource_access_token_events' => false,
|
||||
'token' => token
|
||||
|
|
|
|||
|
|
@ -619,6 +619,7 @@ ProjectHook:
|
|||
- confidential_note_events
|
||||
- repository_update_events
|
||||
- releases_events
|
||||
- milestone_events
|
||||
- emoji_events
|
||||
- resource_access_token_events
|
||||
ProtectedBranch:
|
||||
|
|
|
|||
|
|
@ -38,21 +38,7 @@ RSpec.describe 'Creating a new Prometheus Integration', feature_category: :incid
|
|||
|
||||
let(:mutation_response) { graphql_mutation_response(:prometheus_integration_create) }
|
||||
|
||||
it 'creates a new integration' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
new_integration = ::Integrations::Prometheus.last!
|
||||
integration_response = mutation_response['integration']
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(new_integration).to_s)
|
||||
expect(integration_response['type']).to eq('PROMETHEUS')
|
||||
expect(integration_response['name']).to eq(new_integration.title)
|
||||
expect(integration_response['active']).to eq(new_integration.manual_configuration?)
|
||||
expect(integration_response['token']).to eq(new_integration.project.alerting_setting.token)
|
||||
expect(integration_response['url']).to eq("http://localhost/#{project.full_path}/prometheus/alerts/notify.json")
|
||||
expect(integration_response['apiUrl']).to eq(new_integration.api_url)
|
||||
end
|
||||
it_behaves_like 'creating a new HTTP integration', 'PROMETHEUS'
|
||||
|
||||
context 'without api url' do
|
||||
let(:api_url) { nil }
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ RSpec.describe 'Resetting a token on an existing Prometheus Integration', featur
|
|||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, maintainers: user) }
|
||||
let_it_be(:integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be(:old_integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be(:integration) { create(:alert_management_prometheus_integration, :legacy, project: project) }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
id: GitlabSchema.id_from_object(integration).to_s
|
||||
id: GitlabSchema.id_from_object(old_integration).to_s
|
||||
}
|
||||
graphql_mutation(:prometheus_integration_reset_token, variables) do
|
||||
<<~QL
|
||||
|
|
@ -27,29 +28,15 @@ RSpec.describe 'Resetting a token on an existing Prometheus Integration', featur
|
|||
|
||||
let(:mutation_response) { graphql_mutation_response(:prometheus_integration_reset_token) }
|
||||
|
||||
it 'creates a token' do
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
it 'updates the token' do
|
||||
expect { post_graphql_mutation(mutation, current_user: user) }
|
||||
.to change { integration.reload.token }
|
||||
|
||||
integration_response = mutation_response['integration']
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
|
||||
expect(integration_response['token']).not_to be_nil
|
||||
expect(integration_response['token']).to eq(project.alerting_setting.token)
|
||||
end
|
||||
|
||||
context 'with an existing alerting setting' do
|
||||
let_it_be(:alerting_setting) { create(:project_alerting_setting, project: project) }
|
||||
|
||||
it 'updates the token' do
|
||||
previous_token = alerting_setting.token
|
||||
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
integration_response = mutation_response['integration']
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
|
||||
expect(integration_response['token']).not_to eq(previous_token)
|
||||
expect(integration_response['token']).to eq(alerting_setting.reload.token)
|
||||
end
|
||||
expect(integration_response['token']).to eq(integration.token)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ RSpec.describe 'Updating an existing Prometheus Integration', feature_category:
|
|||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, maintainers: user) }
|
||||
let_it_be(:integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be(:old_integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be(:integration) { create(:alert_management_prometheus_integration, :legacy, project: project) }
|
||||
|
||||
let(:mutation) do
|
||||
variables = {
|
||||
id: GitlabSchema.id_from_object(integration).to_s,
|
||||
id: GitlabSchema.id_from_object(old_integration).to_s,
|
||||
api_url: 'http://modified-url.com',
|
||||
active: true
|
||||
active: false
|
||||
}
|
||||
graphql_mutation(:prometheus_integration_update, variables) do
|
||||
<<~QL
|
||||
|
|
@ -37,7 +38,7 @@ RSpec.describe 'Updating an existing Prometheus Integration', feature_category:
|
|||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s)
|
||||
expect(integration_response['apiUrl']).to eq('http://modified-url.com')
|
||||
expect(integration_response['active']).to be_truthy
|
||||
expect(integration_response['apiUrl']).to be_nil
|
||||
expect(integration_response['active']).to be_falsey
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ RSpec.describe 'getting Alert Management Integrations', feature_category: :incid
|
|||
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:prometheus_integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be(:project_alerting_setting) { create(:project_alerting_setting, project: project) }
|
||||
let_it_be(:old_prometheus_integration) { create(:prometheus_integration, project: project) }
|
||||
let_it_be(:prometheus_integration) { create(:alert_management_prometheus_integration, :legacy, project: project) }
|
||||
let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) }
|
||||
let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) }
|
||||
let_it_be(:other_project_http_integration) { create(:alert_management_http_integration) }
|
||||
|
|
@ -63,12 +63,7 @@ RSpec.describe 'getting Alert Management Integrations', feature_category: :incid
|
|||
),
|
||||
a_graphql_entity_for(
|
||||
prometheus_integration,
|
||||
'type' => 'PROMETHEUS',
|
||||
'name' => 'Prometheus',
|
||||
'active' => prometheus_integration.manual_configuration?,
|
||||
'token' => project_alerting_setting.token,
|
||||
'url' => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/prometheus/alerts/notify.json",
|
||||
'apiUrl' => prometheus_integration.api_url
|
||||
:name, :active, :token, :url, type: 'PROMETHEUS', api_url: nil
|
||||
)
|
||||
]
|
||||
end
|
||||
|
|
@ -87,19 +82,14 @@ RSpec.describe 'getting Alert Management Integrations', feature_category: :incid
|
|||
end
|
||||
|
||||
context 'when Prometheus Integration ID is given' do
|
||||
let(:params) { { id: global_id_of(prometheus_integration) } }
|
||||
let(:params) { { id: global_id_of(old_prometheus_integration) } }
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
it 'returns the correct properties of the Prometheus Integration' do
|
||||
expect(integrations).to contain_exactly a_graphql_entity_for(
|
||||
prometheus_integration,
|
||||
'type' => 'PROMETHEUS',
|
||||
'name' => 'Prometheus',
|
||||
'active' => prometheus_integration.manual_configuration?,
|
||||
'token' => project_alerting_setting.token,
|
||||
'url' => "http://localhost/#{project.full_path}/prometheus/alerts/notify.json",
|
||||
'apiUrl' => prometheus_integration.api_url
|
||||
:name, :active, :token, :url, type: 'PROMETHEUS', api_url: nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,6 +41,24 @@ RSpec.describe Integrations::Test::ProjectService, feature_category: :integratio
|
|||
end
|
||||
end
|
||||
|
||||
context 'milestone' do
|
||||
let(:event) { 'milestone' }
|
||||
|
||||
before do
|
||||
# Mock the integration to support milestone events for testing
|
||||
allow(integration).to receive(:supported_events).and_return(integration.supported_events + ['milestone'])
|
||||
end
|
||||
|
||||
it 'executes integration' do
|
||||
milestone = create(:milestone, project: project)
|
||||
allow(Gitlab::DataBuilder::Milestone).to receive(:build).and_return(sample_data)
|
||||
allow_next(MilestonesFinder).to receive(:execute).and_return([milestone])
|
||||
|
||||
expect(integration).to receive(:test).with(sample_data).and_return(success_result)
|
||||
expect(subject).to eq(success_result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'push' do
|
||||
let(:event) { 'push' }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,19 +12,71 @@ RSpec.describe Milestones::CloseService, feature_category: :team_planning do
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
before do
|
||||
described_class.new(project, user, {}).execute(milestone)
|
||||
let(:service) { described_class.new(project, user, {}) }
|
||||
|
||||
context 'when service is called before test suite' do
|
||||
before do
|
||||
service.execute(milestone)
|
||||
end
|
||||
|
||||
it { expect(milestone).to be_valid }
|
||||
it { expect(milestone).to be_closed }
|
||||
|
||||
describe 'event' do
|
||||
let(:event) { Event.recent.first }
|
||||
|
||||
it { expect(event.milestone).to be_truthy }
|
||||
it { expect(event.target).to eq(milestone) }
|
||||
it { expect(event.action_name).to eq('closed') }
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(milestone).to be_valid }
|
||||
it { expect(milestone).to be_closed }
|
||||
shared_examples 'closes the milestone' do |with_project_hooks:|
|
||||
it 'executes hooks with close action and creates new event' do
|
||||
expect(service).to receive(:execute_hooks).with(milestone, 'close').and_call_original
|
||||
expect(project).to receive(:execute_hooks).with(kind_of(Hash), :milestone_hooks) if with_project_hooks
|
||||
|
||||
describe 'event' do
|
||||
let(:event) { Event.recent.first }
|
||||
expect { service.execute(milestone) }.to change { Event.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(event.milestone).to be_truthy }
|
||||
it { expect(event.target).to eq(milestone) }
|
||||
it { expect(event.action_name).to eq('closed') }
|
||||
shared_examples 'does not close the milestone' do
|
||||
it 'does not execute hooks and does not create new event' do
|
||||
expect(service).not_to receive(:execute_hooks)
|
||||
|
||||
expect { service.execute(milestone) }.not_to change { Event.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when milestone is successfully closed' do
|
||||
context 'when project has active milestone hooks' do
|
||||
let(:project) do
|
||||
create(:project).tap do |project|
|
||||
create(:project_hook, project: project, milestone_events: true)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'closes the milestone', with_project_hooks: true
|
||||
end
|
||||
|
||||
context 'when project has no active milestone hooks' do
|
||||
it_behaves_like 'closes the milestone', with_project_hooks: false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when milestone fails to close' do
|
||||
context 'when milestone is already closed' do
|
||||
let(:milestone) { create(:milestone, :closed, project: project) }
|
||||
|
||||
it_behaves_like 'does not close the milestone'
|
||||
end
|
||||
|
||||
context 'when milestone is a group milestone' do
|
||||
let(:group) { create(:group) }
|
||||
let(:milestone) { create(:milestone, group: group) }
|
||||
|
||||
it_behaves_like 'does not close the milestone'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,6 +29,29 @@ RSpec.describe Milestones::CreateService, feature_category: :team_planning do
|
|||
expect(milestone.title).to eq('New Milestone')
|
||||
expect(milestone.description).to eq('Description')
|
||||
end
|
||||
|
||||
shared_examples 'creates the milestone' do |with_project_hooks:|
|
||||
it 'executes hooks with create action and creates new event' do
|
||||
expect(create_milestone).to receive(:execute_hooks).with(kind_of(Milestone), 'create').and_call_original
|
||||
expect(project).to receive(:execute_hooks).with(kind_of(Hash), :milestone_hooks) if with_project_hooks
|
||||
|
||||
expect { create_milestone.execute }.to change { Event.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project has active milestone hooks' do
|
||||
let(:project) do
|
||||
create(:project).tap do |project|
|
||||
create(:project_hook, project: project, milestone_events: true)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'creates the milestone', with_project_hooks: true
|
||||
end
|
||||
|
||||
context 'when project has no active milestone hooks' do
|
||||
it_behaves_like 'creates the milestone', with_project_hooks: false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when milestone fails to save' do
|
||||
|
|
@ -48,6 +71,12 @@ RSpec.describe Milestones::CreateService, feature_category: :team_planning do
|
|||
create_milestone.execute
|
||||
end
|
||||
|
||||
it 'does not execute hooks and does not create new event' do
|
||||
expect(create_milestone).not_to receive(:execute_hooks)
|
||||
|
||||
expect { create_milestone.execute }.not_to change { Event.count }
|
||||
end
|
||||
|
||||
it 'returns the unsaved milestone' do
|
||||
milestone = create_milestone.execute
|
||||
expect(milestone).to be_a(Milestone)
|
||||
|
|
@ -56,6 +85,21 @@ RSpec.describe Milestones::CreateService, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when milestone is a group milestone' do
|
||||
let(:group) { create(:group) }
|
||||
let(:group_service) { described_class.new(group, user, params) }
|
||||
|
||||
it 'does not execute hooks for group milestones' do
|
||||
milestone = build(:milestone, group: group)
|
||||
allow(milestone).to receive(:save).and_return(true)
|
||||
allow(group_service).to receive(:build_milestone).and_return(milestone)
|
||||
|
||||
expect(group).not_to receive(:execute_hooks)
|
||||
|
||||
group_service.execute
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls before_create method' do
|
||||
expect(create_milestone).to receive(:before_create)
|
||||
create_milestone.execute
|
||||
|
|
|
|||
|
|
@ -48,7 +48,9 @@ RSpec.describe Milestones::DestroyService, feature_category: :team_planning do
|
|||
|
||||
it_behaves_like 'deletes milestone id from issuables'
|
||||
|
||||
it 'logs destroy event' do
|
||||
it 'logs destroy event and runs on-delete webhook' do
|
||||
expect(service).to receive(:execute_hooks).with(milestone, 'delete')
|
||||
|
||||
service.execute(milestone)
|
||||
|
||||
event = Event.where(project_id: milestone.project_id, target_type: 'Milestone')
|
||||
|
|
@ -84,7 +86,9 @@ RSpec.describe Milestones::DestroyService, feature_category: :team_planning do
|
|||
|
||||
it_behaves_like 'deletes milestone id from issuables'
|
||||
|
||||
it 'does not log destroy event' do
|
||||
it 'does not log destroy event and does not run on-delete webhook' do
|
||||
expect(service).not_to receive(:execute_hooks).with(milestone, 'delete')
|
||||
|
||||
expect { service.execute(milestone) }.not_to change { Event.count }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Milestones::ReopenService, feature_category: :team_planning do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:milestone) { create(:milestone, :closed, title: "Milestone v1.2", project: project) }
|
||||
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:service) { described_class.new(project, user, {}) }
|
||||
|
||||
context 'when service is called before test suite' do
|
||||
before do
|
||||
service.execute(milestone)
|
||||
end
|
||||
|
||||
it { expect(milestone).to be_valid }
|
||||
it { expect(milestone).to be_active }
|
||||
|
||||
describe 'event' do
|
||||
let(:event) { Event.recent.first }
|
||||
|
||||
it { expect(event.milestone).to be_truthy }
|
||||
it { expect(event.target).to eq(milestone) }
|
||||
it { expect(event.action_name).to eq('opened') }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'reopens the milestone' do |with_project_hooks:|
|
||||
it 'executes hooks with reopen action and creates new event' do
|
||||
expect(service).to receive(:execute_hooks).with(milestone, 'reopen').and_call_original
|
||||
expect(project).to receive(:execute_hooks).with(kind_of(Hash), :milestone_hooks) if with_project_hooks
|
||||
|
||||
expect { service.execute(milestone) }.to change { Event.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'does not reopen the milestone' do
|
||||
it 'does not execute hooks and does not create new event' do
|
||||
expect(service).not_to receive(:execute_hooks)
|
||||
|
||||
expect { service.execute(milestone) }.not_to change { Event.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when milestone is successfully reopened' do
|
||||
let(:milestone) { create(:milestone, :closed, project: project) }
|
||||
|
||||
context 'when project has active milestone hooks' do
|
||||
let(:project) do
|
||||
create(:project).tap do |project|
|
||||
create(:project_hook, project: project, milestone_events: true)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'reopens the milestone', with_project_hooks: true
|
||||
end
|
||||
|
||||
context 'when project has no active milestone hooks' do
|
||||
it_behaves_like 'reopens the milestone', with_project_hooks: false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when milestone fails to reopen' do
|
||||
context 'when milestone is already active' do
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
it_behaves_like 'does not reopen the milestone'
|
||||
end
|
||||
|
||||
context 'when milestone is a group milestone' do
|
||||
let(:group) { create(:group) }
|
||||
let(:milestone) { create(:milestone, :closed, group: group) }
|
||||
|
||||
it_behaves_like 'does not reopen the milestone'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -211,6 +211,18 @@ RSpec.describe TestHooks::ProjectService, feature_category: :code_testing do
|
|||
end
|
||||
end
|
||||
|
||||
context 'milestone_events' do
|
||||
let(:trigger) { 'milestone_events' }
|
||||
let(:trigger_key) { :milestone_hooks }
|
||||
|
||||
it 'executes hook' do
|
||||
allow(Gitlab::DataBuilder::Milestone).to receive(:build_sample).and_return(sample_data)
|
||||
|
||||
expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
|
||||
expect(service.execute).to include(success_result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'emoji' do
|
||||
let(:trigger) { 'emoji_events' }
|
||||
let(:trigger_key) { :emoji_hooks }
|
||||
|
|
|
|||
|
|
@ -223,6 +223,8 @@ RSpec.configure do |config|
|
|||
|
||||
config.include_context 'when rendered has no HTML escapes', type: :view
|
||||
config.include_context 'with STI disabled', type: :model
|
||||
# Validate JSONB columns only in EE to avoid false positives in FOSS.
|
||||
config.include_context 'with JSONB validated columns', type: :model if Gitlab.ee?
|
||||
|
||||
include StubCurrentOrganization
|
||||
include StubFeatureFlags
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'yaml'
|
||||
|
||||
module Support
|
||||
module JsonbColumnValidation
|
||||
TODO_YAML = File.join(__dir__, 'jsonb_column_validation_todo.yml')
|
||||
|
||||
module_function
|
||||
|
||||
def todo?(model, column)
|
||||
@todo ||= YAML.load_file(TODO_YAML).to_set # rubocop:disable Gitlab/PredicateMemoization -- @todo is never `nil` or `false`.
|
||||
@todo.include?("#{model.name}##{column}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether JSONB columns are validated via JsonSchemaValidator.
|
||||
#
|
||||
# These checks are skipped in FOSS specs because they produce false positives,
|
||||
# as the models are missing their EE extensions, including their validations.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195456#note_2601197778
|
||||
#
|
||||
# See https://docs.gitlab.com/development/migration_style_guide/#storing-json-in-database
|
||||
#
|
||||
# Parameter:
|
||||
# - model: Model class
|
||||
# - jsonb_column: List of JSONB columns
|
||||
RSpec.shared_examples 'Model validates JSONB columns' do |model, jsonb_columns|
|
||||
jsonb_columns.each do |column|
|
||||
context "with JSONB column #{column}" do
|
||||
let(:json_schema_validator) do
|
||||
model.validators_on(column).find { |validator| validator.is_a?(JsonSchemaValidator) }
|
||||
end
|
||||
|
||||
it 'validates via JsonSchemaValidator' do
|
||||
pending 'Still a TODO' if Support::JsonbColumnValidation.todo?(model, column)
|
||||
|
||||
docs_reference = 'See https://docs.gitlab.com/development/migration_style_guide/#storing-json-in-database.'
|
||||
expect(json_schema_validator).to be_present,
|
||||
"This JSONB column is missing schema validation. #{docs_reference}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_context 'with JSONB validated columns' do # rubocop:disable RSpec/SharedContext -- We cannot include `shared_examples` conditionally based on `type: :model`
|
||||
model = described_class
|
||||
jsonb_columns = \
|
||||
model &&
|
||||
model < ApplicationRecord &&
|
||||
model.name && # skip unnamed/anonymous models
|
||||
!model.abstract_class? &&
|
||||
!model.table_name&.start_with?('_test') && # skip test models that define the tables in specs
|
||||
model.columns.select { |c| c.type == :jsonb }.map(&:name).map(&:to_sym)
|
||||
|
||||
if jsonb_columns && jsonb_columns.any?
|
||||
include_examples 'Model validates JSONB columns', described_class, jsonb_columns
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# TODO list for `spec/support/shared_examples/models/jsonb_column_validation_shared_example.rb`
|
||||
# Ideally, this list should only shrink and never grow.
|
||||
- Ai::ActiveContext::Connection#options
|
||||
- Ai::ActiveContext::Migration#metadata
|
||||
- Ai::Conversation::Message#error_details
|
||||
- Ai::DuoWorkflows::Checkpoint#checkpoint
|
||||
- Ai::DuoWorkflows::Checkpoint#metadata
|
||||
- AlertManagement::Alert#payload
|
||||
- AlertManagement::HttpIntegration#payload_example
|
||||
- ApplicationSetting#ci_cd_settings
|
||||
- ApplicationSetting#oauth_provider
|
||||
- ApplicationSetting#rate_limits_unauthenticated_git_http
|
||||
- ApplicationSetting#repository_storages_weighted
|
||||
- ApplicationSetting#tmp_asset_proxy_secret_key
|
||||
- ApplicationSetting#token_prefixes
|
||||
- Ci::BuildMetadata#config_options
|
||||
- Ci::BuildMetadata#config_variables
|
||||
- Ci::PipelineScheduleInput#value
|
||||
- CloudConnector::Keys#secret_key
|
||||
- DependencyProxy::GroupSetting#identity
|
||||
- DependencyProxy::GroupSetting#secret
|
||||
- GeoNodeStatus#status
|
||||
- Geo::Event#payload
|
||||
- Geo::SecondaryUsageData#payload
|
||||
- GitlabSubscriptions::UserAddOnAssignmentVersion#object
|
||||
- MergeRequest::CommitsMetadata#trailers
|
||||
- Operations::FeatureFlags::Strategy#parameters
|
||||
- Packages::Composer::Metadatum#composer_json
|
||||
- RawUsageData#payload
|
||||
- Releases::Evidence#summary
|
||||
- RemoteDevelopment::WorkspaceAgentkState#desired_config
|
||||
- RemoteDevelopment::WorkspacesAgentConfig#annotations
|
||||
- RemoteDevelopment::WorkspacesAgentConfig#labels
|
||||
- RemoteDevelopment::WorkspacesAgentConfigVersion#object
|
||||
- RemoteDevelopment::WorkspacesAgentConfigVersion#object_changes
|
||||
- Sbom::Occurrence#ancestors
|
||||
- Security::ApprovalPolicyRule#content
|
||||
- Security::Policy#metadata
|
||||
- Security::ScanExecutionPolicyRule#content
|
||||
- Security::VulnerabilityManagementPolicyRule#content
|
||||
- ServicePing::NonSqlServicePing#metadata
|
||||
- ServicePing::NonSqlServicePing#payload
|
||||
- ServicePing::QueriesServicePing#payload
|
||||
- VirtualRegistries::Packages::Maven::Upstream#password
|
||||
- VirtualRegistries::Packages::Maven::Upstream#username
|
||||
- Vulnerabilities::Finding::Evidence#data
|
||||
- Vulnerabilities::Finding#location
|
||||
Loading…
Reference in New Issue