maestrano-connector-rails 0.4.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/README.md +1 -1
  4. data/VERSION +1 -1
  5. data/app/controllers/maestrano/connec_controller.rb +17 -19
  6. data/app/jobs/maestrano/connector/rails/all_synchronizations_job.rb +1 -1
  7. data/app/jobs/maestrano/connector/rails/push_to_connec_job.rb +11 -11
  8. data/app/jobs/maestrano/connector/rails/synchronization_job.rb +20 -11
  9. data/app/models/maestrano/connector/rails/concerns/complex_entity.rb +66 -74
  10. data/app/models/maestrano/connector/rails/concerns/connec_helper.rb +102 -0
  11. data/app/models/maestrano/connector/rails/concerns/entity.rb +233 -231
  12. data/app/models/maestrano/connector/rails/concerns/external.rb +7 -0
  13. data/app/models/maestrano/connector/rails/concerns/sub_entity_base.rb +14 -43
  14. data/app/models/maestrano/connector/rails/connec_helper.rb +5 -0
  15. data/app/models/maestrano/connector/rails/organization.rb +8 -2
  16. data/db/20160524112054_add_encryption_on_oauth_keys.rb +8 -0
  17. data/lib/generators/connector/install_generator.rb +1 -0
  18. data/lib/generators/connector/templates/complex_entity_example/contact.rb +1 -1
  19. data/lib/generators/connector/templates/complex_entity_example/contact_and_lead.rb +2 -2
  20. data/lib/generators/connector/templates/entity.rb +13 -19
  21. data/lib/generators/connector/templates/example_entity.rb +2 -2
  22. data/lib/generators/connector/templates/example_entity_spec.rb +73 -0
  23. data/lib/generators/connector/templates/external.rb +11 -0
  24. data/maestrano-connector-rails.gemspec +13 -8
  25. data/release_notes.md +81 -0
  26. data/spec/controllers/connec_controller_spec.rb +19 -6
  27. data/spec/dummy/app/models/maestrano/connector/rails/entity.rb +0 -7
  28. data/spec/dummy/app/models/maestrano/connector/rails/external.rb +7 -0
  29. data/spec/dummy/app/views/home/index.html.erb +1 -36
  30. data/spec/factories.rb +3 -0
  31. data/spec/integration/connec_to_external_spec.rb +188 -0
  32. data/spec/integration/external_to_connec_spec.rb +155 -0
  33. data/spec/integration/integration_complex_spec.rb +281 -0
  34. data/spec/integration/singleton_spec.rb +288 -0
  35. data/spec/jobs/all_synchronizations_job_spec.rb +5 -0
  36. data/spec/jobs/push_to_connec_job_spec.rb +3 -6
  37. data/spec/jobs/synchronization_job_spec.rb +29 -17
  38. data/spec/models/complex_entity_spec.rb +257 -412
  39. data/spec/models/connec_helper_spec.rb +143 -0
  40. data/spec/models/entity_spec.rb +420 -348
  41. data/spec/models/external_spec.rb +4 -0
  42. data/spec/models/organization_spec.rb +2 -1
  43. data/spec/models/sub_entity_base_spec.rb +28 -69
  44. data/template/factories.rb +3 -1
  45. data/template/maestrano-connector-template.rb +11 -13
  46. data/template/maestrano.rb +2 -1
  47. data/template/settings/development.yml +4 -2
  48. data/template/settings/production.yml +1 -11
  49. data/template/settings/settings.yml +8 -0
  50. data/template/settings/test.yml +2 -0
  51. data/template/settings/uat.yml +1 -9
  52. metadata +12 -7
  53. data/Gemfile.lock +0 -256
  54. data/realse_notes.md +0 -16
  55. data/spec/dummy/app/views/admin/index.html.erb +0 -51
  56. data/spec/dummy/db/development.sqlite3 +0 -0
  57. data/spec/dummy/db/test.sqlite3 +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2b30b01e9635bb23e54086aa72a76209ff6ff90
4
- data.tar.gz: 093c8366dae9dd669fd6989f95e8030c44698d1b
3
+ metadata.gz: 2ee22c8ca44f84ce0e4a717c80892f1765af5987
4
+ data.tar.gz: 65e99c4163462829142e439f8323e8e2cd6f6306
5
5
  SHA512:
6
- metadata.gz: c4e022328533a25167c5c3d37b4c146edf6a9d88bec6d952209a3a52d3b156f7262800bdc7148ba8a4320272a4e39720704adb57529ccb294cfda08e5fd623a1
7
- data.tar.gz: cd8609ffcf02458ea2b97419076ec7e521a28fa9fde702d2ce7c6bd9fecf773c6cc63dc9fb3a708f8e5bfb3f46132984d1ce71b2226715b31172f8c2b5c196cd
6
+ metadata.gz: 5a480181d0599bc16c945364ff6949c684eaa431f89b1c5af2f41f7f8999f451ab48a470abe680a1d854f57284fa6831bd73b3ecaf0b503a2053b34674f3a4b9
7
+ data.tar.gz: 00ec463ca23d1994baaf7873ce4a26c8c3cc76c72809e5a1b06cba7ca54899f67ed5dcc92c97f019ac4fbf6363d42862edf770c579245fb42ce30ae488fbdcc5
data/Gemfile CHANGED
@@ -7,6 +7,8 @@ gem 'sidekiq'
7
7
  gem 'haml-rails'
8
8
  gem 'bootstrap-sass'
9
9
  gem 'autoprefixer-rails'
10
+ # gem 'attr_encrypted', '~> 3.0.0'
11
+ # gem 'config'
10
12
 
11
13
  # Add dependencies to develop your gem here.
12
14
  group :development do
data/README.md CHANGED
@@ -10,4 +10,4 @@
10
10
  Maestrano Connector is a Rails Engine that bootstraps the implementation of data syncrhonization between an external application API and the Maestrano ecosystem.
11
11
  Maestrano Connector Integration is currently in closed beta. Want to know more? Send us an email to <contact@maestrano.com>.
12
12
 
13
- You can find the documentation [here](https://maestrano.atlassian.net/wiki/display/CONNECAPIV2/Maestrano+connector+framework+-+rails). See also some generic documentation about integration with Maestrano [here](https://maestrano.atlassian.net/wiki/display/CONNECAPIV2/Maestrano+Developers).
13
+ You can find the documentation [here](https://maestrano.atlassian.net/wiki/display/DEV/Create+a+connector+using+our+framework+-+Ruby+on+Rails). See also some generic documentation about integration with Maestrano [here](https://maestrano.atlassian.net/wiki/display/DEV/Maestrano+Developers).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.4
1
+ 1.0.0
@@ -6,36 +6,34 @@ class Maestrano::ConnecController < Maestrano::Rails::WebHookController
6
6
  begin
7
7
  params.except(:tenant, :controller, :action, :connec).each do |entity_name, entities|
8
8
 
9
- entity_instance_hash = find_entity_instance(entity_name)
10
- next Rails.logger.info "Received notification from Connec! for unknow entity: #{entity_name}" unless entity_instance_hash
11
-
12
- entity_instance = entity_instance_hash[:instance]
9
+ entity_class_hash = find_entity_class(entity_name)
10
+ next Rails.logger.info "Received notification from Connec! for unknow entity: #{entity_name}" unless entity_class_hash
13
11
 
14
12
  entities.each do |entity|
15
13
  organization = Maestrano::Connector::Rails::Organization.find_by_uid_and_tenant(entity[:group_id], params[:tenant])
16
14
  next Rails.logger.warn "Received notification from Connec! for unknown group or group without oauth: #{entity['group_id']} (tenant: #{params[:tenant]})" unless organization && organization.oauth_uid
17
- next unless organization.sync_enabled && organization.synchronized_entities[entity_instance_hash[:name].to_sym]
15
+ next unless organization.sync_enabled && organization.synchronized_entities[entity_class_hash[:name].to_sym]
18
16
 
19
17
 
20
18
  Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received entity from Connec! webhook: Entity=#{entity_name}, Data=#{entity}")
21
- connec_client = Maestrano::Connec::Client[organization.tenant].new(organization.uid)
19
+ connec_client = Maestrano::Connector::Rails::ConnecHelper.get_client(organization)
22
20
  external_client = Maestrano::Connector::Rails::External.get_client(organization)
23
21
  last_synchronization = organization.last_successful_synchronization
24
22
 
25
- entity_instance.before_sync(connec_client, external_client, last_synchronization, organization, {})
23
+ entity_instance = entity_class_hash[:class].new(organization, connec_client, external_client, {})
24
+ entity_instance.before_sync(last_synchronization)
26
25
 
27
-
28
26
  # Build expected input for consolidate_and_map_data
29
- if entity_instance_hash[:is_complex]
30
- filtered_entities = entity_instance.filter_connec_entities(Hash[ *entity_instance.class.connec_entities_names.collect{|name| name.parameterize('_').pluralize == entity_name ? [name, [entity]] : [ name, []]}.flatten(1) ], organization)
31
- mapped_entity = entity_instance.consolidate_and_map_data(filtered_entities, Hash[ *entity_instance.class.external_entities_names.collect{|name| [ name, []]}.flatten(1) ], organization, {})
27
+ if entity_class_hash[:is_complex]
28
+ filtered_entities = entity_instance.filter_connec_entities(Maestrano::Connector::Rails::ComplexEntity.build_hash_with_entities(entity_instance.class.connec_entities_names, entity_name, lambda{|name| name.parameterize('_').pluralize}, [entity]))
29
+ mapped_entity = entity_instance.consolidate_and_map_data(filtered_entities, Maestrano::Connector::Rails::ComplexEntity.build_empty_hash(entity_instance.class.external_entities_names))
32
30
  else
33
- filtered_entities = entity_instance.filter_connec_entities([entity], organization)
34
- mapped_entity = entity_instance.consolidate_and_map_data(filtered_entities, [], organization, {})
31
+ filtered_entities = entity_instance.filter_connec_entities([entity])
32
+ mapped_entity = entity_instance.consolidate_and_map_data(filtered_entities, [])
35
33
  end
36
- entity_instance.push_entities_to_external(external_client, mapped_entity[:connec_entities], organization)
34
+ entity_instance.push_entities_to_external(mapped_entity[:connec_entities])
37
35
 
38
- entity_instance.after_sync(connec_client, external_client, last_synchronization, organization, {})
36
+ entity_instance.after_sync(last_synchronization)
39
37
  end
40
38
  end
41
39
  rescue => e
@@ -47,13 +45,13 @@ class Maestrano::ConnecController < Maestrano::Rails::WebHookController
47
45
 
48
46
 
49
47
  private
50
- def find_entity_instance(entity_name)
51
- Maestrano::Connector::Rails::Entity.entities_list.each do |entity_name_from_list|
48
+ def find_entity_class(entity_name)
49
+ Maestrano::Connector::Rails::External.entities_list.each do |entity_name_from_list|
52
50
  clazz = "Entities::#{entity_name_from_list.singularize.titleize.split.join}".constantize
53
51
  if clazz.methods.include?('connec_entities_names'.to_sym)
54
- return {instance: clazz.new, is_complex: true, name: entity_name_from_list} if clazz.connec_entities_names.map{|n| n.parameterize('_').pluralize}.include?(entity_name)
52
+ return {class: clazz, is_complex: true, name: entity_name_from_list} if clazz.connec_entities_names.map{|n| n.parameterize('_').pluralize}.include?(entity_name)
55
53
  elsif clazz.methods.include?('connec_entity_name'.to_sym)
56
- return {instance: clazz.new, is_complex: false, name: entity_name_from_list} if clazz.normalized_connec_entity_name == entity_name
54
+ return {class: clazz, is_complex: false, name: entity_name_from_list} if clazz.normalized_connec_entity_name == entity_name
57
55
  end
58
56
  end
59
57
  nil
@@ -4,7 +4,7 @@ module Maestrano::Connector::Rails
4
4
 
5
5
  # Trigger synchronization of all active organizations
6
6
  def perform(name=nil, count=nil)
7
- Maestrano::Connector::Rails::Organization.where("oauth_provider IS NOT NULL AND oauth_token IS NOT NULL").each do |o|
7
+ Maestrano::Connector::Rails::Organization.where('oauth_provider IS NOT NULL AND oauth_token IS NOT NULL').each do |o|
8
8
  next unless [true, 1].include?(o.sync_enabled)
9
9
  Maestrano::Connector::Rails::SynchronizationJob.perform_later(o, {})
10
10
  end
@@ -6,26 +6,26 @@ module Maestrano::Connector::Rails
6
6
  def perform(organization, entities_hash, opts={})
7
7
  return unless organization.sync_enabled && organization.oauth_uid
8
8
 
9
- connec_client = Maestrano::Connec::Client[organization.tenant].new(organization.uid)
9
+ connec_client = Maestrano::Connector::Rails::ConnecHelper.get_client(organization)
10
10
  external_client = Maestrano::Connector::Rails::External.get_client(organization)
11
11
  last_synchronization = organization.last_successful_synchronization
12
12
 
13
13
  entities_hash.each do |external_entity_name, entities|
14
- if entity_instance_hash = find_entity_instance(external_entity_name)
14
+ if entity_instance_hash = find_entity_instance(external_entity_name, organization, connec_client, external_client, opts)
15
15
  next unless organization.synchronized_entities[entity_instance_hash[:name].to_sym]
16
16
 
17
17
  entity_instance = entity_instance_hash[:instance]
18
18
 
19
- entity_instance.before_sync(connec_client, external_client, last_synchronization, organization, opts)
19
+ entity_instance.before_sync(last_synchronization)
20
20
  # Build expected input for consolidate_and_map_data
21
21
  if entity_instance_hash[:is_complex]
22
- mapped_entities = entity_instance.consolidate_and_map_data(Hash[ *entity_instance.class.connec_entities_names.collect{|name| [ name, []]}.flatten(1) ], Hash[ *entity_instance.class.external_entities_names.collect{|name| name == external_entity_name ? [name, entities] : [ name, []]}.flatten(1) ], organization, {})
22
+ mapped_entities = entity_instance.consolidate_and_map_data(ComplexEntity.build_empty_hash(entity_instance.class.connec_entities_names), ComplexEntity.build_hash_with_entities(entity_instance.class.external_entities_names, external_entity_name, lambda{|name| name}, entities))
23
23
  else
24
- mapped_entities = entity_instance.consolidate_and_map_data([], entities, organization, {})
24
+ mapped_entities = entity_instance.consolidate_and_map_data([], entities)
25
25
  end
26
- entity_instance.push_entities_to_connec(connec_client, mapped_entities[:external_entities], organization)
26
+ entity_instance.push_entities_to_connec(mapped_entities[:external_entities])
27
27
 
28
- entity_instance.after_sync(connec_client, external_client, last_synchronization, organization, opts)
28
+ entity_instance.after_sync(last_synchronization)
29
29
  else
30
30
  Rails.logger.warn "Called push to connec job with unknow entity: #{external_entity_name}"
31
31
  end
@@ -33,13 +33,13 @@ module Maestrano::Connector::Rails
33
33
  end
34
34
 
35
35
  private
36
- def find_entity_instance(entity_name)
37
- Maestrano::Connector::Rails::Entity.entities_list.each do |entity_name_from_list|
36
+ def find_entity_instance(entity_name, organization, connec_client, external_client, opts)
37
+ Maestrano::Connector::Rails::External.entities_list.each do |entity_name_from_list|
38
38
  clazz = "Entities::#{entity_name_from_list.singularize.titleize.split.join}".constantize
39
39
  if clazz.methods.include?('external_entities_names'.to_sym)
40
- return {instance: clazz.new, is_complex: true, name: entity_name_from_list} if clazz.external_entities_names.include?(entity_name)
40
+ return {instance: clazz.new(organization, connec_client, external_client, opts), is_complex: true, name: entity_name_from_list} if clazz.external_entities_names.include?(entity_name)
41
41
  elsif clazz.methods.include?('external_entity_name'.to_sym)
42
- return {instance: clazz.new, is_complex: false, name: entity_name_from_list} if clazz.external_entity_name == entity_name
42
+ return {instance: clazz.new(organization, connec_client, external_client, opts), is_complex: false, name: entity_name_from_list} if clazz.external_entity_name == entity_name
43
43
  end
44
44
  end
45
45
  nil
@@ -7,7 +7,7 @@ module Maestrano::Connector::Rails
7
7
  # * :only_entities => [person, tasks_list]
8
8
  # * :full_sync => true synchronization is performed without date filtering
9
9
  # * :connec_preemption => true|false : preemption is always|never given to connec in case of conflict (if not set, the most recently updated entity is kept)
10
- def perform(organization, opts)
10
+ def perform(organization, opts={})
11
11
  return unless organization.sync_enabled
12
12
 
13
13
  # Check if previous synchronization is still running
@@ -29,10 +29,19 @@ module Maestrano::Connector::Rails
29
29
 
30
30
  begin
31
31
  last_synchronization = organization.last_successful_synchronization
32
- connec_client = Maestrano::Connec::Client[organization.tenant].new(organization.uid)
32
+ connec_client = ConnecHelper.get_client(organization)
33
33
  external_client = External.get_client(organization)
34
34
 
35
- if opts[:only_entities]
35
+ # First synchronization should be from external to Connec! only to let the smart merging works
36
+ # We do a doube sync: only from external, then only from connec!
37
+ if last_synchronization.nil?
38
+ ConnectorLogger.log('info', organization, "First synchronization ever. Doing two half syncs to allow smart merging to work its magic.")
39
+ [{skip_connec: true}, {skip_external: true}].each do |opt|
40
+ organization.synchronized_entities.select{|k, v| v}.keys.each do |entity|
41
+ sync_entity(entity.to_s, organization, connec_client, external_client, last_synchronization, opts.merge(opt))
42
+ end
43
+ end
44
+ elsif opts[:only_entities]
36
45
  ConnectorLogger.log('info', organization, "Synchronization is partial and will synchronize only #{opts[:only_entities].join(' ')}")
37
46
  # The synchronization is marked as partial and will not be considered as the last-synchronization for the next sync
38
47
  current_synchronization.set_partial
@@ -54,15 +63,15 @@ module Maestrano::Connector::Rails
54
63
  end
55
64
 
56
65
  def sync_entity(entity_name, organization, connec_client, external_client, last_synchronization, opts)
57
- entity_instance = "Entities::#{entity_name.titleize.split.join}".constantize.new
66
+ entity_instance = "Entities::#{entity_name.titleize.split.join}".constantize.new(organization, connec_client, external_client, opts)
58
67
 
59
- entity_instance.before_sync(connec_client, external_client, last_synchronization, organization, opts)
60
- external_entities = entity_instance.get_external_entities(external_client, last_synchronization, organization, opts)
61
- connec_entities = entity_instance.get_connec_entities(connec_client, last_synchronization, organization, opts)
62
- mapped_entities = entity_instance.consolidate_and_map_data(connec_entities, external_entities, organization, opts)
63
- entity_instance.push_entities_to_external(external_client, mapped_entities[:connec_entities], organization)
64
- entity_instance.push_entities_to_connec(connec_client, mapped_entities[:external_entities], organization)
65
- entity_instance.after_sync(connec_client, external_client, last_synchronization, organization, opts)
68
+ entity_instance.before_sync(last_synchronization)
69
+ external_entities = entity_instance.get_external_entities_wrapper(last_synchronization)
70
+ connec_entities = entity_instance.get_connec_entities(last_synchronization)
71
+ mapped_entities = entity_instance.consolidate_and_map_data(connec_entities, external_entities)
72
+ entity_instance.push_entities_to_external(mapped_entities[:connec_entities])
73
+ entity_instance.push_entities_to_connec(mapped_entities[:external_entities])
74
+ entity_instance.after_sync(last_synchronization)
66
75
  end
67
76
  end
68
77
  end
@@ -1,6 +1,13 @@
1
1
  module Maestrano::Connector::Rails::Concerns::ComplexEntity
2
2
  extend ActiveSupport::Concern
3
3
 
4
+ def initialize(organization, connec_client, external_client, opts={})
5
+ @organization = organization
6
+ @connec_client = connec_client
7
+ @external_client = external_client
8
+ @opts = opts
9
+ end
10
+
4
11
  # -------------------------------------------------------------
5
12
  # Complex specific methods
6
13
  # Those methods needs to be implemented in each complex entity
@@ -28,7 +35,7 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
28
35
  # external_entities_names[1]: [unmapped_connec_entitiy4]
29
36
  # }
30
37
  # }
31
- def connec_model_to_external_model(connec_hash_of_entities, organization)
38
+ def connec_model_to_external_model(connec_hash_of_entities)
32
39
  raise "Not implemented"
33
40
  end
34
41
 
@@ -45,100 +52,65 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
45
52
  # connec_entities_names[0]: [unmapped_external_entity3, unmapped_external_entity4]
46
53
  # }
47
54
  # }
48
- def external_model_to_connec_model(external_hash_of_entities, organization)
55
+ def external_model_to_connec_model(external_hash_of_entities)
49
56
  raise "Not implemented"
50
57
  end
51
58
 
52
- # -------------------------------------------------------------
53
- # General methods
54
- # -------------------------------------------------------------
55
- def map_to_external_with_idmap(entity, organization, external_entity_name, sub_entity_instance)
56
- idmap = sub_entity_instance.class.find_idmap({connec_id: entity['id'], external_entity: external_entity_name.downcase, organization_id: organization.id})
57
-
58
- if idmap
59
- return nil if idmap.external_inactive || !idmap.to_external
60
-
61
- if idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
62
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{sub_entity_instance.class.entity_name} : #{entity}")
63
- nil
64
- else
65
- idmap.update(name: sub_entity_instance.class.object_name_from_connec_entity_hash(entity))
66
- {entity: sub_entity_instance.map_to(external_entity_name, entity, organization), idmap: idmap}
67
- end
68
- else
69
- {entity: sub_entity_instance.map_to(external_entity_name, entity, organization), idmap: sub_entity_instance.class.create_idmap_from_connec_entity(entity, external_entity_name, organization)}
70
- end
71
- end
72
-
73
59
  # -------------------------------------------------------------
74
60
  # Entity equivalent methods
75
61
  # -------------------------------------------------------------
76
- def get_connec_entities(client, last_synchronization, organization, opts={})
62
+ def get_connec_entities(last_synchronization)
77
63
  entities = ActiveSupport::HashWithIndifferentAccess.new
78
64
 
79
65
  self.class.connec_entities_names.each do |connec_entity_name|
80
- sub_entity_instance = "Entities::SubEntities::#{connec_entity_name.titleize.split.join}".constantize.new
81
- entities[connec_entity_name] = sub_entity_instance.get_connec_entities(client, last_synchronization, organization, opts)
66
+ sub_entity_instance = instantiate_sub_entity_instance(connec_entity_name)
67
+ entities[connec_entity_name] = sub_entity_instance.get_connec_entities(last_synchronization)
82
68
  end
83
69
  entities
84
70
  end
85
71
 
86
- def get_external_entities(client, last_synchronization, organization, opts={})
72
+ def get_external_entities_wrapper(last_synchronization)
87
73
  entities = ActiveSupport::HashWithIndifferentAccess.new
88
74
 
89
75
  self.class.external_entities_names.each do |external_entity_name|
90
- sub_entity_instance = "Entities::SubEntities::#{external_entity_name.titleize.split.join}".constantize.new
91
- entities[external_entity_name] = sub_entity_instance.get_external_entities(client, last_synchronization, organization, opts)
76
+ sub_entity_instance = instantiate_sub_entity_instance(external_entity_name)
77
+ entities[external_entity_name] = sub_entity_instance.get_external_entities_wrapper(last_synchronization)
92
78
  end
93
79
  entities
94
80
  end
95
81
 
96
- def consolidate_and_map_data(connec_entities, external_entities, organization, opts={})
97
- modeled_external_entities = external_model_to_connec_model(external_entities, organization)
98
- modeled_connec_entities = connec_model_to_external_model(connec_entities, organization)
82
+ def consolidate_and_map_data(connec_entities, external_entities)
83
+ modeled_external_entities = external_model_to_connec_model(external_entities)
84
+ modeled_connec_entities = connec_model_to_external_model(connec_entities)
99
85
 
100
- modeled_external_entities.each do |external_entity_name, entities_in_connec_model|
101
- entities_in_connec_model.each do |connec_entity_name, entities|
102
- sub_entity_instance = "Entities::SubEntities::#{external_entity_name.titleize.split.join}".constantize.new
103
-
104
- entities.map!{|entity|
105
- idmap = sub_entity_instance.class.find_idmap(external_id: sub_entity_instance.class.id_from_external_entity_hash(entity), connec_entity: connec_entity_name.downcase, organization_id: organization.id)
106
-
107
- # No idmap: creating one, nothing else to do
108
- unless idmap
109
- next {entity: sub_entity_instance.map_to(connec_entity_name, entity, organization), idmap: sub_entity_instance.class.create_idmap_from_external_entity(entity, connec_entity_name, organization)}
110
- end
111
-
112
- # Not pushing entity to Connec!
113
- next nil unless idmap.to_connec
86
+ mapped_connec_entities = consolidate_and_map_connec_entities(modeled_connec_entities, modeled_external_entities)
87
+ mapped_external_entities = consolidate_and_map_external_entities(modeled_external_entities)
114
88
 
115
- # Not pushing to Connec! and flagging as inactive if inactive in external application
116
- inactive = sub_entity_instance.class.inactive_from_external_entity_hash?(entity)
117
- idmap.update(external_inactive: inactive)
118
- next nil if inactive
89
+ return {connec_entities: mapped_connec_entities, external_entities: mapped_external_entities}
90
+ end
119
91
 
120
- # Entity has not been modified since its last push to connec!
121
- next nil if Maestrano::Connector::Rails::Entity.not_modified_since_last_push_to_connec?(idmap, entity, sub_entity_instance, organization)
92
+ def consolidate_and_map_connec_entities(modeled_connec_entities, modeled_external_entities)
93
+ modeled_connec_entities.each do |connec_entity_name, entities_in_external_model|
94
+ entities_in_external_model.each do |external_entity_name, entities|
122
95
 
123
- idmap.update(name: sub_entity_instance.class.object_name_from_external_entity_hash(entity))
96
+ sub_entity_instance = instantiate_sub_entity_instance(connec_entity_name)
97
+ equivalent_external_entities = (modeled_external_entities[external_entity_name] && modeled_external_entities[external_entity_name][connec_entity_name]) || []
124
98
 
125
- # Check for conflict with entities from connec!
126
- equivalent_connec_entities = modeled_connec_entities[connec_entity_name][external_entity_name] || []
127
- Maestrano::Connector::Rails::Entity.solve_conflict(entity, sub_entity_instance, equivalent_connec_entities, connec_entity_name, idmap, organization, opts)
128
- }.compact!
99
+ entities_in_external_model[external_entity_name] = sub_entity_instance.consolidate_and_map_connec_entities(entities, equivalent_external_entities, sub_entity_instance.class.references[external_entity_name] || [], external_entity_name)
129
100
  end
130
101
  end
102
+ modeled_connec_entities
103
+ end
131
104
 
132
- modeled_connec_entities.each do |connec_entity_name, entities_in_external_model|
133
- entities_in_external_model.each do |external_entity_name, entities|
134
- sub_entity_instance = "Entities::SubEntities::#{connec_entity_name.titleize.split.join}".constantize.new
135
- entities.map!{|entity|
136
- self.map_to_external_with_idmap(entity, organization, external_entity_name, sub_entity_instance)
137
- }.compact!
105
+ def consolidate_and_map_external_entities(modeled_external_entities)
106
+ modeled_external_entities.each do |external_entity_name, entities_in_connec_model|
107
+ entities_in_connec_model.each do |connec_entity_name, entities|
108
+ sub_entity_instance = instantiate_sub_entity_instance(external_entity_name)
109
+
110
+ entities_in_connec_model[connec_entity_name] = sub_entity_instance.consolidate_and_map_external_entities(entities, connec_entity_name)
138
111
  end
139
112
  end
140
-
141
- return {connec_entities: modeled_connec_entities, external_entities: modeled_external_entities}
113
+ modeled_external_entities
142
114
  end
143
115
 
144
116
  # input : {
@@ -150,30 +122,30 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
150
122
  # connec_entities_names[0]: [mapped_external_entity3, mapped_external_entity4]
151
123
  # }
152
124
  # }
153
- def push_entities_to_connec(connec_client, mapped_external_entities_with_idmaps, organization)
125
+ def push_entities_to_connec(mapped_external_entities_with_idmaps)
154
126
  mapped_external_entities_with_idmaps.each do |external_entity_name, entities_in_connec_model|
155
- sub_entity_instance = "Entities::SubEntities::#{external_entity_name.titleize.split.join}".constantize.new
127
+ sub_entity_instance = instantiate_sub_entity_instance(external_entity_name)
156
128
  entities_in_connec_model.each do |connec_entity_name, mapped_entities_with_idmaps|
157
- sub_entity_instance.push_entities_to_connec_to(connec_client, mapped_entities_with_idmaps, connec_entity_name, organization)
129
+ sub_entity_instance.push_entities_to_connec_to(mapped_entities_with_idmaps, connec_entity_name)
158
130
  end
159
131
  end
160
132
  end
161
133
 
162
134
 
163
- def push_entities_to_external(external_client, mapped_connec_entities_with_idmaps, organization)
135
+ def push_entities_to_external(mapped_connec_entities_with_idmaps)
164
136
  mapped_connec_entities_with_idmaps.each do |connec_entity_name, entities_in_external_model|
165
- sub_entity_instance = "Entities::SubEntities::#{connec_entity_name.titleize.split.join}".constantize.new
137
+ sub_entity_instance = instantiate_sub_entity_instance(connec_entity_name)
166
138
  entities_in_external_model.each do |external_entity_name, mapped_entities_with_idmaps|
167
- sub_entity_instance.push_entities_to_external_to(external_client, mapped_entities_with_idmaps, external_entity_name, organization)
139
+ sub_entity_instance.push_entities_to_external_to(mapped_entities_with_idmaps, external_entity_name)
168
140
  end
169
141
  end
170
142
  end
171
143
 
172
- def before_sync(connec_client, external_client, last_synchronization, organization, opts)
144
+ def before_sync(last_synchronization)
173
145
  # Does nothing by default
174
146
  end
175
147
 
176
- def after_sync(connec_client, external_client, last_synchronization, organization, opts)
148
+ def after_sync(last_synchronization)
177
149
  # Does nothing by default
178
150
  end
179
151
 
@@ -183,7 +155,27 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
183
155
  # external_entities_names[0]: [unmapped_external_entity1}, unmapped_external_entity2],
184
156
  # external_entities_names[1]: [unmapped_external_entity3}, unmapped_external_entity4]
185
157
  # }
186
- def filter_connec_entities(entities, organization, opts={})
158
+ def filter_connec_entities(entities)
187
159
  entities
188
160
  end
161
+
162
+ def instantiate_sub_entity_instance(entity_name)
163
+ "Entities::SubEntities::#{entity_name.titleize.split.join}".constantize.new(@organization, @connec_client, @external_client, @opts)
164
+ end
165
+
166
+ # -------------------------------------------------------------
167
+ # Helper methods
168
+ # -------------------------------------------------------------
169
+ module ClassMethods
170
+ # output : {entities_names[0] => [], entities_names[1] => []}
171
+ def build_empty_hash(entities_names)
172
+ Hash[ *entities_names.collect{|name| [ name, []]}.flatten(1) ]
173
+ end
174
+
175
+ # output: {entities_name[0] => [], entities_name[1] => entities}
176
+ # with proc.call(entities_name[1] == entity_name)
177
+ def build_hash_with_entities(entities_name, entity_name, proc, entities)
178
+ Hash[ *entities_name.collect{|name| proc.call(name) == entity_name ? [name, entities] : [ name, []]}.flatten(1) ]
179
+ end
180
+ end
189
181
  end
@@ -0,0 +1,102 @@
1
+ module Maestrano::Connector::Rails::Concerns::ConnecHelper
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+
6
+ def get_client(organization)
7
+ client = Maestrano::Connec::Client[organization.tenant].new(organization.uid)
8
+ client.class.headers('CONNEC-EXTERNAL-IDS' => 'true')
9
+ client
10
+ end
11
+
12
+ # Replace the ids arrays by the external id
13
+ # If a reference has no id for this oauth_provider and oauth_uid but has one for connec returns nil
14
+ def unfold_references(connec_entity, references, organization)
15
+ unfolded_connec_entity = connec_entity.with_indifferent_access
16
+ not_nil = true
17
+
18
+ # Id
19
+ id = unfolded_connec_entity['id'].find{|id| id['provider'] == organization.oauth_provider && id['realm'] == organization.oauth_uid}
20
+ unfolded_connec_entity[:__connec_id] = unfolded_connec_entity['id'].find{|id| id['provider'] == 'connec'}['id']
21
+ if id
22
+ unfolded_connec_entity['id'] = id['id']
23
+ else
24
+ unfolded_connec_entity['id'] = nil
25
+ end
26
+
27
+ # Other refs
28
+ references.each do |reference|
29
+ not_nil &&= unfold_references_helper(unfolded_connec_entity, reference.split('/'), organization)
30
+ end
31
+ not_nil ? unfolded_connec_entity : nil
32
+ end
33
+
34
+ def fold_references(mapped_external_entity, references, organization)
35
+ mapped_external_entity = mapped_external_entity.with_indifferent_access
36
+ (references + ['id']).each do |reference|
37
+ fold_references_helper(mapped_external_entity, reference.split('/'), organization)
38
+ end
39
+ mapped_external_entity
40
+ end
41
+
42
+ def id_hash(id, organization)
43
+ {
44
+ id: id,
45
+ provider: organization.oauth_provider,
46
+ realm: organization.oauth_uid
47
+ }
48
+ end
49
+
50
+ def fold_references_helper(entity, array_of_refs, organization)
51
+ ref = array_of_refs.shift
52
+ field = entity[ref]
53
+
54
+ # Follow embedment path, remplace if it's a string
55
+ unless field.blank?
56
+ case field
57
+ when Array
58
+ field.each do |f|
59
+ fold_references_helper(f, array_of_refs.dup, organization)
60
+ end
61
+ when HashWithIndifferentAccess
62
+ fold_references_helper(entity[ref], array_of_refs, organization)
63
+ when String
64
+ id = field
65
+ entity[ref] = [id_hash(id, organization)]
66
+ end
67
+ end
68
+ end
69
+
70
+ def unfold_references_helper(entity, array_of_refs, organization)
71
+ ref = array_of_refs.shift
72
+ field = entity[ref]
73
+
74
+ # Unfold the id
75
+ if array_of_refs.empty?
76
+ if id = field && field.find{|id| id[:provider] == organization.oauth_provider && id[:realm] == organization.oauth_uid}
77
+ entity[ref] = id['id']
78
+ elsif field && field.find{|id| id[:provider] == 'connec'}
79
+ # We may enqueue a fetch on the endpoint of the missing association, followed by a re-fetch on this one.
80
+ # However it's expected to be an edge case, so for now we rely on the fact that the webhooks should be relativly in order.
81
+ # Worst case it'll be done on following sync
82
+ return nil
83
+ end
84
+
85
+ # Follow embedment path
86
+ else
87
+ unless field.blank?
88
+ case field
89
+ when Array
90
+ field.each do |f|
91
+ unfold_references_helper(f, array_of_refs.dup, organization)
92
+ end
93
+ when HashWithIndifferentAccess
94
+ unfold_references_helper(entity[ref], array_of_refs, organization)
95
+ end
96
+ end
97
+ end
98
+ true
99
+ end
100
+
101
+ end
102
+ end