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.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/app/controllers/maestrano/connec_controller.rb +17 -19
- data/app/jobs/maestrano/connector/rails/all_synchronizations_job.rb +1 -1
- data/app/jobs/maestrano/connector/rails/push_to_connec_job.rb +11 -11
- data/app/jobs/maestrano/connector/rails/synchronization_job.rb +20 -11
- data/app/models/maestrano/connector/rails/concerns/complex_entity.rb +66 -74
- data/app/models/maestrano/connector/rails/concerns/connec_helper.rb +102 -0
- data/app/models/maestrano/connector/rails/concerns/entity.rb +233 -231
- data/app/models/maestrano/connector/rails/concerns/external.rb +7 -0
- data/app/models/maestrano/connector/rails/concerns/sub_entity_base.rb +14 -43
- data/app/models/maestrano/connector/rails/connec_helper.rb +5 -0
- data/app/models/maestrano/connector/rails/organization.rb +8 -2
- data/db/20160524112054_add_encryption_on_oauth_keys.rb +8 -0
- data/lib/generators/connector/install_generator.rb +1 -0
- data/lib/generators/connector/templates/complex_entity_example/contact.rb +1 -1
- data/lib/generators/connector/templates/complex_entity_example/contact_and_lead.rb +2 -2
- data/lib/generators/connector/templates/entity.rb +13 -19
- data/lib/generators/connector/templates/example_entity.rb +2 -2
- data/lib/generators/connector/templates/example_entity_spec.rb +73 -0
- data/lib/generators/connector/templates/external.rb +11 -0
- data/maestrano-connector-rails.gemspec +13 -8
- data/release_notes.md +81 -0
- data/spec/controllers/connec_controller_spec.rb +19 -6
- data/spec/dummy/app/models/maestrano/connector/rails/entity.rb +0 -7
- data/spec/dummy/app/models/maestrano/connector/rails/external.rb +7 -0
- data/spec/dummy/app/views/home/index.html.erb +1 -36
- data/spec/factories.rb +3 -0
- data/spec/integration/connec_to_external_spec.rb +188 -0
- data/spec/integration/external_to_connec_spec.rb +155 -0
- data/spec/integration/integration_complex_spec.rb +281 -0
- data/spec/integration/singleton_spec.rb +288 -0
- data/spec/jobs/all_synchronizations_job_spec.rb +5 -0
- data/spec/jobs/push_to_connec_job_spec.rb +3 -6
- data/spec/jobs/synchronization_job_spec.rb +29 -17
- data/spec/models/complex_entity_spec.rb +257 -412
- data/spec/models/connec_helper_spec.rb +143 -0
- data/spec/models/entity_spec.rb +420 -348
- data/spec/models/external_spec.rb +4 -0
- data/spec/models/organization_spec.rb +2 -1
- data/spec/models/sub_entity_base_spec.rb +28 -69
- data/template/factories.rb +3 -1
- data/template/maestrano-connector-template.rb +11 -13
- data/template/maestrano.rb +2 -1
- data/template/settings/development.yml +4 -2
- data/template/settings/production.yml +1 -11
- data/template/settings/settings.yml +8 -0
- data/template/settings/test.yml +2 -0
- data/template/settings/uat.yml +1 -9
- metadata +12 -7
- data/Gemfile.lock +0 -256
- data/realse_notes.md +0 -16
- data/spec/dummy/app/views/admin/index.html.erb +0 -51
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ee22c8ca44f84ce0e4a717c80892f1765af5987
|
4
|
+
data.tar.gz: 65e99c4163462829142e439f8323e8e2cd6f6306
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a480181d0599bc16c945364ff6949c684eaa431f89b1c5af2f41f7f8999f451ab48a470abe680a1d854f57284fa6831bd73b3ecaf0b503a2053b34674f3a4b9
|
7
|
+
data.tar.gz: 00ec463ca23d1994baaf7873ce4a26c8c3cc76c72809e5a1b06cba7ca54899f67ed5dcc92c97f019ac4fbf6363d42862edf770c579245fb42ce30ae488fbdcc5
|
data/Gemfile
CHANGED
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/
|
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.
|
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
|
-
|
10
|
-
next Rails.logger.info "Received notification from Connec! for unknow entity: #{entity_name}" unless
|
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[
|
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::
|
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.
|
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
|
30
|
-
filtered_entities = entity_instance.filter_connec_entities(
|
31
|
-
mapped_entity = entity_instance.consolidate_and_map_data(filtered_entities,
|
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]
|
34
|
-
mapped_entity = entity_instance.consolidate_and_map_data(filtered_entities, []
|
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(
|
34
|
+
entity_instance.push_entities_to_external(mapped_entity[:connec_entities])
|
37
35
|
|
38
|
-
entity_instance.after_sync(
|
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
|
51
|
-
Maestrano::Connector::Rails::
|
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 {
|
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 {
|
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(
|
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::
|
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(
|
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(
|
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
|
24
|
+
mapped_entities = entity_instance.consolidate_and_map_data([], entities)
|
25
25
|
end
|
26
|
-
entity_instance.push_entities_to_connec(
|
26
|
+
entity_instance.push_entities_to_connec(mapped_entities[:external_entities])
|
27
27
|
|
28
|
-
entity_instance.after_sync(
|
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::
|
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 =
|
32
|
+
connec_client = ConnecHelper.get_client(organization)
|
33
33
|
external_client = External.get_client(organization)
|
34
34
|
|
35
|
-
|
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(
|
60
|
-
external_entities = entity_instance.
|
61
|
-
connec_entities = entity_instance.get_connec_entities(
|
62
|
-
mapped_entities = entity_instance.consolidate_and_map_data(connec_entities, external_entities
|
63
|
-
entity_instance.push_entities_to_external(
|
64
|
-
entity_instance.push_entities_to_connec(
|
65
|
-
entity_instance.after_sync(
|
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
|
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
|
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(
|
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 =
|
81
|
-
entities[connec_entity_name] = sub_entity_instance.get_connec_entities(
|
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
|
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 =
|
91
|
-
entities[external_entity_name] = sub_entity_instance.
|
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
|
97
|
-
modeled_external_entities = external_model_to_connec_model(external_entities
|
98
|
-
modeled_connec_entities = connec_model_to_external_model(connec_entities
|
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
|
-
|
101
|
-
|
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
|
-
|
116
|
-
|
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
|
-
|
121
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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(
|
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 =
|
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(
|
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(
|
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 =
|
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(
|
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(
|
144
|
+
def before_sync(last_synchronization)
|
173
145
|
# Does nothing by default
|
174
146
|
end
|
175
147
|
|
176
|
-
def after_sync(
|
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
|
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
|