maestrano-connector-rails 0.4.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
@@ -1,14 +1,14 @@
|
|
1
1
|
module Maestrano::Connector::Rails::Concerns::Entity
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
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
|
11
10
|
|
11
|
+
module ClassMethods
|
12
12
|
# ----------------------------------------------
|
13
13
|
# IdMap methods
|
14
14
|
# ----------------------------------------------
|
@@ -19,34 +19,18 @@ module Maestrano::Connector::Rails::Concerns::Entity
|
|
19
19
|
}
|
20
20
|
end
|
21
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
22
|
# organization_and_id can be either:
|
27
23
|
# * {connec_id: 'id', organization_id: 'id'}
|
28
24
|
# * {external_id: 'id', organization_id: 'id'}
|
29
25
|
# Needs to include either connec_entity or external_entity for complex entities
|
26
|
+
def find_or_create_idmap(organization_and_id)
|
27
|
+
Maestrano::Connector::Rails::IdMap.find_or_create_by(names_hash.merge(organization_and_id))
|
28
|
+
end
|
30
29
|
def find_idmap(organization_and_id)
|
31
30
|
Maestrano::Connector::Rails::IdMap.find_by(names_hash.merge(organization_and_id))
|
32
31
|
end
|
33
|
-
|
34
|
-
|
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)
|
32
|
+
def create_idmap(organization_and_id)
|
33
|
+
Maestrano::Connector::Rails::IdMap.create(names_hash.merge(organization_and_id))
|
50
34
|
end
|
51
35
|
|
52
36
|
# ----------------------------------------------
|
@@ -114,11 +98,16 @@ module Maestrano::Connector::Rails::Concerns::Entity
|
|
114
98
|
raise "Not implemented"
|
115
99
|
end
|
116
100
|
|
117
|
-
#
|
101
|
+
# An array of connec fields that are references
|
118
102
|
def references
|
119
103
|
[]
|
120
104
|
end
|
121
105
|
|
106
|
+
# An array of fields for smart merging. See connec! documentation
|
107
|
+
def connec_matching_fields
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
122
111
|
def can_read_connec?
|
123
112
|
can_write_external?
|
124
113
|
end
|
@@ -135,10 +124,6 @@ module Maestrano::Connector::Rails::Concerns::Entity
|
|
135
124
|
true
|
136
125
|
end
|
137
126
|
|
138
|
-
def can_update_connec?
|
139
|
-
true
|
140
|
-
end
|
141
|
-
|
142
127
|
def can_update_external?
|
143
128
|
true
|
144
129
|
end
|
@@ -147,24 +132,19 @@ module Maestrano::Connector::Rails::Concerns::Entity
|
|
147
132
|
# ----------------------------------------------
|
148
133
|
# Mapper methods
|
149
134
|
# ----------------------------------------------
|
150
|
-
# Map a Connec! entity to the external
|
151
|
-
def map_to_external(entity
|
152
|
-
|
153
|
-
self.class.
|
154
|
-
|
155
|
-
end
|
156
|
-
|
157
|
-
self.class.mapper_class.normalize(entity).merge(ref_hash)
|
135
|
+
# Map a Connec! entity to the external model
|
136
|
+
def map_to_external(entity)
|
137
|
+
connec_id = entity[:__connec_id]
|
138
|
+
mapped_entity = self.class.mapper_class.normalize(entity)
|
139
|
+
(connec_id ? mapped_entity.merge(__connec_id: connec_id) : mapped_entity).with_indifferent_access
|
158
140
|
end
|
159
141
|
|
160
|
-
# Map an external entity to Connec!
|
161
|
-
def map_to_connec(entity
|
162
|
-
|
163
|
-
self.class.references
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
self.class.mapper_class.denormalize(entity).merge(ref_hash)
|
142
|
+
# Map an external entity to Connec! model
|
143
|
+
def map_to_connec(entity)
|
144
|
+
mapped_entity = self.class.mapper_class.denormalize(entity).merge(id: self.class.id_from_external_entity_hash(entity))
|
145
|
+
folded_entity = Maestrano::Connector::Rails::ConnecHelper.fold_references(mapped_entity, self.class.references, @organization)
|
146
|
+
folded_entity.merge!(opts: (mapped_entity[:opts] || {}).merge(matching_fields: self.class.connec_matching_fields)) if self.class.connec_matching_fields
|
147
|
+
folded_entity
|
168
148
|
end
|
169
149
|
|
170
150
|
# ----------------------------------------------
|
@@ -174,324 +154,346 @@ module Maestrano::Connector::Rails::Concerns::Entity
|
|
174
154
|
# * full_sync
|
175
155
|
# * $filter (see Connec! documentation)
|
176
156
|
# * $orderby (see Connec! documentation)
|
177
|
-
def get_connec_entities(
|
178
|
-
return []
|
157
|
+
def get_connec_entities(last_synchronization)
|
158
|
+
return [] if @opts[:skip_connec] || !self.class.can_read_connec?
|
179
159
|
|
180
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Fetching Connec! #{self.class.connec_entity_name}")
|
160
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Fetching Connec! #{self.class.connec_entity_name}")
|
181
161
|
|
182
162
|
entities = []
|
183
163
|
query_params = {}
|
184
|
-
query_params[:$orderby] = opts[:$orderby] if opts[:$orderby]
|
164
|
+
query_params[:$orderby] = @opts[:$orderby] if @opts[:$orderby]
|
185
165
|
|
186
166
|
# Fetch first page
|
187
|
-
|
188
|
-
|
189
|
-
|
167
|
+
page_number = 0
|
168
|
+
if last_synchronization.blank? || @opts[:full_sync]
|
169
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('debug', @organization, "entity=#{self.class.connec_entity_name}, fetching all data")
|
170
|
+
query_params[:$filter] = @opts[:$filter] if @opts[:$filter]
|
190
171
|
else
|
191
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "entity=#{self.class.connec_entity_name}, fetching data since #{last_synchronization.updated_at.iso8601}")
|
192
|
-
filter = "updated_at gt '#{last_synchronization.updated_at.iso8601}'"
|
193
|
-
filter += " and #{opts[:$filter]}" if opts[:$filter]
|
194
|
-
query_params[:$filter] = filter
|
172
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('debug', @organization, "entity=#{self.class.connec_entity_name}, fetching data since #{last_synchronization.updated_at.iso8601}")
|
173
|
+
query_params[:$filter] = "updated_at gt '#{last_synchronization.updated_at.iso8601}'" + (@opts[:$filter] ? " and #{@opts[:$filter]}" : '')
|
195
174
|
end
|
196
|
-
response = client.get("/#{self.class.normalized_connec_entity_name}?#{query_params.to_query}")
|
197
|
-
raise "No data received from Connec! when trying to fetch #{self.class.normalized_connec_entity_name}" unless response && !response.body.blank?
|
198
175
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
entities << response_hash["#{self.class.normalized_connec_entity_name}"]
|
203
|
-
else
|
204
|
-
raise "Received unrecognized Connec! data when trying to fetch #{self.class.normalized_connec_entity_name}"
|
205
|
-
end
|
176
|
+
uri = "/#{self.class.normalized_connec_entity_name}?#{query_params.to_query}"
|
177
|
+
response_hash = fetch_connec(uri, 0)
|
178
|
+
entities = response_hash["#{self.class.normalized_connec_entity_name}"]
|
206
179
|
|
207
180
|
# Fetch subsequent pages
|
208
181
|
while response_hash['pagination'] && response_hash['pagination']['next']
|
182
|
+
page_number += 1
|
209
183
|
# ugly way to convert https://api-connec/api/v2/group_id/organizations?next_page_params to /organizations?next_page_params
|
210
184
|
next_page = response_hash['pagination']['next'].gsub(/^(.*)\/#{self.class.normalized_connec_entity_name}/, self.class.normalized_connec_entity_name)
|
211
|
-
response = client.get(next_page)
|
212
185
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
response_hash = JSON.parse(response.body)
|
217
|
-
if response_hash["#{self.class.normalized_connec_entity_name}"]
|
218
|
-
entities << response_hash["#{self.class.normalized_connec_entity_name}"]
|
219
|
-
else
|
220
|
-
raise "Received unrecognized Connec! data when trying to fetch subsequent page of #{self.class.connec_entity_name.pluralize}"
|
221
|
-
end
|
186
|
+
response_hash = fetch_connec(uri, page_number)
|
187
|
+
entities << response_hash["#{self.class.normalized_connec_entity_name}"]
|
222
188
|
end
|
223
189
|
|
224
|
-
entities
|
225
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received data: Source=Connec!, Entity=#{self.class.connec_entity_name}, Data=#{entities}")
|
190
|
+
entities.flatten!
|
191
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Received data: Source=Connec!, Entity=#{self.class.connec_entity_name}, Data=#{entities}")
|
226
192
|
entities
|
227
193
|
end
|
228
194
|
|
229
|
-
def push_entities_to_connec(
|
230
|
-
push_entities_to_connec_to(
|
195
|
+
def push_entities_to_connec(mapped_external_entities_with_idmaps)
|
196
|
+
push_entities_to_connec_to(mapped_external_entities_with_idmaps, self.class.connec_entity_name)
|
231
197
|
end
|
232
198
|
|
233
|
-
def push_entities_to_connec_to(
|
199
|
+
def push_entities_to_connec_to(mapped_external_entities_with_idmaps, connec_entity_name)
|
234
200
|
return unless self.class.can_write_connec?
|
235
201
|
|
236
|
-
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}")
|
202
|
+
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}")
|
237
203
|
|
238
|
-
|
239
|
-
|
240
|
-
while start < mapped_external_entities_with_idmaps.size
|
241
|
-
# Prepare batch request
|
242
|
-
batch_entities = mapped_external_entities_with_idmaps.slice(start, request_per_call)
|
243
|
-
batch_request = {sequential: true, ops: []}
|
244
|
-
batch_entities.each do |mapped_external_entity_with_idmap|
|
245
|
-
external_entity = mapped_external_entity_with_idmap[:entity]
|
246
|
-
idmap = mapped_external_entity_with_idmap[:idmap]
|
247
|
-
if idmap.connec_id.blank?
|
248
|
-
batch_request[:ops] << batch_op('post', external_entity, nil, self.class.normalize_connec_entity_name(connec_entity_name), organization)
|
249
|
-
else
|
250
|
-
next unless self.class.can_update_connec?
|
251
|
-
batch_request[:ops] << batch_op('put', external_entity, idmap.connec_id, self.class.normalize_connec_entity_name(connec_entity_name), organization)
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
# Batch call
|
256
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending batch request to Connec! for #{self.class.normalize_connec_entity_name(connec_entity_name)}. Batch_request_size: #{batch_request[:ops].size}. Call_number: #{(start/request_per_call) + 1}")
|
257
|
-
response = connec_client.batch(batch_request)
|
258
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('debug', organization, "Received batch response from Connec! for #{self.class.normalize_connec_entity_name(connec_entity_name)}: #{response}")
|
259
|
-
raise "No data received from Connec! when trying to send batch request for #{self.class.connec_entity_name.pluralize}" unless response && !response.body.blank?
|
260
|
-
response = JSON.parse(response.body)
|
261
|
-
|
262
|
-
# Parse barch response
|
263
|
-
response['results'].each_with_index do |result, index|
|
264
|
-
if result['status'] == 200
|
265
|
-
batch_entities[index][:idmap].update_attributes(last_push_to_connec: Time.now, message: nil)
|
266
|
-
elsif result['status'] == 201
|
267
|
-
batch_entities[index][:idmap].update_attributes(connec_id: result['body'][self.class.normalize_connec_entity_name(connec_entity_name)]['id'], last_push_to_connec: Time.now, message: nil)
|
268
|
-
else
|
269
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('error', organization, "Error while pushing to Connec!: #{result['body']}")
|
270
|
-
batch_entities[index][:idmap].update_attributes(message: result['body'].truncate(255))
|
271
|
-
end
|
272
|
-
end
|
273
|
-
start += request_per_call
|
274
|
-
end
|
204
|
+
proc = lambda{|mapped_external_entity_with_idmap| batch_op('post', mapped_external_entity_with_idmap[:entity], nil, self.class.normalize_connec_entity_name(connec_entity_name))}
|
205
|
+
batch_calls(mapped_external_entities_with_idmaps, proc, connec_entity_name)
|
275
206
|
end
|
276
207
|
|
277
|
-
def batch_op(method, mapped_external_entity, id, connec_entity_name
|
278
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending #{method.upcase} #{connec_entity_name}: #{mapped_external_entity} to Connec! (Preparing batch request)")
|
208
|
+
def batch_op(method, mapped_external_entity, id, connec_entity_name)
|
209
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Sending #{method.upcase} #{connec_entity_name}: #{mapped_external_entity} to Connec! (Preparing batch request)")
|
279
210
|
{
|
280
211
|
method: method,
|
281
|
-
url: "/api/v2/#{organization.uid}/#{connec_entity_name}"
|
212
|
+
url: "/api/v2/#{@organization.uid}/#{connec_entity_name}/#{id}", # id should be nil for POST
|
282
213
|
params: {
|
283
214
|
"#{connec_entity_name}".to_sym => mapped_external_entity
|
284
215
|
}
|
285
216
|
}
|
286
217
|
end
|
287
218
|
|
288
|
-
def map_to_external_with_idmap(entity, organization)
|
289
|
-
idmap = self.class.find_idmap({connec_id: entity['id'], organization_id: organization.id})
|
290
|
-
|
291
|
-
if idmap
|
292
|
-
return nil if idmap.external_inactive || !idmap.to_external
|
293
|
-
|
294
|
-
if idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
|
295
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{self.class.connec_entity_name} : #{entity}")
|
296
|
-
nil
|
297
|
-
else
|
298
|
-
idmap.update(name: self.class.object_name_from_connec_entity_hash(entity))
|
299
|
-
{entity: map_to_external(entity, organization), idmap: idmap}
|
300
|
-
end
|
301
|
-
else
|
302
|
-
{entity: map_to_external(entity, organization), idmap: self.class.create_idmap_from_connec_entity(entity, organization)}
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
219
|
# ----------------------------------------------
|
307
220
|
# External methods
|
308
221
|
# ----------------------------------------------
|
309
|
-
def
|
310
|
-
return []
|
311
|
-
|
222
|
+
def get_external_entities_wrapper(last_synchronization)
|
223
|
+
return [] if @opts[:skip_external] || !self.class.can_read_external?
|
224
|
+
get_external_entities(last_synchronization)
|
225
|
+
end
|
226
|
+
|
227
|
+
def get_external_entities(last_synchronization)
|
228
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Fetching #{Maestrano::Connector::Rails::External.external_name} #{self.class.external_entity_name.pluralize}")
|
312
229
|
raise "Not implemented"
|
313
230
|
end
|
314
231
|
|
315
|
-
def push_entities_to_external(
|
316
|
-
push_entities_to_external_to(
|
232
|
+
def push_entities_to_external(mapped_connec_entities_with_idmaps)
|
233
|
+
push_entities_to_external_to(mapped_connec_entities_with_idmaps, self.class.external_entity_name)
|
317
234
|
end
|
318
235
|
|
319
|
-
def push_entities_to_external_to(
|
236
|
+
def push_entities_to_external_to(mapped_connec_entities_with_idmaps, external_entity_name)
|
320
237
|
return unless self.class.can_write_external?
|
321
|
-
|
322
|
-
|
323
|
-
|
238
|
+
|
239
|
+
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}")
|
240
|
+
ids_to_send_to_connec = mapped_connec_entities_with_idmaps.map{ |mapped_connec_entity_with_idmap|
|
241
|
+
push_entity_to_external(mapped_connec_entity_with_idmap, external_entity_name)
|
242
|
+
}.compact
|
243
|
+
|
244
|
+
unless ids_to_send_to_connec.empty?
|
245
|
+
# Send the external ids to connec if it was a creation
|
246
|
+
proc = lambda{|id| batch_op('put', {id: [Maestrano::Connector::Rails::ConnecHelper.id_hash(id[:external_id], @organization)]}, id[:connec_id], self.class.normalize_connec_entity_name(self.class.connec_entity_name)) }
|
247
|
+
batch_calls(ids_to_send_to_connec, proc, self.class.connec_entity_name, true)
|
324
248
|
end
|
325
249
|
end
|
326
250
|
|
327
|
-
|
251
|
+
|
252
|
+
def push_entity_to_external(mapped_connec_entity_with_idmap, external_entity_name)
|
328
253
|
idmap = mapped_connec_entity_with_idmap[:idmap]
|
329
|
-
|
254
|
+
mapped_connec_entity = mapped_connec_entity_with_idmap[:entity]
|
330
255
|
|
331
256
|
begin
|
257
|
+
# Create and return id to send to connec!
|
332
258
|
if idmap.external_id.blank?
|
333
|
-
|
334
|
-
|
259
|
+
connec_id = mapped_connec_entity_with_idmap[:idmap].connec_id
|
260
|
+
external_id = create_external_entity(mapped_connec_entity, external_entity_name)
|
261
|
+
idmap.update(external_id: external_id, last_push_to_external: Time.now, message: nil)
|
262
|
+
return {connec_id: connec_id, external_id: external_id, idmap: idmap}
|
263
|
+
|
264
|
+
# Update
|
335
265
|
else
|
336
266
|
return unless self.class.can_update_external?
|
337
|
-
update_external_entity(
|
338
|
-
|
267
|
+
update_external_entity(mapped_connec_entity, idmap.external_id, external_entity_name)
|
268
|
+
|
269
|
+
# Return the id to send it to connec! if the first push of a singleton
|
270
|
+
if self.class.singleton? && idmap.last_push_to_external.nil?
|
271
|
+
connec_id = mapped_connec_entity_with_idmap[:idmap].connec_id
|
272
|
+
idmap.update(last_push_to_external: Time.now, message: nil)
|
273
|
+
return {connec_id: connec_id, external_id: idmap.external_id}
|
274
|
+
else
|
275
|
+
idmap.update(last_push_to_external: Time.now, message: nil)
|
276
|
+
end
|
277
|
+
|
339
278
|
end
|
340
279
|
rescue => e
|
341
280
|
# Store External error
|
342
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('error', organization, "Error while pushing to #{Maestrano::Connector::Rails::External.external_name}: #{e}")
|
343
|
-
idmap.
|
281
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('error', @organization, "Error while pushing to #{Maestrano::Connector::Rails::External.external_name}: #{e}")
|
282
|
+
idmap.update(message: e.message.truncate(255))
|
344
283
|
end
|
284
|
+
nil
|
345
285
|
end
|
346
286
|
|
347
|
-
def create_external_entity(
|
348
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Sending create #{external_entity_name}: #{mapped_connec_entity} to #{Maestrano::Connector::Rails::External.external_name}")
|
287
|
+
def create_external_entity(mapped_connec_entity, external_entity_name)
|
288
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Sending create #{external_entity_name}: #{mapped_connec_entity} to #{Maestrano::Connector::Rails::External.external_name}")
|
349
289
|
raise "Not implemented"
|
350
290
|
end
|
351
291
|
|
352
|
-
def update_external_entity(
|
353
|
-
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}")
|
292
|
+
def update_external_entity(mapped_connec_entity, external_id, external_entity_name)
|
293
|
+
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}")
|
354
294
|
raise "Not implemented"
|
355
295
|
end
|
356
296
|
|
357
297
|
# This method is called during the webhook workflow only. It should return the array of filtered entities
|
358
298
|
# The aim is to have the same filtering as with the Connec! filters on API calls in the webhooks
|
359
|
-
def filter_connec_entities(entities
|
299
|
+
def filter_connec_entities(entities)
|
360
300
|
entities
|
361
301
|
end
|
302
|
+
|
362
303
|
# ----------------------------------------------
|
363
304
|
# General methods
|
364
305
|
# ----------------------------------------------
|
365
306
|
# * Discards entities that do not need to be pushed because they have not been updated since their last push
|
366
307
|
# * Discards entities from one of the two source in case of conflict
|
367
308
|
# * Maps not discarded entities and associates them with their idmap, or create one if there isn't any
|
368
|
-
# *
|
369
|
-
def consolidate_and_map_data(connec_entities, external_entities
|
370
|
-
return consolidate_and_map_singleton(connec_entities, external_entities
|
309
|
+
# * Returns a hash {connec_entities: [], external_entities: []}
|
310
|
+
def consolidate_and_map_data(connec_entities, external_entities)
|
311
|
+
return consolidate_and_map_singleton(connec_entities, external_entities) if self.class.singleton?
|
312
|
+
|
313
|
+
mapped_connec_entities = consolidate_and_map_connec_entities(connec_entities, external_entities, self.class.references, self.class.external_entity_name)
|
314
|
+
mapped_external_entities = consolidate_and_map_external_entities(external_entities, self.class.connec_entity_name)
|
371
315
|
|
372
|
-
|
373
|
-
|
316
|
+
return {connec_entities: mapped_connec_entities, external_entities: mapped_external_entities}
|
317
|
+
end
|
318
|
+
|
319
|
+
def consolidate_and_map_connec_entities(connec_entities, external_entities, references, external_entity_name)
|
320
|
+
connec_entities.map{|entity|
|
321
|
+
entity = Maestrano::Connector::Rails::ConnecHelper.unfold_references(entity, references, @organization)
|
322
|
+
next nil unless entity
|
323
|
+
connec_id = entity.delete(:__connec_id)
|
374
324
|
|
375
|
-
|
376
|
-
|
377
|
-
next
|
325
|
+
if entity['id'].blank?
|
326
|
+
idmap = self.class.create_idmap(organization_id: @organization.id, name: self.class.object_name_from_connec_entity_hash(entity), external_entity: external_entity_name.downcase, connec_id: connec_id)
|
327
|
+
next map_connec_entity_with_idmap(entity, external_entity_name, idmap)
|
378
328
|
end
|
379
329
|
|
330
|
+
idmap = self.class.find_or_create_idmap(external_id: entity['id'], organization_id: @organization.id, external_entity: external_entity_name.downcase, connec_id: connec_id)
|
331
|
+
idmap.update(name: self.class.object_name_from_connec_entity_hash(entity))
|
332
|
+
|
333
|
+
next nil if idmap.external_inactive || !idmap.to_external || (!@opts[:full_sync] && not_modified_since_last_push_to_external?(idmap, entity))
|
334
|
+
|
335
|
+
# Check for conflict with entities from external
|
336
|
+
solve_conflict(entity, external_entities, external_entity_name, idmap)
|
337
|
+
}.compact
|
338
|
+
end
|
339
|
+
|
340
|
+
def consolidate_and_map_external_entities(external_entities, connec_entity_name)
|
341
|
+
external_entities.map{|entity|
|
342
|
+
entity_id = self.class.id_from_external_entity_hash(entity)
|
343
|
+
idmap = self.class.find_or_create_idmap(external_id: entity_id, organization_id: @organization.id, connec_entity: connec_entity_name.downcase)
|
344
|
+
|
380
345
|
# Not pushing entity to Connec!
|
381
346
|
next nil unless idmap.to_connec
|
382
347
|
|
383
348
|
# Not pushing to Connec! and flagging as inactive if inactive in external application
|
384
349
|
inactive = self.class.inactive_from_external_entity_hash?(entity)
|
385
|
-
idmap.update(external_inactive: inactive)
|
350
|
+
idmap.update(external_inactive: inactive, name: self.class.object_name_from_external_entity_hash(entity))
|
386
351
|
next nil if inactive
|
387
352
|
|
388
353
|
# Entity has not been modified since its last push to connec!
|
389
|
-
next nil if
|
354
|
+
next nil if !@opts[:full_sync] && not_modified_since_last_push_to_connec?(idmap, entity)
|
390
355
|
|
391
|
-
idmap.update(name: self.class.object_name_from_external_entity_hash(entity))
|
392
356
|
|
393
|
-
|
394
|
-
self.class.solve_conflict(entity, self, connec_entities, self.class.connec_entity_name, idmap, organization, opts)
|
357
|
+
map_external_entity_with_idmap(entity, connec_entity_name, idmap)
|
395
358
|
}.compact
|
396
|
-
|
397
|
-
mapped_connec_entities = connec_entities.map{|entity|
|
398
|
-
map_to_external_with_idmap(entity, organization)
|
399
|
-
}.compact
|
400
|
-
|
401
|
-
return {connec_entities: mapped_connec_entities, external_entities: mapped_external_entities}
|
402
359
|
end
|
403
360
|
|
404
|
-
def consolidate_and_map_singleton(connec_entities, external_entities
|
361
|
+
def consolidate_and_map_singleton(connec_entities, external_entities)
|
405
362
|
return {connec_entities: [], external_entities: []} if external_entities.empty? && connec_entities.empty?
|
406
363
|
|
407
|
-
idmap = self.class.find_or_create_idmap({organization_id: organization.id})
|
364
|
+
idmap = self.class.find_or_create_idmap({organization_id: @organization.id})
|
365
|
+
# No to_connec, to_external and inactive consideration here as we don't expect those workflow for singleton
|
408
366
|
|
409
367
|
if external_entities.empty?
|
410
368
|
keep_external = false
|
411
369
|
elsif connec_entities.empty?
|
412
370
|
keep_external = true
|
413
|
-
elsif
|
414
|
-
keep_external =
|
371
|
+
elsif @opts.has_key?(:connec_preemption)
|
372
|
+
keep_external = !@opts[:connec_preemption]
|
415
373
|
else
|
416
|
-
keep_external =
|
374
|
+
keep_external = !is_connec_more_recent?(connec_entities.first, external_entities.first)
|
417
375
|
end
|
376
|
+
|
418
377
|
if keep_external
|
419
378
|
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))
|
420
|
-
return {connec_entities: [], external_entities: [{entity: map_to_connec(external_entities.first
|
379
|
+
return {connec_entities: [], external_entities: [{entity: map_to_connec(external_entities.first), idmap: idmap}]}
|
421
380
|
else
|
422
|
-
|
423
|
-
|
381
|
+
entity = Maestrano::Connector::Rails::ConnecHelper.unfold_references(connec_entities.first, self.class.references, @organization)
|
382
|
+
idmap.update(name: self.class.object_name_from_connec_entity_hash(entity), connec_id: entity.delete(:__connec_id))
|
383
|
+
idmap.update(external_id: self.class.id_from_external_entity_hash(external_entities.first)) unless external_entities.empty?
|
384
|
+
return {connec_entities: [{entity: map_to_external(entity), idmap: idmap}], external_entities: []}
|
424
385
|
end
|
425
386
|
end
|
426
387
|
|
427
388
|
# ----------------------------------------------
|
428
389
|
# After and before sync
|
429
390
|
# ----------------------------------------------
|
430
|
-
def before_sync(
|
391
|
+
def before_sync(last_synchronization)
|
431
392
|
# Does nothing by default
|
432
393
|
end
|
433
394
|
|
434
|
-
def after_sync(
|
395
|
+
def after_sync(last_synchronization)
|
435
396
|
# Does nothing by default
|
436
397
|
end
|
437
398
|
|
399
|
+
|
438
400
|
# ----------------------------------------------
|
439
401
|
# Internal helper methods
|
440
402
|
# ----------------------------------------------
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
403
|
+
private
|
404
|
+
# array_with_idmap must be an array of hashes with a key idmap
|
405
|
+
# proc is a lambda to create a batch_op from an element of the array
|
406
|
+
def batch_calls(array_with_idmap, proc, connec_entity_name, id_update_only=false)
|
407
|
+
request_per_call = @opts[:request_per_batch_call] || 100
|
408
|
+
start = 0
|
409
|
+
while start < array_with_idmap.size
|
410
|
+
# Prepare batch request
|
411
|
+
batch_entities = array_with_idmap.slice(start, request_per_call)
|
412
|
+
batch_request = {sequential: true, ops: []}
|
413
|
+
|
414
|
+
batch_entities.each do |id|
|
415
|
+
batch_request[:ops] << proc.call(id)
|
416
|
+
end
|
417
|
+
|
418
|
+
# Batch call
|
419
|
+
log_info = id_update_only ? 'with only ids' : ''
|
420
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Sending batch request to Connec! #{log_info} for #{self.class.normalize_connec_entity_name(connec_entity_name)}. Batch_request_size: #{batch_request[:ops].size}. Call_number: #{(start/request_per_call) + 1}")
|
421
|
+
response = @connec_client.batch(batch_request)
|
422
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('debug', @organization, "Received batch response from Connec! for #{self.class.normalize_connec_entity_name(connec_entity_name)}: #{response}")
|
423
|
+
raise "No data received from Connec! when trying to send batch request #{log_info} for #{self.class.connec_entity_name.pluralize}" unless response && !response.body.blank?
|
424
|
+
response = JSON.parse(response.body)
|
425
|
+
|
426
|
+
# Parse batch response
|
427
|
+
response['results'].each_with_index do |result, index|
|
428
|
+
if result['status'] == 200
|
429
|
+
batch_entities[index][:idmap].update(connec_id: result['body'][self.class.normalize_connec_entity_name(connec_entity_name)]['id'].find{|id| id['provider'] == 'connec'}['id'], last_push_to_connec: Time.now, message: nil) unless id_update_only # id_update_only only apply for 200 as it's doing PUTs
|
430
|
+
elsif result['status'] == 201
|
431
|
+
batch_entities[index][:idmap].update(connec_id: result['body'][self.class.normalize_connec_entity_name(connec_entity_name)]['id'].find{|id| id['provider'] == 'connec'}['id'], last_push_to_connec: Time.now, message: nil)
|
432
|
+
else
|
433
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('error', @organization, "Error while pushing to Connec!: #{result['body']}")
|
434
|
+
batch_entities[index][:idmap].update(message: result['body'].to_s.truncate(255))
|
435
|
+
end
|
436
|
+
end
|
437
|
+
start += request_per_call
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def not_modified_since_last_push_to_connec?(idmap, entity)
|
442
|
+
not_modified = idmap.last_push_to_connec && idmap.last_push_to_connec > self.class.last_update_date_from_external_entity_hash(entity)
|
443
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Discard #{Maestrano::Connector::Rails::External::external_name} #{self.class.external_entity_name} : #{entity}") if not_modified
|
444
|
+
not_modified
|
445
|
+
end
|
446
|
+
|
447
|
+
def not_modified_since_last_push_to_external?(idmap, entity)
|
448
|
+
not_modified = idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
|
449
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Discard Connec! #{self.class.connec_entity_name} : #{entity}") if not_modified
|
450
|
+
not_modified
|
446
451
|
end
|
447
452
|
|
448
|
-
def
|
449
|
-
connec_entity['updated_at']
|
453
|
+
def is_connec_more_recent?(connec_entity, external_entity)
|
454
|
+
connec_entity['updated_at'] > self.class.last_update_date_from_external_entity_hash(external_entity)
|
450
455
|
end
|
451
456
|
|
452
|
-
def solve_conflict(
|
453
|
-
if
|
457
|
+
def solve_conflict(connec_entity, external_entities, external_entity_name, idmap)
|
458
|
+
if external_entity = external_entities.find{|external_entity| connec_entity['id'] == self.class.id_from_external_entity_hash(external_entity)}
|
454
459
|
# We keep the most recently updated entity
|
455
|
-
if
|
456
|
-
|
460
|
+
if @opts.has_key?(:connec_preemption)
|
461
|
+
keep_connec = @opts[:connec_preemption]
|
457
462
|
else
|
458
|
-
|
463
|
+
keep_connec = is_connec_more_recent?(connec_entity, external_entity)
|
459
464
|
end
|
460
465
|
|
461
|
-
if
|
462
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{Maestrano::Connector::Rails::External::external_name} #{
|
463
|
-
|
464
|
-
|
466
|
+
if keep_connec
|
467
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Conflict between #{Maestrano::Connector::Rails::External::external_name} #{external_entity_name} #{external_entity} and Connec! #{self.class.connec_entity_name} #{connec_entity}. Entity from external kept")
|
468
|
+
external_entities.delete(external_entity)
|
469
|
+
map_connec_entity_with_idmap(connec_entity, external_entity_name, idmap)
|
465
470
|
else
|
466
|
-
Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Conflict between #{Maestrano::Connector::Rails::External::external_name} #{
|
471
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('info', @organization, "Conflict between #{Maestrano::Connector::Rails::External::external_name} #{external_entity_name} #{external_entity} and Connec! #{self.class.connec_entity_name} #{connec_entity}. Entity from Connec! kept")
|
467
472
|
nil
|
468
473
|
end
|
469
474
|
|
470
475
|
else
|
471
|
-
|
476
|
+
map_connec_entity_with_idmap(connec_entity, external_entity_name, idmap)
|
472
477
|
end
|
473
478
|
end
|
474
479
|
|
475
|
-
def
|
476
|
-
|
477
|
-
|
478
|
-
field = field.split('/')
|
479
|
-
id = entity
|
480
|
-
field.each do |f|
|
481
|
-
id &&= id[f]
|
482
|
-
end
|
480
|
+
def map_connec_entity_with_idmap(connec_entity, external_entity_name, idmap)
|
481
|
+
{entity: map_to_external(connec_entity), idmap: idmap}
|
482
|
+
end
|
483
483
|
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
484
|
+
def map_external_entity_with_idmap(external_entity, connec_entity_name, idmap)
|
485
|
+
{entity: map_to_connec(external_entity), idmap: idmap}
|
486
|
+
end
|
487
|
+
|
488
|
+
def fetch_connec(uri, page_number)
|
489
|
+
response = @connec_client.get(uri)
|
490
|
+
raise "No data received from Connec! when trying to fetch page #{page_number} of #{self.class.normalized_connec_entity_name}" unless response && !response.body.blank?
|
491
|
+
|
492
|
+
response_hash = JSON.parse(response.body)
|
493
|
+
Maestrano::Connector::Rails::ConnectorLogger.log('debug', @organization, "received first page entity=#{self.class.connec_entity_name}, response=#{response_hash}")
|
494
|
+
raise "Received unrecognized Connec! data when trying to fetch page #{page_number} of #{self.class.normalized_connec_entity_name}: #{response_hash}" unless response_hash["#{self.class.normalized_connec_entity_name}"]
|
495
|
+
|
496
|
+
response_hash
|
491
497
|
end
|
492
|
-
end
|
493
498
|
|
494
|
-
def map_external_entity_with_idmap(external_entity, connec_entity_name, idmap, organization)
|
495
|
-
{entity: map_to_connec(external_entity, organization), idmap: idmap}
|
496
|
-
end
|
497
499
|
end
|