Class: Integration
Overview
To add new integration you should build a class inherited from Integration and implement a set of methods
Direct Known Subclasses
Integrations::AppleAppStore, Integrations::Asana, Integrations::Assembla, Integrations::BaseChatNotification, Integrations::BaseCi, Integrations::BaseIssueTracker, Integrations::BaseMonitoring, Integrations::BaseSlashCommands, Integrations::BaseThirdPartyWiki, Integrations::Campfire, Integrations::Datadog, Integrations::EmailsOnPush, Integrations::ExternalWiki, Integrations::GooglePlay, Integrations::Harbor, Integrations::Irker, Integrations::Packagist, Integrations::PipelinesEmail, Integrations::Pivotaltracker, Integrations::Pushover, Integrations::SquashTm
Constant Summary
collapse
- UnknownType =
Class.new(StandardError)
- INTEGRATION_NAMES =
%w[
asana assembla bamboo bugzilla buildkite campfire clickup confluence custom_issue_tracker datadog discord
drone_ci emails_on_push ewm external_wiki hangouts_chat harbor irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands squash_tm teamcity telegram
unify_circuit webex_teams youtrack zentao
].freeze
- PROJECT_SPECIFIC_INTEGRATION_NAMES =
%w[
apple_app_store gitlab_slack_application google_play jenkins shimo
].freeze
- DEV_INTEGRATION_NAMES =
Fake integrations to help with local development.
%w[
mock_ci mock_monitoring
].freeze
- BASE_CLASSES =
Base classes which aren’t actual integrations.
%w[
Integrations::BaseChatNotification
Integrations::BaseCi
Integrations::BaseIssueTracker
Integrations::BaseMonitoring
Integrations::BaseSlackNotification
Integrations::BaseSlashCommands
Integrations::BaseThirdPartyWiki
].freeze
- SECTION_TYPE_CONFIGURATION =
'configuration'
- SECTION_TYPE_CONNECTION =
'connection'
- SECTION_TYPE_TRIGGER =
'trigger'
- SNOWPLOW_EVENT_ACTION =
'perform_integrations_action'
- SNOWPLOW_EVENT_LABEL =
'redis_hll_counters.ecosystem.ecosystem_total_unique_counts_monthly'
ApplicationRecord::MAX_PLUCK
ResetOnUnionError::MAX_RESET_PERIOD
Instance Attribute Summary
Attributes included from Importable
#imported, #importing
Class Method Summary
collapse
-
.available_integration_names(include_project_specific: true, include_dev: true) ⇒ Object
Returns a list of available integration names.
-
.available_integration_types(include_project_specific: true, include_dev: true) ⇒ Object
Returns a list of available integration types.
-
.boolean_accessor(*args) ⇒ Object
Provide convenient boolean accessor methods for each serialized property.
-
.build_from_integration(integration, project_id: nil, group_id: nil) ⇒ Object
-
.create_from_active_default_integrations(owner, association) ⇒ Object
Returns the number of successfully saved integrations Duplicate integrations are excluded from this count by their validations.
-
.default_integration(type, scope) ⇒ Object
-
.default_test_event ⇒ Object
-
.dev_integration_names ⇒ Object
-
.event_description(event) ⇒ Object
-
.event_names ⇒ Object
-
.field(name, storage: field_storage, **attrs) ⇒ Object
:nocov: Tested on subclasses.
-
.fields ⇒ Object
-
.find_or_initialize_all_non_project_specific(scope) ⇒ Object
-
.find_or_initialize_non_project_specific_integration(name, instance: false, group_id: nil) ⇒ Object
-
.inherited_descendants_from_self_or_ancestors_from(integration) ⇒ Object
-
.instance_exists_for?(type) ⇒ Boolean
-
.integration_name_to_model(name) ⇒ Object
Returns the model for the given integration name.
-
.integration_name_to_type(name) ⇒ Object
Returns the STI type for the given integration name.
-
.integration_names ⇒ Object
-
.project_specific_integration_names ⇒ Object
-
.prop_accessor(*args) ⇒ Object
Provide convenient accessor methods for each serialized property.
-
.supported_events ⇒ Object
-
.to_param ⇒ Object
Instance Method Summary
collapse
extended, extensions, included, method_added, override, prepended, queue_verification, verify!
#exposing_secrets_fields
#build_message, #log_error, #log_exception, #log_info, #logger
cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order
#serializable_hash
Class Method Details
.available_integration_names(include_project_specific: true, include_dev: true) ⇒ Object
Returns a list of available integration names. Example: [“asana”, …]
291
292
293
294
295
296
297
|
# File 'app/models/integration.rb', line 291
def self.available_integration_names(include_project_specific: true, include_dev: true)
names = integration_names
names += project_specific_integration_names if include_project_specific
names += dev_integration_names if include_dev
names.sort_by(&:downcase)
end
|
.available_integration_types(include_project_specific: true, include_dev: true) ⇒ Object
Returns a list of available integration types. Example: [“Integrations::Asana”, …]
317
318
319
320
321
|
# File 'app/models/integration.rb', line 317
def self.available_integration_types(include_project_specific: true, include_dev: true)
available_integration_names(include_project_specific: include_project_specific, include_dev: include_dev).map do
integration_name_to_type(_1)
end
end
|
.boolean_accessor(*args) ⇒ Object
Provide convenient boolean accessor methods for each serialized property. Also keep track of updated properties in a similar way as ActiveModel::Dirty
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
|
# File 'app/models/integration.rb', line 217
def self.boolean_accessor(*args)
args.each do |arg|
prop_accessor(arg) unless method_defined?(arg)
class_eval <<~RUBY, __FILE__, __LINE__ + 1
# Make the original getter available as a private method.
alias_method :#{arg}_before_type_cast, :#{arg}
private(:#{arg}_before_type_cast)
def #{arg}
Gitlab::Utils.to_boolean(#{arg}_before_type_cast)
end
def #{arg}?
# '!!' is used because nil or empty string is converted to nil
!!#{arg}
end
RUBY
end
end
|
.build_from_integration(integration, project_id: nil, group_id: nil) ⇒ Object
348
349
350
351
352
353
354
355
356
|
# File 'app/models/integration.rb', line 348
def self.build_from_integration(integration, project_id: nil, group_id: nil)
new_integration = integration.dup
new_integration.instance = false
new_integration.project_id = project_id
new_integration.group_id = group_id
new_integration.inherit_from_id = integration.id if integration.inheritable?
new_integration
end
|
.create_from_active_default_integrations(owner, association) ⇒ Object
Returns the number of successfully saved integrations Duplicate integrations are excluded from this count by their validations.
401
402
403
404
405
406
407
408
409
410
|
# File 'app/models/integration.rb', line 401
def self.create_from_active_default_integrations(owner, association)
group_ids = sorted_ancestors(owner).select(:id)
array = group_ids.to_sql.present? ? "array(#{group_ids.to_sql})" : 'ARRAY[]'
order = Arel.sql("type_new ASC, array_position(#{array}::bigint[], #{table_name}.group_id), instance DESC")
from_union([active.where(instance: true), active.where(group_id: group_ids, inherit_from_id: nil)])
.order(order)
.group_by(&:type)
.count { |type, parents| build_from_integration(parents.first, association => owner.id).save }
end
|
.default_integration(type, scope) ⇒ Object
380
381
382
|
# File 'app/models/integration.rb', line 380
def self.default_integration(type, scope)
closest_group_integration(type, scope) || instance_level_integration(type)
end
|
.default_test_event ⇒ Object
255
256
257
|
# File 'app/models/integration.rb', line 255
def self.default_test_event
'push'
end
|
.dev_integration_names ⇒ Object
.event_description(event) ⇒ Object
.field(name, storage: field_storage, **attrs) ⇒ Object
:nocov: Tested on subclasses.
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
# File 'app/models/integration.rb', line 157
def self.field(name, storage: field_storage, **attrs)
fields << ::Integrations::Field.new(name: name, integration_class: self, **attrs)
case storage
when :attribute
when :properties
prop_accessor(name)
when :data_fields
data_field(name)
else
raise ArgumentError, "Unknown field storage: #{storage}"
end
boolean_accessor(name) if attrs[:type] == :checkbox && storage != :attribute
end
|
.fields ⇒ Object
175
176
177
|
# File 'app/models/integration.rb', line 175
def self.fields
@fields ||= []
end
|
.find_or_initialize_all_non_project_specific(scope) ⇒ Object
269
270
271
|
# File 'app/models/integration.rb', line 269
def self.find_or_initialize_all_non_project_specific(scope)
scope + build_nonexistent_integrations_for(scope)
end
|
.find_or_initialize_non_project_specific_integration(name, instance: false, group_id: nil) ⇒ Object
263
264
265
266
267
|
# File 'app/models/integration.rb', line 263
def self.find_or_initialize_non_project_specific_integration(name, instance: false, group_id: nil)
return unless name.in?(available_integration_names(include_project_specific: false))
integration_name_to_model(name).find_or_initialize_by(instance: instance, group_id: group_id)
end
|
.inherited_descendants_from_self_or_ancestors_from(integration) ⇒ Object
412
413
414
415
416
417
418
419
420
421
|
# File 'app/models/integration.rb', line 412
def self.inherited_descendants_from_self_or_ancestors_from(integration)
inherit_from_ids =
where(type: integration.type, group: integration.group.self_and_ancestors)
.or(where(type: integration.type, instance: true)).select(:id)
from_union([
where(type: integration.type, inherit_from_id: inherit_from_ids, group: integration.group.descendants),
where(type: integration.type, inherit_from_id: inherit_from_ids, project: Project.in_namespace(integration.group.self_and_descendants))
])
end
|
.instance_exists_for?(type) ⇒ Boolean
376
377
378
|
# File 'app/models/integration.rb', line 376
def self.instance_exists_for?(type)
exists?(instance: true, type: type)
end
|
.integration_name_to_model(name) ⇒ Object
Returns the model for the given integration name. Example: :asana => Integrations::Asana
325
326
327
328
|
# File 'app/models/integration.rb', line 325
def self.integration_name_to_model(name)
type = integration_name_to_type(name)
integration_type_to_model(type)
end
|
.integration_name_to_type(name) ⇒ Object
Returns the STI type for the given integration name. Example: “asana” => “Integrations::Asana”
.integration_names ⇒ Object
299
300
301
|
# File 'app/models/integration.rb', line 299
def self.integration_names
INTEGRATION_NAMES
end
|
.project_specific_integration_names ⇒ Object
.prop_accessor(*args) ⇒ Object
Provide convenient accessor methods for each serialized property. Also keep track of updated properties in a similar way as ActiveModel::Dirty
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
# File 'app/models/integration.rb', line 185
def self.prop_accessor(*args)
args.each do |arg|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
unless method_defined?(arg)
def #{arg}
properties['#{arg}'] if properties.present?
end
end
def #{arg}=(value)
self.properties ||= {}
updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties = self.properties.merge('#{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
|
.supported_events ⇒ Object
251
252
253
|
# File 'app/models/integration.rb', line 251
def self.supported_events
%w[commit push tag_push issue confidential_issue merge_request wiki_page]
end
|
.to_param ⇒ Object
243
244
245
|
# File 'app/models/integration.rb', line 243
def self.to_param
raise NotImplementedError
end
|
Instance Method Details
#activate_disabled_reason ⇒ Object
439
440
441
|
# File 'app/models/integration.rb', line 439
def activate_disabled_reason
nil
end
|
#activated? ⇒ Boolean
423
424
425
|
# File 'app/models/integration.rb', line 423
def activated?
active
end
|
#api_field_names ⇒ Object
517
518
519
|
# File 'app/models/integration.rb', line 517
def api_field_names
fields.reject { _1[:type] == :password || _1[:name] == 'webhook' || (_1.key?(:if) && _1[:if] != true) }.pluck(:name)
end
|
#async_execute(data) ⇒ Object
590
591
592
593
594
595
|
# File 'app/models/integration.rb', line 590
def async_execute(data)
return if ::Gitlab::SilentMode.enabled?
return unless supported_events.include?(data[:object_kind])
Integrations::ExecuteWorker.perform_async(id, data)
end
|
#attributes ⇒ Object
485
486
487
|
# File 'app/models/integration.rb', line 485
def attributes
super.except('properties')
end
|
#category ⇒ Object
443
444
445
|
# File 'app/models/integration.rb', line 443
def category
read_attribute(:category).to_sym
end
|
#chat? ⇒ Boolean
602
603
604
|
# File 'app/models/integration.rb', line 602
def chat?
category == :chat
end
|
#ci? ⇒ Boolean
606
607
608
|
# File 'app/models/integration.rb', line 606
def ci?
category == :ci
end
|
#configurable_events ⇒ Object
525
526
527
528
529
530
531
532
533
534
|
# File 'app/models/integration.rb', line 525
def configurable_events
events = supported_events
if events.count == 1
[]
else
events
end
end
|
#default_test_event ⇒ Object
540
541
542
|
# File 'app/models/integration.rb', line 540
def default_test_event
self.class.default_test_event
end
|
#description ⇒ Object
455
456
457
|
# File 'app/models/integration.rb', line 455
def description
end
|
#dup ⇒ Object
360
361
362
363
364
365
366
367
368
369
370
|
# File 'app/models/integration.rb', line 360
def dup
new_integration = super
new_integration.assign_attributes(reencrypt_properties)
if supports_data_fields?
fields = data_fields.dup
fields.integration = new_integration
end
new_integration
end
|
#editable? ⇒ Boolean
435
436
437
|
# File 'app/models/integration.rb', line 435
def editable?
true
end
|
#event_channel_names ⇒ Object
509
510
511
|
# File 'app/models/integration.rb', line 509
def event_channel_names
[]
end
|
#event_names ⇒ Object
513
514
515
|
# File 'app/models/integration.rb', line 513
def event_names
self.class.event_names
end
|
#execute(data) ⇒ Object
544
545
546
|
# File 'app/models/integration.rb', line 544
def execute(data)
end
|
#fields ⇒ Object
179
180
181
|
# File 'app/models/integration.rb', line 179
def fields
self.class.fields.dup
end
|
521
522
523
|
# File 'app/models/integration.rb', line 521
def form_fields
fields.reject { _1[:api_only] == true || (_1.key?(:if) && _1[:if] != true) }
end
|
#group_level? ⇒ Boolean
564
565
566
|
# File 'app/models/integration.rb', line 564
def group_level?
group_id.present?
end
|
#help ⇒ Object
459
460
461
|
# File 'app/models/integration.rb', line 459
def help
end
|
#inheritable? ⇒ Boolean
372
373
374
|
# File 'app/models/integration.rb', line 372
def inheritable?
instance_level? || group_level?
end
|
#initialize_properties ⇒ Object
447
448
449
|
# File 'app/models/integration.rb', line 447
def initialize_properties
self.properties = {} if has_attribute?(:encrypted_properties) && encrypted_properties.nil?
end
|
#instance_level? ⇒ Boolean
568
569
570
|
# File 'app/models/integration.rb', line 568
def instance_level?
instance?
end
|
#json_fields ⇒ Object
Expose a list of fields in the JSON endpoint.
This list is used in ‘Integration#as_json(only: json_fields)`.
479
480
481
|
# File 'app/models/integration.rb', line 479
def json_fields
%w[active]
end
|
#operating? ⇒ Boolean
427
428
429
|
# File 'app/models/integration.rb', line 427
def operating?
active && persisted?
end
|
#parent ⇒ Object
572
573
574
|
# File 'app/models/integration.rb', line 572
def parent
project || group
end
|
#project_level? ⇒ Boolean
560
561
562
|
# File 'app/models/integration.rb', line 560
def project_level?
project_id.present?
end
|
#properties=(props) ⇒ Object
72
73
74
|
# File 'app/models/integration.rb', line 72
def properties=(props)
self.attr_encrypted_props = props&.with_indifferent_access&.freeze
end
|
#reencrypt_properties ⇒ Object
499
500
501
502
503
504
505
506
507
|
# File 'app/models/integration.rb', line 499
def reencrypt_properties
unless properties.nil? || properties.empty?
alg = self.class.attr_encrypted_attributes[:properties][:algorithm]
iv = generate_iv(alg)
ep = self.class.attr_encrypt(:properties, properties, { iv: iv })
end
{ 'encrypted_properties' => ep, 'encrypted_properties_iv' => iv }
end
|
#reset_updated_properties ⇒ Object
586
587
588
|
# File 'app/models/integration.rb', line 586
def reset_updated_properties
@updated_properties = nil
end
|
#secret_fields ⇒ Object
472
473
474
|
# File 'app/models/integration.rb', line 472
def secret_fields
fields.select(&:secret?).pluck(:name)
end
|
#sections ⇒ Object
468
469
470
|
# File 'app/models/integration.rb', line 468
def sections
[]
end
|
#show_active_box? ⇒ Boolean
431
432
433
|
# File 'app/models/integration.rb', line 431
def show_active_box?
true
end
|
#supported_events ⇒ Object
536
537
538
|
# File 'app/models/integration.rb', line 536
def supported_events
self.class.supported_events
end
|
#supports_data_fields? ⇒ Boolean
598
599
600
|
# File 'app/models/integration.rb', line 598
def supports_data_fields?
false
end
|
#test(data) ⇒ Object
548
549
550
551
552
|
# File 'app/models/integration.rb', line 548
def test(data)
result = execute(data)
{ success: result.present?, result: result }
end
|
#testable? ⇒ Boolean
556
557
558
|
# File 'app/models/integration.rb', line 556
def testable?
project_level?
end
|
#title ⇒ Object
451
452
453
|
# File 'app/models/integration.rb', line 451
def title
end
|
#to_database_hash ⇒ Object
Returns a hash of attributes (columns => values) used for inserting into the database.
490
491
492
493
494
495
496
497
|
# File 'app/models/integration.rb', line 490
def to_database_hash
column = self.class.attribute_aliases.fetch('type', 'type')
as_json(
except: %w[id instance project_id group_id created_at updated_at]
).merge(column => type)
.merge(reencrypt_properties)
end
|
#to_param ⇒ Object
463
464
465
466
|
# File 'app/models/integration.rb', line 463
def to_param
self.class.to_param
end
|
#updated_properties ⇒ Object
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, so we need a specific implementation for integration properties. This allows to track changes to properties set with the accessor methods, but not direct manipulation of properties hash.
582
583
584
|
# File 'app/models/integration.rb', line 582
def updated_properties
@updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new
end
|