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
@@ -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
|