Commit 2adb5457 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch '239036-improve-service-model' into 'master'

Improve Service model

Closes #239036

See merge request gitlab-org/gitlab!40601
parents 2a65277f 2474de7b
......@@ -26,16 +26,17 @@ class Service < ApplicationRecord
default_value_for :active, false
default_value_for :alert_events, true
default_value_for :push_events, true
default_value_for :issues_events, true
default_value_for :confidential_issues_events, true
default_value_for :category, 'common'
default_value_for :commit_events, true
default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true
default_value_for :note_events, true
default_value_for :confidential_issues_events, true
default_value_for :confidential_note_events, true
default_value_for :issues_events, true
default_value_for :job_events, true
default_value_for :merge_requests_events, true
default_value_for :note_events, true
default_value_for :pipeline_events, true
default_value_for :push_events, true
default_value_for :tag_push_events, true
default_value_for :wiki_page_events, true
after_initialize :initialize_properties
......@@ -81,7 +82,163 @@ class Service < ApplicationRecord
scope :alert_hooks, -> { where(alert_events: true, active: true) }
scope :deployment, -> { where(category: 'deployment') }
default_value_for :category, 'common'
# Provide convenient accessor methods for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args)
args.each do |arg|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
unless method_defined?(arg)
def #{arg}
properties['#{arg}']
end
end
def #{arg}=(value)
self.properties ||= {}
updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value
end
def #{arg}_changed?
#{arg}_touched? && #{arg} != #{arg}_was
end
def #{arg}_touched?
updated_properties.include?('#{arg}')
end
def #{arg}_was
updated_properties['#{arg}']
end
RUBY
end
end
# Provide convenient boolean accessor methods for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.boolean_accessor(*args)
self.prop_accessor(*args)
args.each do |arg|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{arg}?
# '!!' is used because nil or empty string is converted to nil
!!ActiveRecord::Type::Boolean.new.cast(#{arg})
end
RUBY
end
end
def self.to_param
raise NotImplementedError
end
def self.event_names
self.supported_events.map { |event| ServicesHelper.service_event_field_name(event) }
end
def self.supported_event_actions
%w[]
end
def self.supported_events
%w[commit push tag_push issue confidential_issue merge_request wiki_page]
end
def self.event_description(event)
ServicesHelper.service_event_description(event)
end
def self.find_or_create_templates
create_nonexistent_templates
for_template
end
def self.create_nonexistent_templates
nonexistent_services = list_nonexistent_services_for(for_template)
return if nonexistent_services.empty?
# Create within a transaction to perform the lowest possible SQL queries.
transaction do
nonexistent_services.each do |service_type|
service_type.constantize.create(template: true)
end
end
end
private_class_method :create_nonexistent_templates
def self.find_or_initialize_integration(name, instance: false, group_id: nil)
if name.in?(available_services_names)
"#{name}_service".camelize.constantize.find_or_initialize_by(instance: instance, group_id: group_id)
end
end
def self.find_or_initialize_all(scope)
scope + build_nonexistent_services_for(scope)
end
def self.build_nonexistent_services_for(scope)
list_nonexistent_services_for(scope).map do |service_type|
service_type.constantize.new
end
end
private_class_method :build_nonexistent_services_for
def self.list_nonexistent_services_for(scope)
# Using #map instead of #pluck to save one query count. This is because
# ActiveRecord loaded the object here, so we don't need to query again later.
available_services_types - scope.map(&:type)
end
private_class_method :list_nonexistent_services_for
def self.available_services_names
service_names = services_names
service_names += dev_services_names
service_names.sort_by(&:downcase)
end
def self.services_names
SERVICE_NAMES
end
def self.dev_services_names
return [] unless Rails.env.development?
DEV_SERVICE_NAMES
end
def self.available_services_types
available_services_names.map { |service_name| "#{service_name}_service".camelize }
end
def self.services_types
services_names.map { |service_name| "#{service_name}_service".camelize }
end
def self.build_from_integration(project_id, integration)
service = integration.dup
if integration.supports_data_fields?
data_fields = integration.data_fields.dup
data_fields.service = service
end
service.template = false
service.instance = false
service.inherit_from_id = integration.id if integration.instance?
service.project_id = project_id
service.active = false if service.invalid?
service
end
def self.instance_exists_for?(type)
exists?(instance: true, type: type)
end
def self.instance_for(type)
find_by(instance: true, type: type)
end
def activated?
active
......@@ -124,10 +281,6 @@ class Service < ApplicationRecord
self.class.to_param
end
def self.to_param
raise NotImplementedError
end
def fields
# implement inside child
[]
......@@ -137,7 +290,7 @@ class Service < ApplicationRecord
#
# This list is used in `Service#as_json(only: json_fields)`.
def json_fields
%w(active)
%w[active]
end
def to_service_hash
......@@ -156,10 +309,6 @@ class Service < ApplicationRecord
self.class.event_names
end
def self.event_names
self.supported_events.map { |event| ServicesHelper.service_event_field_name(event) }
end
def event_field(event)
nil
end
......@@ -188,18 +337,10 @@ class Service < ApplicationRecord
self.class.supported_event_actions
end
def self.supported_event_actions
%w()
end
def supported_events
self.class.supported_events
end
def self.supported_events
%w(commit push tag_push issue confidential_issue merge_request wiki_page)
end
def execute(data)
# implement inside child
end
......@@ -216,55 +357,6 @@ class Service < ApplicationRecord
!instance?
end
# Provide convenient accessor methods
# for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args)
args.each do |arg|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
unless method_defined?(arg)
def #{arg}
properties['#{arg}']
end
end
def #{arg}=(value)
self.properties ||= {}
updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value
end
def #{arg}_changed?
#{arg}_touched? && #{arg} != #{arg}_was
end
def #{arg}_touched?
updated_properties.include?('#{arg}')
end
def #{arg}_was
updated_properties['#{arg}']
end
RUBY
end
end
# Provide convenient boolean accessor methods
# for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.boolean_accessor(*args)
self.prop_accessor(*args)
args.each do |arg|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{arg}?
# '!!' is used because nil or empty string is converted to nil
!!ActiveRecord::Type::Boolean.new.cast(#{arg})
end
RUBY
end
end
# Returns a hash of the properties that have been assigned a new value since last save,
# indicating their original values (attr => original value).
# ActiveRecord does not provide a mechanism to track changes in serialized keys,
......@@ -289,92 +381,6 @@ class Service < ApplicationRecord
self.category == :issue_tracker
end
def self.find_or_create_templates
create_nonexistent_templates
for_template
end
private_class_method def self.create_nonexistent_templates
nonexistent_services = list_nonexistent_services_for(for_template)
return if nonexistent_services.empty?
# Create within a transaction to perform the lowest possible SQL queries.
transaction do
nonexistent_services.each do |service_type|
service_type.constantize.create(template: true)
end
end
end
def self.find_or_initialize_integration(name, instance: false, group_id: nil)
if name.in?(available_services_names)
"#{name}_service".camelize.constantize.find_or_initialize_by(instance: instance, group_id: group_id)
end
end
def self.find_or_initialize_all(scope)
scope + build_nonexistent_services_for(scope)
end
private_class_method def self.build_nonexistent_services_for(scope)
list_nonexistent_services_for(scope).map do |service_type|
service_type.constantize.new
end
end
private_class_method def self.list_nonexistent_services_for(scope)
available_services_types - scope.map(&:type)
end
def self.available_services_names
service_names = services_names
service_names += dev_services_names
service_names.sort_by(&:downcase)
end
def self.services_names
SERVICE_NAMES
end
def self.dev_services_names
return [] unless Rails.env.development?
DEV_SERVICE_NAMES
end
def self.available_services_types
available_services_names.map { |service_name| "#{service_name}_service".camelize }
end
def self.services_types
services_names.map { |service_name| "#{service_name}_service".camelize }
end
def self.build_from_integration(project_id, integration)
service = integration.dup
if integration.supports_data_fields?
data_fields = integration.data_fields.dup
data_fields.service = service
end
service.template = false
service.instance = false
service.inherit_from_id = integration.id if integration.instance?
service.project_id = project_id
service.active = false if service.invalid?
service
end
def self.instance_exists_for?(type)
exists?(instance: true, type: type)
end
def self.instance_for(type)
find_by(instance: true, type: type)
end
# override if needed
def supports_data_fields?
false
......@@ -402,10 +408,6 @@ class Service < ApplicationRecord
end
end
def self.event_description(event)
ServicesHelper.service_event_description(event)
end
def valid_recipients?
activated? && !importing?
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment