maestrano-connector-rails 0.2.20 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 641c5c4a44a4f83f3c44ed2c5e23a474eba48abb
4
- data.tar.gz: 9912810a2b3d6986e68ba89d19d5068e12daaade
3
+ metadata.gz: 2f0083a436b601632884149aff3e8111b01ba1a6
4
+ data.tar.gz: c0e95181ea0b2a66b137e2ec6047ddd4bdc40f2c
5
5
  SHA512:
6
- metadata.gz: 3ad9efcebceb9e3506693ae02d101ee61efdd103d5f04c62ab97b9764c890da72410d9440b8bcc84235beef2bd9431926d3e13cab6e021e524158f8a54a75698
7
- data.tar.gz: cdd4bc5ac76e057c4b4249c66f18e88c6c4eba76b020e1fe211563c1074f64b0e61a4ed575fe881aff49cfbca1ed69d9bd7c74f07ea467d49c39ced3c87f8a09
6
+ metadata.gz: 41fceaf54ad78bdca9cbc7dd6dc07f5d22b67ad6ce532b5cf39d59aa57faccc57f3c657371b75a359570e58b648e3b59cfb0d2d271db4c13c1e8dd9f712e37fa
7
+ data.tar.gz: 639653b1337385bb1b60d45e160b2bb9db96308c4bb023dbfc1a86942e6fdabc78c923351884fed05f2fb83a938b38e4c76ad7bc385a1405a1bd9db69df306a5
data/README.md CHANGED
@@ -8,162 +8,6 @@
8
8
  </p>
9
9
 
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
-
12
11
  Maestrano Connector Integration is currently in closed beta. Want to know more? Send us an email to <contact@maestrano.com>.
13
12
 
14
- - - -
15
-
16
- 1. [Getting Setup](#getting-setup)
17
- 2. [Getting Started](#getting-started)
18
- * [Integration with Maestrano](#integration-with-maestrano)
19
- * [Integration with the external application](#integration-with-the-external-application)
20
- 3. [Preparing synchronizations](#preparing-synchronizations)
21
- * [External.rb](#external.rb)
22
- * [Entity.rb](#entity.rb)
23
- * [Mapping entities](#mapping-entities)
24
- 4. [Pages controllers and views](#pages-controllers-and-views)
25
- 5. [Complex entities](#complex-entities)
26
- 6. [Webhooks](#Webhooks)
27
-
28
- - - -
29
-
30
- ## Getting Setup
31
- Before integrating with us you will need to have your application registered on the Maestrano platform. Please refer to the online documentation we provide: https://maestrano.atlassian.net/wiki/display/CONNECAPIV2/Maestrano+Developers
32
-
33
- ## Getting Started
34
- Create a new rails application using the connector template. The template works with both Ruby 2.2 and jRuby 9.0.4.0, you will be asked which one you want to use.
35
- ```console
36
- rails new <project_name> -m https://raw.githubusercontent.com/Maestrano/maestrano-connector-rails/master/template/maestrano-connector-template.rb
37
- ```
38
-
39
- If and only if you have an error in the template's rails generate step, you'll need to re-run the following command in your project folder:
40
- ```console
41
- bundle
42
- rails g connector:install
43
- rake db:migrate
44
- ```
45
-
46
- ### Integration with Maestrano
47
-
48
- First thing to do is to put your Connec!™ API keys in the config/application.yml:
49
- ```ruby
50
- connec_api_id: 'API_ID'
51
- connec_api_key: 'API_KEY'
52
- ```
53
-
54
- The next thing you need to do is to set your configuration in config/initializers/maestrano.rb. You will need to define where your application will be publicly hosted:
55
- ```ruby
56
- config.app.host = 'http://path_to_app'
57
- ```
58
- The rest of the config has default values, so you can take a look but you don't really need to change anything else.
59
- For a more detailed configuration, please refer to https://github.com/maestrano/maestrano-ruby#configuration
60
-
61
- Please note that the connectors support multi-tenancy, so you may have to set up configuration for tenants other than the Maestrano platform (the default one).
62
-
63
- These configuration are automatically retrieved by Maestrano via a metadata endpoint that is provided by the gem, so you're all setup as it is.
64
-
65
- Time to test! If your launch your application (please make sure that you launch the application on the same path as the one in the config file). If you click on the 'Link your Maestrano account' link on your connector home page, you should be able to do a full SSO process with Maestrano.
66
-
67
- ### Integration with the external application
68
-
69
- Now that you're all setup with Maestrano, it's time to take a look at the external application you are integrating with. Hopefully it has one or several gems for both the authentication process and the API calls. In any case, you'll need to take a look at their documentation.
70
-
71
- You will probably have to request API keys and adds them to the application.yml alongside the Maestrano's ones.
72
-
73
- The connector engine is thought to be able to use oauth authentication processes, and an oauth_controller is provided as an example. Please note that it is only an example and you will have to implements most of it, and to create the needed routes, and use them in the provided view.
74
-
75
- If all went well, you should now be able to use the 'Link this company to...' link on the home page. Congrats!
76
-
77
- ## Preparing synchronizations
78
-
79
- The aim of the connector is to perform synchronizations between Connec!™ and the external application, meaning fetching data on both ends, process them, and push the result to the relevant parties. The Connec!™ part and the synchronization process itself is handled by the engine, so all you have to do is to implements some methods to bridge the calls to the external application.
80
-
81
- ### external.rb
82
-
83
- First file to look for is `models/maestrano/connector/rails/external.rb`. It contains two methods that you need to implement:
84
-
85
- * external_name, which is used for logging purpose only, and should only return the name of the external application, e.g.
86
- ```ruby
87
- def self.external_name
88
- 'This awesome CRM'
89
- end
90
- ```
91
- * get_client, which should return either the external application gem api client, or, in the worst case, a custom HTTParty client.
92
-
93
-
94
- ### entity.rb
95
-
96
- The second important file is `models/maestrano/connector/rails/entity.rb`. It contains a method to declare the entities synchronizable by your connector (more on that later), some methods to retrieve and send data to the external application API, and lastly two methods to extract `id` and `timestamps` from the entity format sent by the external application.
97
-
98
- The details of each methods are explained in the entity.rb file provided.
99
-
100
- ### Mapping entities
101
-
102
- Now that you're all setup with both Connec!™ and the external application, it's time to decide which entities (contacts, accounts, events, ...) you want to synchronize. For each type of entity your connector will synchronize, you will need to create a class that inherits from `Maestrano::Connector::Rails::Entity`.
103
-
104
- An example of such a class in provided in the models/entities/ folder, and demonstrates which methods you'll need to implements for each entity. The main thing to do is the mapping between the external entity and the Connec!™ entity. For the mapping, we use the hash_mapper gem (<https://github.com/ismasan/hash_mapper>).
105
-
106
- This type of entity class enable one-to-one model correspondance. For more complex needs, please refer to the complex entity section below.
107
-
108
- You'll find the connec API documentation here: <http://maestrano.github.io/connec/>, and should also refer to the external application API documentation.
109
-
110
- Also don't forget that each entity your connector synchronize should be declared in the entity.rb class, and, because the synchronizable entities are stored in the local database for each organization, you'll need to create a migration for exisiting organization if you add an new entity.
111
-
112
- #### Overriding methods
113
-
114
- To fit each entity specific need, you can overide all methods define in the entity class, including those implemented by the engine.
115
-
116
- In particular, you will probably need to override the mapping methods to realize reference between entities (this can't be done during the mapping because it requires the organization id).
117
-
118
- For example:
119
- ```ruby
120
- def map_to_connec(entity, organization)
121
- if id = entity['AccountId']
122
- idmap = Maestrano::Connector::Rails::IdMap.find_by(external_entity: 'account', external_id: id, organization_id: organization.id, connec_entity: 'organization')
123
- entity['AccountId'] = idmap ? idmap.connec_id : ''
124
- end
125
- self.mapper_class.denormalize(entity)
126
- end
127
- ```
128
-
129
- ### Triggering synchronizations
130
- Performing the synchronization of all the linked organizations can be triggered by executing
131
- ```ruby
132
- Maestrano::Connector::Rails::AllSynchronizationsJob
133
- ```
134
-
135
- The synchronization of a specific organization can be performed by executing
136
- ```ruby
137
- Maestrano::Connector::Rails::SynchronizationJob.perform_later(organization, {})
138
- ```
139
-
140
- ## Pages controllers and views
141
-
142
- The home and admin pages views and controllers are provided as example, but you are free to customize them and the styling is left for you to do.
143
-
144
- ## Complex entities
145
-
146
- For more complex correspondances, like one-to-many or many-to-many ones, you can use the complex entity workflow. To see how it works, you can run
147
- ```console
148
- rails g connector:complex_entity
149
- ```
150
-
151
- This will generate some example files demonstrating a one-to-many correspondance between Connec!™ person and external contact and lead data models.
152
-
153
- The complex entities workflow uses two methods to pre-process data which you have to implements for each complex entity (see contact_and_lead.rb). They are called before the mapping step, and you can use them to perform any data model specific operations.
154
-
155
- ## Webhooks
156
-
157
- Connec!™ issues webhooks each time an entity is created or updated. This allows real time integration from Connec!™ to the application you're integrating with. All you have to do is subscribe to the webhooks on the entities your interested in (in your Maestrano.rb file), everything else is magically handled by the gem.
158
-
159
- If the application you're integrating with support webhooks, you can and should use them to allow a full real time integration. The gem provide a job to help you do that. It will perform the necessary checks, mappings and then push the entities to Connec!™. All you have to do is build a controller and config the necessary routes to catch the webhook, and call this job. For example:
160
- ```ruby
161
- class ExternalWebhooksController < ApplicationController
162
- def notification
163
- organization = # find organization from webhook params
164
- Maestrano::Connector::Rails::PushToConnecJob.perform_later(organization, params[:notif][:name], parmas[:notif][:objects])
165
- end
166
- end
167
- ```
168
-
169
- If you have questions, please contact the technical team <developers@maestrano.com>.
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).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.20
1
+ 0.3.0
@@ -16,7 +16,7 @@ class Maestrano::ConnecController < Maestrano::Rails::WebHookController
16
16
 
17
17
  # Build expected input for consolidate_and_map_data
18
18
  if entity_instance_hash[:is_complex]
19
- mapped_entity = entity_instance.consolidate_and_map_data(Hash[ *entity_instance.connec_entities_names.collect{|name| name.downcase.pluralize == entity_name ? [name, [entity]] : [ name, []]}.flatten(1) ], Hash[ *entity_instance.external_entities_names.collect{|name| [ name, []]}.flatten(1) ], organization, {})
19
+ mapped_entity = entity_instance.consolidate_and_map_data(Hash[ *entity_instance.class.connec_entities_names.collect{|name| name.parameterize('_').pluralize == entity_name ? [name, [entity]] : [ name, []]}.flatten(1) ], Hash[ *entity_instance.class.external_entities_names.collect{|name| [ name, []]}.flatten(1) ], organization, {})
20
20
  else
21
21
  mapped_entity = entity_instance.consolidate_and_map_data([entity], [], organization, {})
22
22
  end
@@ -44,11 +44,11 @@ class Maestrano::ConnecController < Maestrano::Rails::WebHookController
44
44
  private
45
45
  def find_entity_instance(entity_name)
46
46
  Maestrano::Connector::Rails::Entity.entities_list.each do |entity_name_from_list|
47
- instance = "Entities::#{entity_name_from_list.singularize.titleize.split.join}".constantize.new
48
- if instance.methods.include?('connec_entities_names'.to_sym)
49
- return {instance: instance, is_complex: true, name: entity_name_from_list} if instance.connec_entities_names.map{|n| n.pluralize.downcase}.include?(entity_name)
50
- elsif instance.methods.include?('connec_entity_name'.to_sym)
51
- return {instance: instance, is_complex: false, name: entity_name_from_list} if instance.normalized_connec_entity_name == entity_name
47
+ clazz = "Entities::#{entity_name_from_list.singularize.titleize.split.join}".constantize
48
+ if clazz.methods.include?('connec_entities_names'.to_sym)
49
+ 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)
50
+ elsif clazz.methods.include?('connec_entity_name'.to_sym)
51
+ return {instance: clazz.new, is_complex: false, name: entity_name_from_list} if clazz.normalized_connec_entity_name == entity_name
52
52
  end
53
53
  end
54
54
  nil
@@ -12,9 +12,8 @@ module Maestrano::Connector::Rails
12
12
  if entity_instance_hash = find_entity_instance(external_entity_name)
13
13
  entity_instance = entity_instance_hash[:instance]
14
14
  next unless organization.synchronized_entities[entity_instance_hash[:name].to_sym]
15
-
16
15
  if entity_instance_hash[:is_complex]
17
- mapped_entities = entity_instance.consolidate_and_map_data(Hash[ *entity_instance.connec_entities_names.collect{|name| [ name, []]}.flatten(1) ], Hash[ *entity_instance.external_entities_names.collect{|name| name == external_entity_name ? [name, entities] : [ name, []]}.flatten(1) ], organization, {})
16
+ 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, {})
18
17
  else
19
18
  mapped_entities = entity_instance.consolidate_and_map_data([], entities, organization, {})
20
19
  end
@@ -29,11 +28,11 @@ module Maestrano::Connector::Rails
29
28
  private
30
29
  def find_entity_instance(entity_name)
31
30
  Maestrano::Connector::Rails::Entity.entities_list.each do |entity_name_from_list|
32
- instance = "Entities::#{entity_name_from_list.singularize.titleize.split.join}".constantize.new
33
- if instance.methods.include?('external_entities_names'.to_sym)
34
- return {instance: instance, is_complex: true, name: entity_name_from_list} if instance.external_entities_names.include?(entity_name)
35
- elsif instance.methods.include?('external_entity_name'.to_sym)
36
- return {instance: instance, is_complex: false, name: entity_name_from_list} if instance.external_entity_name == entity_name
31
+ clazz = "Entities::#{entity_name_from_list.singularize.titleize.split.join}".constantize
32
+ if clazz.methods.include?('external_entities_names'.to_sym)
33
+ return {instance: clazz.new, is_complex: true, name: entity_name_from_list} if clazz.external_entities_names.include?(entity_name)
34
+ elsif clazz.methods.include?('external_entity_name'.to_sym)
35
+ return {instance: clazz.new, is_complex: false, name: entity_name_from_list} if clazz.external_entity_name == entity_name
37
36
  end
38
37
  end
39
38
  nil
@@ -49,11 +49,13 @@ module Maestrano::Connector::Rails
49
49
  def sync_entity(entity_name, organization, connec_client, external_client, last_synchronization, opts)
50
50
  entity_instance = "Entities::#{entity_name.titleize.split.join}".constantize.new
51
51
 
52
+ entity_instance.before_sync(connec_client, external_client, organization, opts)
52
53
  external_entities = entity_instance.get_external_entities(external_client, last_synchronization, organization, opts)
53
54
  connec_entities = entity_instance.get_connec_entities(connec_client, last_synchronization, organization, opts)
54
55
  mapped_entities = entity_instance.consolidate_and_map_data(connec_entities, external_entities, organization, opts)
55
56
  entity_instance.push_entities_to_external(external_client, mapped_entities[:connec_entities], organization)
56
57
  entity_instance.push_entities_to_connec(connec_client, mapped_entities[:external_entities], organization)
58
+ entity_instance.after_sync(connec_client, external_client, organization, opts)
57
59
  end
58
60
  end
59
61
  end
@@ -1,18 +1,18 @@
1
1
  module Maestrano::Connector::Rails::Concerns::ComplexEntity
2
2
  extend ActiveSupport::Concern
3
3
 
4
- @@external_name = Maestrano::Connector::Rails::External.external_name
5
-
6
4
  # -------------------------------------------------------------
7
5
  # Complex specific methods
8
6
  # Those methods needs to be implemented in each complex entity
9
7
  # -------------------------------------------------------------
10
- def connec_entities_names
11
- raise "Not implemented"
12
- end
8
+ module ClassMethods
9
+ def connec_entities_names
10
+ raise "Not implemented"
11
+ end
13
12
 
14
- def external_entities_names
15
- raise "Not implemented"
13
+ def external_entities_names
14
+ raise "Not implemented"
15
+ end
16
16
  end
17
17
 
18
18
  # input : {
@@ -53,18 +53,18 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
53
53
  # General methods
54
54
  # -------------------------------------------------------------
55
55
  def map_to_external_with_idmap(entity, organization, external_entity_name, sub_entity_instance)
56
- idmap = sub_entity_instance.find_idmap({connec_id: entity['id'], external_entity: external_entity_name, organization_id: organization.id})
56
+ idmap = sub_entity_instance.class.find_idmap({connec_id: entity['id'], external_entity: external_entity_name, organization_id: organization.id})
57
57
 
58
58
  if idmap
59
- idmap.update(name: sub_entity_instance.object_name_from_connec_entity_hash(entity))
59
+ idmap.update(name: sub_entity_instance.class.object_name_from_connec_entity_hash(entity))
60
60
  if (!idmap.to_external) || idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
61
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{sub_entity_instance.entity_name} : #{entity}")
61
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{sub_entity_instance.class.entity_name} : #{entity}")
62
62
  nil
63
63
  else
64
64
  {entity: sub_entity_instance.map_to(external_entity_name, entity, organization), idmap: idmap}
65
65
  end
66
66
  else
67
- {entity: sub_entity_instance.map_to(external_entity_name, entity, organization), idmap: sub_entity_instance.create_idmap_from_connec_entity(entity, external_entity_name, organization)}
67
+ {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)}
68
68
  end
69
69
  end
70
70
 
@@ -100,20 +100,20 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
100
100
  sub_entity_instance = "Entities::SubEntities::#{external_entity_name.titleize.split.join}".constantize.new
101
101
 
102
102
  entities.map!{|entity|
103
- idmap = sub_entity_instance.find_idmap(external_id: sub_entity_instance.get_id_from_external_entity_hash(entity), connec_entity: connec_entity_name, organization_id: organization.id)
103
+ idmap = sub_entity_instance.class.find_idmap(external_id: sub_entity_instance.class.id_from_external_entity_hash(entity), connec_entity: connec_entity_name, organization_id: organization.id)
104
104
 
105
105
  # No idmap: creating one, nothing else to do
106
106
  if idmap
107
- idmap.update(name: sub_entity_instance.object_name_from_external_entity_hash(entity))
107
+ idmap.update(name: sub_entity_instance.class.object_name_from_external_entity_hash(entity))
108
108
  else
109
- next {entity: sub_entity_instance.map_to(connec_entity_name, entity, organization), idmap: sub_entity_instance.create_idmap_from_external_entity(entity, connec_entity_name, organization)}
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
110
  end
111
111
 
112
112
  # Not pushing entity to Connec!
113
113
  next nil unless idmap.to_connec
114
114
 
115
115
  # Entity has not been modified since its last push to connec!
116
- next nil if Maestrano::Connector::Rails::Entity.not_modified_since_last_push_to_connec(idmap, entity, sub_entity_instance, organization)
116
+ next nil if Maestrano::Connector::Rails::Entity.not_modified_since_last_push_to_connec?(idmap, entity, sub_entity_instance, organization)
117
117
 
118
118
  # Check for conflict with entities from connec!
119
119
  equivalent_connec_entities = modeled_connec_entities[connec_entity_name][external_entity_name] || []
@@ -8,143 +8,191 @@ module Maestrano::Connector::Rails::Concerns::Entity
8
8
  def entities_list
9
9
  raise "Not implemented"
10
10
  end
11
- end
12
11
 
13
- @@external_name = Maestrano::Connector::Rails::External.external_name
12
+ # ----------------------------------------------
13
+ # IdMap methods
14
+ # ----------------------------------------------
15
+ def names_hash
16
+ {
17
+ connec_entity: connec_entity_name.downcase,
18
+ external_entity: external_entity_name.downcase
19
+ }
20
+ end
21
+
22
+ def find_or_create_idmap(organization_and_id)
23
+ Maestrano::Connector::Rails::IdMap.find_or_create_by(names_hash.merge(organization_and_id))
24
+ end
25
+
26
+ # organization_and_id can be either:
27
+ # * {connec_id: 'id', organization_id: 'id'}
28
+ # * {external_id: 'id', organization_id: 'id'}
29
+ # Needs to include either connec_entity or external_entity for complex entities
30
+ def find_idmap(organization_and_id)
31
+ Maestrano::Connector::Rails::IdMap.find_by(names_hash.merge(organization_and_id))
32
+ end
33
+
34
+ def create_idmap_from_external_entity(entity, organization)
35
+ h = names_hash.merge({
36
+ external_id: id_from_external_entity_hash(entity),
37
+ name: object_name_from_external_entity_hash(entity),
38
+ organization_id: organization.id
39
+ })
40
+ Maestrano::Connector::Rails::IdMap.create(h)
41
+ end
42
+
43
+ def create_idmap_from_connec_entity(entity, organization)
44
+ h = names_hash.merge({
45
+ connec_id: entity['id'],
46
+ name: object_name_from_connec_entity_hash(entity),
47
+ organization_id: organization.id
48
+ })
49
+ Maestrano::Connector::Rails::IdMap.create(h)
50
+ end
51
+
52
+ # ----------------------------------------------
53
+ # Connec! methods
54
+ # ----------------------------------------------
55
+ def normalized_connec_entity_name
56
+ normalize_connec_entity_name(connec_entity_name)
57
+ end
58
+
59
+ def normalize_connec_entity_name(name)
60
+ if singleton?
61
+ name.parameterize('_')
62
+ else
63
+ name.parameterize('_').pluralize
64
+ end
65
+ end
66
+
67
+ # ----------------------------------------------
68
+ # External methods
69
+ # ----------------------------------------------
70
+ def id_from_external_entity_hash(entity)
71
+ raise "Not implemented"
72
+ end
73
+
74
+ def last_update_date_from_external_entity_hash(entity)
75
+ raise "Not implemented"
76
+ end
77
+
78
+ # ----------------------------------------------
79
+ # Entity specific methods
80
+ # Those methods need to be define in each entity
81
+ # ----------------------------------------------
82
+ # Is this resource a singleton (in Connec!)?
83
+ def singleton?
84
+ false
85
+ end
86
+
87
+ # Entity name in Connec!
88
+ def connec_entity_name
89
+ raise "Not implemented"
90
+ end
91
+
92
+ # Entity name in external system
93
+ def external_entity_name
94
+ raise "Not implemented"
95
+ end
96
+
97
+ # Entity Mapper Class
98
+ def mapper_class
99
+ raise "Not implemented"
100
+ end
101
+
102
+ # Return a string representing the object from a connec! entity hash
103
+ def object_name_from_connec_entity_hash(entity)
104
+ raise "Not implemented"
105
+ end
106
+
107
+ # Return a string representing the object from an external entity hash
108
+ def object_name_from_external_entity_hash(entity)
109
+ raise "Not implemented"
110
+ end
111
+ end
14
112
 
15
113
  # ----------------------------------------------
16
114
  # Mapper methods
17
115
  # ----------------------------------------------
18
116
  # Map a Connec! entity to the external format
19
117
  def map_to_external(entity, organization)
20
- mapper_class.normalize(entity)
118
+ self.class.mapper_class.normalize(entity)
21
119
  end
22
120
 
23
121
  # Map an external entity to Connec! format
24
122
  def map_to_connec(entity, organization)
25
- mapper_class.denormalize(entity)
123
+ self.class.mapper_class.denormalize(entity)
26
124
  end
27
125
 
28
- # ----------------------------------------------
29
- # IdMap methods
30
- # ----------------------------------------------
31
- def names_hash
32
- {
33
- connec_entity: connec_entity_name.downcase,
34
- external_entity: external_entity_name.downcase
35
- }
36
- end
37
-
38
- def find_or_create_idmap(organization_and_id)
39
- Maestrano::Connector::Rails::IdMap.find_or_create_by(names_hash.merge(organization_and_id))
40
- end
41
-
42
- # organization_and_id can be either:
43
- # * {connec_id: 'id', organization_id: 'id'}
44
- # * {external_id: 'id', organization_id: 'id'}
45
- # Needs to include either connec_entity or external_entity for complex entities
46
- def find_idmap(organization_and_id)
47
- Maestrano::Connector::Rails::IdMap.find_by(names_hash.merge(organization_and_id))
48
- end
49
-
50
- def create_idmap_from_external_entity(entity, organization)
51
- h = names_hash.merge({
52
- external_id: get_id_from_external_entity_hash(entity),
53
- name: object_name_from_external_entity_hash(entity),
54
- organization_id: organization.id
55
- })
56
- Maestrano::Connector::Rails::IdMap.create(h)
57
- end
58
-
59
- def create_idmap_from_connec_entity(entity, organization)
60
- h = names_hash.merge({
61
- connec_id: entity['id'],
62
- name: object_name_from_connec_entity_hash(entity),
63
- organization_id: organization.id
64
- })
65
- Maestrano::Connector::Rails::IdMap.create(h)
66
- end
67
126
  # ----------------------------------------------
68
127
  # Connec! methods
69
128
  # ----------------------------------------------
70
- def normalized_connec_entity_name
71
- normalize_connec_entity_name(connec_entity_name)
72
- end
73
-
74
- def normalize_connec_entity_name(connec_entity_name)
75
- if singleton?
76
- connec_entity_name.downcase
77
- else
78
- connec_entity_name.downcase.pluralize
79
- end
80
- end
81
-
82
129
  def get_connec_entities(client, last_synchronization, organization, opts={})
83
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching Connec! #{connec_entity_name}")
130
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching Connec! #{self.class.connec_entity_name}")
84
131
 
85
132
  entities = []
86
133
 
87
134
  # Fetch first page
88
135
  if last_synchronization.blank? || opts[:full_sync]
89
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{connec_entity_name}, fetching all data")
90
- response = client.get("/#{normalized_connec_entity_name}")
136
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{self.class.connec_entity_name}, fetching all data")
137
+ response = client.get("/#{self.class.normalized_connec_entity_name}")
91
138
  else
92
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{connec_entity_name}, fetching data since #{last_synchronization.updated_at.iso8601}")
139
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{self.class.connec_entity_name}, fetching data since #{last_synchronization.updated_at.iso8601}")
93
140
  query_param = URI.encode("$filter=updated_at gt '#{last_synchronization.updated_at.iso8601}'")
94
- response = client.get("/#{normalized_connec_entity_name}?#{query_param}")
141
+ response = client.get("/#{self.class.normalized_connec_entity_name}?#{query_param}")
95
142
  end
96
- raise "No data received from Connec! when trying to fetch #{connec_entity_name.pluralize}" unless response
143
+ raise "No data received from Connec! when trying to fetch #{self.class.connec_entity_name.pluralize}" unless response
97
144
 
98
145
  response_hash = JSON.parse(response.body)
99
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received first page entity=#{connec_entity_name}, response=#{response.body}")
100
- if response_hash["#{normalized_connec_entity_name}"]
101
- entities << response_hash["#{normalized_connec_entity_name}"]
146
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received first page entity=#{self.class.connec_entity_name}, response=#{response.body}")
147
+ if response_hash["#{self.class.normalized_connec_entity_name}"]
148
+ entities << response_hash["#{self.class.normalized_connec_entity_name}"]
102
149
  else
103
- raise "Received unrecognized Connec! data when trying to fetch #{connec_entity_name.pluralize}"
150
+ raise "Received unrecognized Connec! data when trying to fetch #{self.class.connec_entity_name.pluralize}"
104
151
  end
105
152
 
106
153
  # Fetch subsequent pages
107
154
  while response_hash['pagination'] && response_hash['pagination']['next']
108
155
  # ugly way to convert https://api-connec/api/v2/group_id/organizations?next_page_params to /organizations?next_page_params
109
- next_page = response_hash['pagination']['next'].gsub(/^(.*)\/#{normalized_connec_entity_name}/, normalized_connec_entity_name)
156
+ next_page = response_hash['pagination']['next'].gsub(/^(.*)\/#{self.class.normalized_connec_entity_name}/, self.class.normalized_connec_entity_name)
110
157
  response = client.get(next_page)
111
158
 
112
- raise "No data received from Connec! when trying to fetch subsequent page of #{connec_entity_name.pluralize}" unless response
113
- Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received next page entity=#{connec_entity_name}, response=#{response.body}")
159
+ raise "No data received from Connec! when trying to fetch subsequent page of #{self.class.connec_entity_name.pluralize}" unless response
160
+ Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "received next page entity=#{self.class.connec_entity_name}, response=#{response.body}")
114
161
 
115
162
  response_hash = JSON.parse(response.body)
116
- if response_hash["#{normalized_connec_entity_name}"]
117
- entities << response_hash["#{normalized_connec_entity_name}"]
163
+ if response_hash["#{self.class.normalized_connec_entity_name}"]
164
+ entities << response_hash["#{self.class.normalized_connec_entity_name}"]
118
165
  else
119
- raise "Received unrecognized Connec! data when trying to fetch subsequent page of #{connec_entity_name.pluralize}"
166
+ raise "Received unrecognized Connec! data when trying to fetch subsequent page of #{self.class.connec_entity_name.pluralize}"
120
167
  end
121
168
  end
122
169
 
123
170
  entities = entities.flatten
124
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received data: Source=Connec!, Entity=#{connec_entity_name}, Data=#{entities}")
171
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received data: Source=Connec!, Entity=#{self.class.connec_entity_name}, Data=#{entities}")
125
172
  entities
126
173
  end
127
174
 
128
175
  def push_entities_to_connec(connec_client, mapped_external_entities_with_idmaps, organization)
129
- push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, connec_entity_name, organization)
176
+ push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, self.class.connec_entity_name, organization)
130
177
  end
131
178
 
132
179
  def push_entities_to_connec_to(connec_client, mapped_external_entities_with_idmaps, connec_entity_name, organization)
133
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending #{@@external_name} #{external_entity_name.pluralize} to Connec! #{connec_entity_name.pluralize}")
180
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending #{Maestrano::Connector::Rails::External.external_name} #{self.class.external_entity_name.pluralize} to Connec! #{connec_entity_name.pluralize}")
134
181
  mapped_external_entities_with_idmaps.each do |mapped_external_entity_with_idmap|
135
182
  external_entity = mapped_external_entity_with_idmap[:entity]
136
183
  idmap = mapped_external_entity_with_idmap[:idmap]
137
184
 
138
185
  begin
139
186
  if idmap.connec_id.blank?
140
- connec_entity = create_connec_entity(connec_client, external_entity, normalize_connec_entity_name(connec_entity_name), organization)
141
- idmap.update_attributes(connec_id: connec_entity['id'], connec_entity: connec_entity_name.downcase, last_push_to_connec: Time.now, message: nil)
187
+ connec_entity = create_connec_entity(connec_client, external_entity, self.class.normalize_connec_entity_name(connec_entity_name), organization)
188
+ idmap.update_attributes(connec_id: connec_entity['id'], last_push_to_connec: Time.now, message: nil)
142
189
  else
143
- connec_entity = update_connec_entity(connec_client, external_entity, idmap.connec_id, normalize_connec_entity_name(connec_entity_name), organization)
190
+ connec_entity = update_connec_entity(connec_client, external_entity, idmap.connec_id, self.class.normalize_connec_entity_name(connec_entity_name), organization)
144
191
  idmap.update_attributes(last_push_to_connec: Time.now, message: nil)
145
192
  end
146
193
  rescue => e
147
194
  # Store Connec! error if any
195
+ Maestrano::Connector::Rails::ConnectorLogger.log('error', organization, "Error while pushing to Connec!: #{e}")
148
196
  idmap.update_attributes(message: e.message)
149
197
  end
150
198
  end
@@ -167,18 +215,18 @@ module Maestrano::Connector::Rails::Concerns::Entity
167
215
  end
168
216
 
169
217
  def map_to_external_with_idmap(entity, organization)
170
- idmap = find_idmap({connec_id: entity['id'], organization_id: organization.id})
218
+ idmap = self.class.find_idmap({connec_id: entity['id'], organization_id: organization.id})
171
219
 
172
220
  if idmap
173
- idmap.update(name: object_name_from_connec_entity_hash(entity))
221
+ idmap.update(name: self.class.object_name_from_connec_entity_hash(entity))
174
222
  if (!idmap.to_external) || (idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at'])
175
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{connec_entity_name} : #{entity}")
223
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{self.class.connec_entity_name} : #{entity}")
176
224
  nil
177
225
  else
178
226
  {entity: map_to_external(entity, organization), idmap: idmap}
179
227
  end
180
228
  else
181
- {entity: map_to_external(entity, organization), idmap: create_idmap_from_connec_entity(entity, organization)}
229
+ {entity: map_to_external(entity, organization), idmap: self.class.create_idmap_from_connec_entity(entity, organization)}
182
230
  end
183
231
  end
184
232
 
@@ -186,16 +234,16 @@ module Maestrano::Connector::Rails::Concerns::Entity
186
234
  # External methods
187
235
  # ----------------------------------------------
188
236
  def get_external_entities(client, last_synchronization, organization, opts={})
189
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching #{@@external_name} #{external_entity_name.pluralize}")
237
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching #{Maestrano::Connector::Rails::External.external_name} #{self.class.external_entity_name.pluralize}")
190
238
  raise "Not implemented"
191
239
  end
192
240
 
193
241
  def push_entities_to_external(external_client, mapped_connec_entities_with_idmaps, organization)
194
- push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, external_entity_name, organization)
242
+ push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, self.class.external_entity_name, organization)
195
243
  end
196
244
 
197
245
  def push_entities_to_external_to(external_client, mapped_connec_entities_with_idmaps, external_entity_name, organization)
198
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending Connec! #{connec_entity_name.pluralize} to #{@@external_name} #{external_entity_name.pluralize}")
246
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending Connec! #{self.class.connec_entity_name.pluralize} to #{Maestrano::Connector::Rails::External.external_name} #{external_entity_name.pluralize}")
199
247
  mapped_connec_entities_with_idmaps.each do |mapped_connec_entity_with_idmap|
200
248
  push_entity_to_external(external_client, mapped_connec_entity_with_idmap, external_entity_name, organization)
201
249
  end
@@ -208,35 +256,27 @@ module Maestrano::Connector::Rails::Concerns::Entity
208
256
  begin
209
257
  if idmap.external_id.blank?
210
258
  external_id = create_external_entity(external_client, connec_entity, external_entity_name, organization)
211
- idmap.update_attributes(external_id: external_id, external_entity: external_entity_name.downcase, last_push_to_external: Time.now, message: nil)
259
+ idmap.update_attributes(external_id: external_id, last_push_to_external: Time.now, message: nil)
212
260
  else
213
261
  update_external_entity(external_client, connec_entity, idmap.external_id, external_entity_name, organization)
214
262
  idmap.update_attributes(last_push_to_external: Time.now, message: nil)
215
263
  end
216
264
  rescue => e
217
265
  # Store External error
266
+ Maestrano::Connector::Rails::ConnectorLogger.log('error', organization, "Error while pushing to #{Maestrano::Connector::Rails::External.external_name}: #{e}")
218
267
  idmap.update_attributes(message: e.message)
219
268
  end
220
269
  end
221
270
 
222
271
  def create_external_entity(client, mapped_connec_entity, external_entity_name, organization)
223
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending create #{external_entity_name}: #{mapped_connec_entity} to #{@@external_name}")
272
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending create #{external_entity_name}: #{mapped_connec_entity} to #{Maestrano::Connector::Rails::External.external_name}")
224
273
  raise "Not implemented"
225
274
  end
226
275
 
227
276
  def update_external_entity(client, mapped_connec_entity, external_id, external_entity_name, organization)
228
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending update #{external_entity_name} (id=#{external_id}): #{mapped_connec_entity} to #{@@external_name}")
277
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending update #{external_entity_name} (id=#{external_id}): #{mapped_connec_entity} to #{Maestrano::Connector::Rails::External.external_name}")
229
278
  raise "Not implemented"
230
279
  end
231
-
232
- def get_id_from_external_entity_hash(entity)
233
- raise "Not implemented"
234
- end
235
-
236
- def get_last_update_date_from_external_entity_hash(entity)
237
- raise "Not implemented"
238
- end
239
-
240
280
  # ----------------------------------------------
241
281
  # General methods
242
282
  # ----------------------------------------------
@@ -245,25 +285,25 @@ module Maestrano::Connector::Rails::Concerns::Entity
245
285
  # * Maps not discarded entities and associates them with their idmap, or create one if there isn't any
246
286
  # * Return a hash {connec_entities: [], external_entities: []}
247
287
  def consolidate_and_map_data(connec_entities, external_entities, organization, opts={})
248
- return consolidate_and_map_singleton(connec_entities, external_entities, organization, opts) if singleton?
288
+ return consolidate_and_map_singleton(connec_entities, external_entities, organization, opts) if self.class.singleton?
249
289
 
250
290
  mapped_external_entities = external_entities.map{|entity|
251
- idmap = find_idmap({external_id: get_id_from_external_entity_hash(entity), organization_id: organization.id})
291
+ idmap = self.class.find_idmap({external_id: self.class.id_from_external_entity_hash(entity), organization_id: organization.id})
252
292
  # No idmap: creating one, nothing else to do
253
293
  if idmap
254
- idmap.update(name: object_name_from_external_entity_hash(entity))
294
+ idmap.update(name: self.class.object_name_from_external_entity_hash(entity))
255
295
  else
256
- next {entity: map_to_connec(entity, organization), idmap: create_idmap_from_external_entity(entity, organization)}
296
+ next {entity: map_to_connec(entity, organization), idmap: self.class.create_idmap_from_external_entity(entity, organization)}
257
297
  end
258
298
 
259
299
  # Not pushing entity to Connec!
260
300
  next nil unless idmap.to_connec
261
301
 
262
302
  # Entity has not been modified since its last push to connec!
263
- next nil if self.class.not_modified_since_last_push_to_connec(idmap, entity, self, organization)
303
+ next nil if self.class.not_modified_since_last_push_to_connec?(idmap, entity, self, organization)
264
304
 
265
305
  # Check for conflict with entities from connec!
266
- self.class.solve_conflict(entity, self, connec_entities, connec_entity_name, idmap, organization, opts)
306
+ self.class.solve_conflict(entity, self, connec_entities, self.class.connec_entity_name, idmap, organization, opts)
267
307
  }
268
308
  mapped_external_entities.compact!
269
309
 
@@ -278,7 +318,7 @@ module Maestrano::Connector::Rails::Concerns::Entity
278
318
  def consolidate_and_map_singleton(connec_entities, external_entities, organization, opts={})
279
319
  return {connec_entities: [], external_entities: []} if external_entities.empty? && connec_entities.empty?
280
320
 
281
- idmap = find_or_create_idmap({organization_id: organization.id})
321
+ idmap = self.class.find_or_create_idmap({organization_id: organization.id})
282
322
 
283
323
  if external_entities.empty?
284
324
  keep_external = false
@@ -290,61 +330,37 @@ module Maestrano::Connector::Rails::Concerns::Entity
290
330
  keep_external = self.class.is_external_more_recent?(connec_entities.first, external_entities.first, self)
291
331
  end
292
332
  if keep_external
293
- idmap.update(external_id: get_id_from_external_entity_hash(external_entities.first), name: object_name_from_external_entity_hash(external_entities.first))
333
+ idmap.update(external_id: self.class.id_from_external_entity_hash(external_entities.first), name: self.class.object_name_from_external_entity_hash(external_entities.first))
294
334
  return {connec_entities: [], external_entities: [{entity: map_to_connec(external_entities.first, organization), idmap: idmap}]}
295
335
  else
296
- idmap.update(connec_id: connec_entities.first['id'], name: object_name_from_connec_entity_hash(connec_entities.first))
336
+ idmap.update(connec_id: connec_entities.first['id'], name: self.class.object_name_from_connec_entity_hash(connec_entities.first))
297
337
  return {connec_entities: [{entity: map_to_external(connec_entities.first, organization), idmap: idmap}], external_entities: []}
298
338
  end
299
339
  end
300
340
 
301
341
  # ----------------------------------------------
302
- # Entity specific methods
303
- # Those methods need to be define in each entity
342
+ # After and before sync
304
343
  # ----------------------------------------------
305
- # Is this resource a singleton (in Connec!)?
306
- def singleton?
307
- false
344
+ def before_sync(connec_client, external_client, organization)
345
+ # Does nothing by default
308
346
  end
309
347
 
310
- # Entity name in Connec!
311
- def connec_entity_name
312
- raise "Not implemented"
348
+ def after_sync(connec_client, external_client, organization)
349
+ # Does nothing by default
313
350
  end
314
351
 
315
- # Entity name in external system
316
- def external_entity_name
317
- raise "Not implemented"
318
- end
319
-
320
- # Entity Mapper Class
321
- def mapper_class
322
- raise "Not implemented"
323
- end
324
-
325
- # Return a string representing the object from a connec! entity hash
326
- def object_name_from_connec_entity_hash(entity)
327
- raise "Not implemented"
328
- end
329
-
330
- # Return a string representing the object from an external entity hash
331
- def object_name_from_external_entity_hash(entity)
332
- raise "Not implemented"
333
- end
334
-
335
-
336
352
  # ----------------------------------------------
337
353
  # Internal helper methods
338
354
  # ----------------------------------------------
339
355
  module ClassMethods
340
- def not_modified_since_last_push_to_connec(idmap, entity, entity_instance, organization)
341
- result = idmap.last_push_to_connec && idmap.last_push_to_connec > entity_instance.get_last_update_date_from_external_entity_hash(entity)
342
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard #{entity_instance.external_entity_name} : #{entity}") if result
356
+ def not_modified_since_last_push_to_connec?(idmap, entity, entity_instance, organization)
357
+ result = idmap.last_push_to_connec && idmap.last_push_to_connec > entity_instance.class.last_update_date_from_external_entity_hash(entity)
358
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard #{Maestrano::Connector::Rails::External::external_name} #{entity_instance.class.external_entity_name} : #{entity}") if result
343
359
  result
344
360
  end
345
361
 
346
362
  def is_external_more_recent?(connec_entity, external_entity, entity_instance)
347
- connec_entity['updated_at'] < entity_instance.get_last_update_date_from_external_entity_hash(external_entity)
363
+ connec_entity['updated_at'] < entity_instance.class.last_update_date_from_external_entity_hash(external_entity)
348
364
  end
349
365
 
350
366
  def solve_conflict(external_entity, entity_instance, connec_entities, connec_entity_name, idmap, organization, opts)
@@ -357,11 +373,11 @@ module Maestrano::Connector::Rails::Concerns::Entity
357
373
  end
358
374
 
359
375
  if keep_external
360
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{entity_instance.external_entity_name} #{external_entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from external kept")
376
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{Maestrano::Connector::Rails::External::external_name} #{entity_instance.class.external_entity_name} #{external_entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from external kept")
361
377
  connec_entities.delete(connec_entity)
362
378
  entity_instance.map_external_entity_with_idmap(external_entity, connec_entity_name, idmap, organization)
363
379
  else
364
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{entity_instance.external_entity_name} #{external_entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from Connec! kept")
380
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{Maestrano::Connector::Rails::External::external_name} #{entity_instance.class.external_entity_name} #{external_entity} and Connec! #{connec_entity_name} #{connec_entity}. Entity from Connec! kept")
365
381
  nil
366
382
  end
367
383