gooddata_marketo 0.0.1

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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +131 -0
  4. data/README.md +207 -0
  5. data/bin/Gemfile +10 -0
  6. data/bin/auth.json +17 -0
  7. data/bin/main.rb +0 -0
  8. data/bin/process.rbx +541 -0
  9. data/examples/all_lead_changes.rb +119 -0
  10. data/examples/all_leads.rb +249 -0
  11. data/examples/lead_changes_to_ads.rb +63 -0
  12. data/gooddata_marketo.gemspec +24 -0
  13. data/gooddata_marketo_gem.zip +0 -0
  14. data/lib/gooddata_marketo.rb +24 -0
  15. data/lib/gooddata_marketo/adapters/rest.rb +287 -0
  16. data/lib/gooddata_marketo/client.rb +373 -0
  17. data/lib/gooddata_marketo/data/activity_types.rb +104 -0
  18. data/lib/gooddata_marketo/data/reserved_sql_keywords.rb +205 -0
  19. data/lib/gooddata_marketo/helpers/s3.rb +141 -0
  20. data/lib/gooddata_marketo/helpers/stringwizard.rb +32 -0
  21. data/lib/gooddata_marketo/helpers/table.rb +323 -0
  22. data/lib/gooddata_marketo/helpers/webdav.rb +118 -0
  23. data/lib/gooddata_marketo/loads.rb +235 -0
  24. data/lib/gooddata_marketo/models/campaigns.rb +57 -0
  25. data/lib/gooddata_marketo/models/channels.rb +30 -0
  26. data/lib/gooddata_marketo/models/child/activity.rb +104 -0
  27. data/lib/gooddata_marketo/models/child/criteria.rb +17 -0
  28. data/lib/gooddata_marketo/models/child/lead.rb +118 -0
  29. data/lib/gooddata_marketo/models/child/mobj.rb +68 -0
  30. data/lib/gooddata_marketo/models/etl.rb +75 -0
  31. data/lib/gooddata_marketo/models/leads.rb +493 -0
  32. data/lib/gooddata_marketo/models/load.rb +17 -0
  33. data/lib/gooddata_marketo/models/mobjects.rb +121 -0
  34. data/lib/gooddata_marketo/models/streams.rb +137 -0
  35. data/lib/gooddata_marketo/models/tags.rb +35 -0
  36. data/lib/gooddata_marketo/models/validate.rb +46 -0
  37. metadata +177 -0
@@ -0,0 +1,68 @@
1
+ # encoding: UTF-8
2
+
3
+ class GoodDataMarketo::MObject
4
+
5
+ def initialize data, config = {}
6
+
7
+ @object = {
8
+ :type => data[:type],
9
+ :id => data[:id],
10
+ :raw => data
11
+ }
12
+
13
+ @headers = @object.keys.map{|k| k.to_s.capitalize! }
14
+ @headers.pop()
15
+
16
+ attributes = data[:attrib_list][:attrib]
17
+ attribute_map = Hash.new
18
+ attributes.map { |attr|
19
+ @headers << property = attr[:name]
20
+ value = attr[:value]
21
+ attribute_map[property] = value
22
+ }
23
+
24
+ @attributes = attribute_map
25
+
26
+ end
27
+
28
+ def type
29
+ @object[:type]
30
+ end
31
+
32
+ def to_row
33
+ row = [self.id,self.time,self.type,self.name]
34
+ @attributes.each do |attr|
35
+ row << attr[1]
36
+ end
37
+ row.map! { |i| i.to_s }
38
+ end
39
+
40
+ def headers
41
+ @headers
42
+ end
43
+
44
+ def id
45
+ @object[:id]
46
+ end
47
+
48
+ def attributes a = nil
49
+
50
+ if a
51
+ @attributes[a]
52
+ else
53
+ @attributes
54
+ end
55
+
56
+ end
57
+
58
+ def raw
59
+ @activity[:raw]
60
+ end
61
+
62
+ alias :to_a :to_row
63
+
64
+ alias :json :raw
65
+
66
+ alias :object_type :type
67
+
68
+ end
@@ -0,0 +1,75 @@
1
+
2
+ class GoodDataMarketo::ETL
3
+
4
+ def initialize config = {}
5
+ @queue = config[:queue]
6
+ @marketo = config[:marketo] || config[:client]
7
+ end
8
+
9
+ def determine_loads_state config = {}
10
+
11
+ loads = @marketo.loads(:user => GOODDATA_USER,
12
+ :pass => GOODDATA_PASSWORD,
13
+ :project => GOODDATA_PROJECT,
14
+ :marketo_client => @marketo)
15
+
16
+ if loads.available?
17
+
18
+ file = loads.available.first
19
+ load = loads.create :name => file
20
+
21
+ load.execute
22
+
23
+ # Data from the job can now be accessed ARRAY load.storage
24
+ # load.storage
25
+
26
+ # Increment the load by one day if it is time related.
27
+
28
+ case load.json[:method]
29
+
30
+ when 'get_changes'
31
+
32
+ time_increment = config[:increment] || (12*60*60)
33
+ oca = load.arguments[:oldest_created_at]
34
+ lca = load.arguments[:latest_created_at]
35
+
36
+ load.arguments[:oldest_created_at] = (Time.parse(oca) + time_increment).to_s
37
+ load.arguments[:latest_created_at] = (Time.parse(lca) + time_increment).to_s
38
+
39
+ # If the latest time is later then today kill the load.
40
+ if Time.parse(lca) > Time.now
41
+
42
+ load.terminate
43
+
44
+ self.determine_loads_state
45
+
46
+ # Otherwise save the load and resume additional loads.
47
+ else
48
+
49
+ load.save
50
+
51
+ self.determine_loads_state
52
+
53
+ end
54
+
55
+ when 'get_multiple'
56
+
57
+ self.determine_loads_state
58
+
59
+ else
60
+ raise 'Unable to determine lead type ("get_multiple"/"get_changes")!'
61
+
62
+ end
63
+
64
+ else
65
+
66
+ load = @queue.pop
67
+ loads.create load
68
+
69
+ self.determine_loads_state
70
+
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,493 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'gooddata_marketo/models/child/lead'
4
+ require 'gooddata_marketo/models/child/activity'
5
+
6
+ class GoodDataMarketo::Leads
7
+
8
+ attr_reader :client
9
+
10
+ def initialize config = {}
11
+
12
+ @client = config[:client]
13
+
14
+ end
15
+
16
+ def [](a)
17
+ if a.include? '@'
18
+ self.get_by_email(a)
19
+ else
20
+ self.get_by_id(a)
21
+ end
22
+ end
23
+
24
+ # POSSIBLE KEY TYPES FOR LEAD QUERIES
25
+ #
26
+ # IDNUM: The Marketo ID (e.g. 64)
27
+ # COOKIE: The value generated by the Munchkin Javascript. (e.g. id:561-HYG-937&token:_mch-marketo.com-1258067434006-50277)
28
+ # EMAIL: The email address associated with the lead. (e.g. rufus@marketo.com)
29
+ # SFDCLEADID: The lead ID from SalesForce
30
+ # LEADOWNEREMAIL: The Lead Owner Email
31
+ # SFDCACCOUNTID: The Account ID from SalesForce
32
+ # SFDCCONTACTID: The Contact ID from SalesForce
33
+ # SFDCLEADID: TheLead ID from SalesForce
34
+ # SFDCLEADOWNERID: The Lead owner ID from SalesForce
35
+ # SFDCOPPTYID: The Opportunity ID from SalesForce
36
+
37
+ def get_by_email email # http://developers.marketo.com/documentation/soap/getlead/
38
+ type = 'EMAIL'
39
+ request = { :lead_key => { :key_type => type, :key_value => email } }
40
+ response = client.call(:get_lead, request)
41
+ # GoodDataMarketo::Lead.new response[:lead_record_list][:lead_record], :client => client (CLIENT REMOVED DUE TO PERFORMANCE ISSUE)
42
+ GoodDataMarketo::Lead.new response[:lead_record_list][:lead_record]
43
+
44
+ end
45
+
46
+ def get_by_id id
47
+ type = 'IDNUM'
48
+ request = { :lead_key => { :key_type => type, :key_value => id } }
49
+ response = client.call(:get_lead, request)
50
+ # GoodDataMarketo::Lead.new response[:lead_record_list][:lead_record], :client => client (CLIENT REMOVED DUE TO PERFORMANCE ISSUE)
51
+ GoodDataMarketo::Lead.new response[:lead_record_list][:lead_record]
52
+
53
+ end
54
+
55
+ def get_multiple config = {} # http://developers.marketo.com/documentation/soap/getmultipleleads/
56
+
57
+ values_array = config[:ids] || config[:values]
58
+
59
+ @leads_from_call = []
60
+
61
+ # Possible types
62
+ # IDNUM, COOKIE, EMAIL, LEADOWNEREMAIL, SFDCACCOUNTID, SFDCCONTACTID, SFDCLEADID, SFDCLEADOWNERID, SFDCOPPTYID.
63
+
64
+ type = config[:type] || 'IDNUM'
65
+ xsi_type = config[:xsi_type] || 'ns1:LeadKeySelector'
66
+
67
+ if values_array.is_a? String
68
+ values_array = [values_array]
69
+ end
70
+
71
+ segments = values_array.each_slice(100).to_a
72
+
73
+ segments.each { |values|
74
+
75
+ request = {
76
+ :lead_selector => {
77
+ :key_type => type,
78
+ :key_values => {
79
+ :string_item => values
80
+ }
81
+ },
82
+ :batch_size => config[:batch_size] || "100", # Unable to determine timeout rate at the 1000 so moved to 200.
83
+ :attributes! => { :lead_selector => { 'xsi:type' => xsi_type } }
84
+ }
85
+
86
+ inc = config[:include_attributes] || config[:include] || config[:types]
87
+ if inc
88
+ if inc.is_a? String
89
+ inc = [inc]
90
+ end
91
+ request[:activity_filter][:include_attributes] = inc
92
+ end
93
+
94
+ exc = config[:exclude_attributes] || config[:exclude]
95
+ if exc
96
+ if exc.is_a? String
97
+ exc = [exc]
98
+ end
99
+ request[:activity_filter][:exclude_attributes] = exc
100
+ end
101
+
102
+ activities = config[:filters] || config[:activity_name_filter] || config[:activities]
103
+ if activities
104
+ activities = [activities] if activities.is_a? String
105
+ request[:activity_name_filter] = Hash.new
106
+ request[:activity_name_filter][:string_item] = activities
107
+ end
108
+
109
+ # Ensure that the API call is not made with both.
110
+ if inc && exc
111
+ raise "Include and exclude attributes may not be used in the same call."
112
+ end
113
+
114
+ request[:start_position] = Hash.new if config[:oldest_created_at] || config[:activity_created_at] || config[:offset]
115
+
116
+ if config[:oldest_created_at]
117
+ begin
118
+ oca = StringWizard.time(config[:oldest_created_at]).to_s
119
+ request[:start_position][:oldest_created_at] = oca
120
+ rescue Exception => e
121
+ puts e if GoodDataMarketo.logging
122
+ end
123
+
124
+ end
125
+
126
+ if config[:activity_created_at]
127
+ begin
128
+ aca = StringWizard.time(config[:activity_created_at]).to_s
129
+ request[:start_position][:activity_created_at] = aca
130
+ rescue Exception => e
131
+ puts e if GoodDataMarketo.logging
132
+ end
133
+ end
134
+
135
+ if config[:latest_created_at]
136
+ begin
137
+ lca = StringWizard.time(config[:latest_created_at]).to_s
138
+ request[:start_position][:latest_created_at] = lca
139
+ rescue Exception => e
140
+ puts e if GoodDataMarketo.logging
141
+ end
142
+ end
143
+
144
+ offset_date = config[:offset]
145
+
146
+ request[:start_position][:offset] = offset_date if offset_date
147
+
148
+ # Execute a stream unless the number of leads is less than the batch limit of 100
149
+ begin
150
+
151
+ if values.length < 101 && values.length > 1
152
+
153
+ c = client.call(:get_multiple_leads, request)
154
+ c[:lead_record_list][:lead_record].each { |lead|
155
+ # l = GoodDataMarketo::Lead.new lead, :client => client (CLIENT REMOVED DUE TO PERFORMANCE ISSUE)
156
+ if client.load
157
+ l = lead.to_json
158
+ else
159
+ l = GoodDataMarketo::Lead.new lead
160
+ end
161
+
162
+ @leads_from_call << l
163
+ }
164
+
165
+ if client.load
166
+ ids = client.load.arguments[:ids].drop(values.length)
167
+ client.load.arguments[:ids] = ids
168
+ client.load.save
169
+
170
+ end
171
+
172
+
173
+ elsif values.length == 1
174
+ c = client.call(:get_multiple_leads, request)
175
+
176
+ # l = GoodDataMarketo::Lead.new c[:lead_record_list][:lead_record], :client => client (CLIENT REMOVED DUE TO PERFORMANCE ISSUE)
177
+ if client.load
178
+ l = c[:lead_record_list][:lead_record].to_json
179
+ else
180
+ l = GoodDataMarketo::Lead.new c[:lead_record_list][:lead_record]
181
+ end
182
+
183
+ @leads_from_call << l
184
+
185
+ else
186
+
187
+ c = client.stream(:get_multiple_leads, request)
188
+
189
+ if client.load
190
+
191
+ ids = client.load.arguments[:ids].drop(values.length)
192
+
193
+ client.load.arguments[:ids] = ids
194
+
195
+ client.load.save
196
+
197
+ end
198
+
199
+ puts "#{Time.now} => Marketo:Leads:#{c.storage.length}" if GoodDataMarketo.logging
200
+ c.storage.each do |request|
201
+ request[:lead_record_list][:lead_record].each { |lead|
202
+ # l = GoodDataMarketo::Lead.new lead, :client => client (CLIENT REMOVED DUE TO PERFORMANCE ISSUE)
203
+
204
+ # To conserve memory on large batches sent the raw file to load storage.
205
+ if client.load
206
+ l = lead.to_json
207
+ else
208
+ l = GoodDataMarketo::Lead.new lead
209
+ end
210
+
211
+ @leads_from_call << l
212
+ }
213
+ end
214
+
215
+ end
216
+ rescue Exception => exp
217
+ puts exp if GoodDataMarketo.logging
218
+ puts "#{Time.now} => 0 results for Marketo query."
219
+ end
220
+
221
+ puts "#{Time.now} => Marketo:Leads:Queue:#{@leads_from_call.length}"
222
+
223
+ }
224
+ client.load.log('RESPONSE') if client.load
225
+ client.load.storage = @leads_from_call if client.load
226
+ @leads_from_call
227
+
228
+ end
229
+
230
+ def get_changes(config = {}) # http://developers.marketo.com/documentation/soap/getleadchanges/
231
+
232
+ # EXAMPLE REQUEST
233
+ # request = {
234
+ # :start_position => {
235
+ # :oldest_created_at => "2013-07-01 23:58:14 -0700",
236
+ # },
237
+ # :activity_name_filter => {
238
+ # :stringItem => ["Visit Webpage", "Click Link"] },
239
+ # :batch_size => "100"
240
+ # }
241
+
242
+ # OPTIONAL ACTIVITIES
243
+ # NewLead
244
+ # AssocWithOpprtntyInSales
245
+ # DissocFromOpprtntyInSales
246
+ # UpdateOpprtntyInSales
247
+ # ChangeDataValue
248
+ # MergeLeads
249
+ # OpenEmail
250
+ # SendEmail
251
+
252
+ request = {
253
+ :start_position => Hash.new,
254
+ :batch_size => config[:batch_size] || '1000'
255
+ }
256
+
257
+ ###################
258
+ # Activity Config #
259
+ ###################
260
+
261
+ query = config[:lead] || config[:email] || config[:values] || config[:value]
262
+
263
+ if query
264
+ query = [query] if query.is_a? String
265
+ xsi_type = config[:xsi_type] || 'ns1:LeadKeySelector'
266
+
267
+ request[:lead_selector] = {}
268
+ request[:lead_selector][:key_values] = {}
269
+ request[:lead_selector][:key_type] = config[:type] || 'EMAIL'
270
+ request[:lead_selector][:key_values][:string_item] = query
271
+ request[:attributes!] = { :lead_selector => { 'xsi:type' => xsi_type } }
272
+
273
+ end
274
+
275
+ inc = config[:include_attributes] || config[:include] || config[:types]
276
+ if inc
277
+ if inc.is_a? String
278
+ inc = [inc]
279
+ end
280
+ request[:activity_filter][:include_attributes] = inc
281
+ end
282
+
283
+ exc = config[:exclude_attributes] || config[:exclude]
284
+ if exc
285
+ if exc.is_a? String
286
+ exc = [exc]
287
+ end
288
+ request[:activity_filter][:exclude_attributes] = exc
289
+ end
290
+
291
+ activities = config[:filters] || config[:activity_name_filter] || config[:activities]
292
+ if activities
293
+
294
+ activities = [activities] if activities.is_a? String
295
+ request[:activity_name_filter] = Hash.new
296
+ request[:activity_name_filter][:string_item] = activities
297
+ end
298
+
299
+ # Ensure that the API call is not made with both.
300
+ if inc && exc
301
+ raise "Include and exclude attributes may not be used in the same call."
302
+ end
303
+
304
+ #########################
305
+ # Start Position Config #
306
+ #########################
307
+
308
+ if config[:oldest_created_at]
309
+ begin
310
+ oca = StringWizard.time(config[:oldest_created_at])
311
+ request[:start_position][:oldest_created_at] = oca
312
+ rescue Exception => e
313
+ puts e if GoodDataMarketo.logging
314
+ end
315
+
316
+ end
317
+
318
+ if config[:latest_created_at]
319
+ begin
320
+ lca = StringWizard.time(config[:latest_created_at])
321
+ request[:start_position][:latest_created_at] = lca
322
+ rescue Exception => e
323
+ puts e if GoodDataMarketo.logging
324
+ end
325
+
326
+ end
327
+
328
+ if config[:activity_created_at]
329
+ begin
330
+ aca = StringWizard.time(config[:activity_created_at])
331
+ request[:start_position][:activity_created_at] = aca
332
+ rescue Exception => e
333
+ puts e if GoodDataMarketo.logging
334
+ end
335
+ end
336
+
337
+ o = config[:offset]
338
+ request[:start_position][:offset] = o if o
339
+
340
+ raise 'A start position type is required (:oldest_created_at, :offset, :activity_created_at)' unless oca || aca || o
341
+
342
+ c = client.stream(:get_lead_changes, request)
343
+
344
+ # Load all of the leads from the stream into an array for processes from a load or direct call.
345
+ # If it is a direct call, build an object, if not move the item as raw json.
346
+
347
+ leads_from_changes_call = []
348
+ begin
349
+
350
+ c.storage.pmap do |request|
351
+
352
+ stored_item = request[:lead_change_record_list][:lead_change_record]
353
+
354
+ if stored_item.is_a? Array
355
+ stored_item.each { |activity|
356
+ if client.load
357
+ l = activity.to_json
358
+ else
359
+ l = GoodDataMarketo::Activity.new activity
360
+ end
361
+
362
+ leads_from_changes_call << l
363
+ }
364
+ else
365
+ if client.load
366
+ l = stored_item.to_json
367
+ else
368
+ l = GoodDataMarketo::Activity.new activity
369
+ end
370
+ leads_from_changes_call << l
371
+ end
372
+ end
373
+
374
+ rescue
375
+
376
+ puts "#{Time.now} => 0 results for Marketo query."
377
+ end
378
+
379
+ client.load.log('RESPONSE') if client.load
380
+ client.load.storage = leads_from_changes_call if client.load
381
+
382
+ leads_from_changes_call
383
+
384
+ end
385
+
386
+ def get_activities config = {} # http://developers.marketo.com/documentation/soap/getleadactivity/
387
+
388
+ # EXAMPLE HISTORY REQUEST
389
+ # request = {
390
+ # :lead_selector => {
391
+ # :key_type => "IDNUM",
392
+ # :key_value => "example@host.com"},
393
+ # :activity_filter => {
394
+ # :include_types => {
395
+ # :activity_type => ["VisitWebpage", "FillOutForm"]
396
+ # }
397
+ # },
398
+ # :start_position => {
399
+ # :"last_created_at/" => "",
400
+ # :"offset/" => "" },
401
+ # :batch_size => "10"
402
+ # }
403
+
404
+ request = {
405
+ :lead_key => {
406
+ :key_type => config[:type] || "EMAIL",
407
+ :key_value => config[:value]},
408
+ :batch_size => config[:batch_size] || "1000"
409
+ }
410
+
411
+ activity_types = config[:activity_types] || config[:activity_type]
412
+
413
+ if activity_types
414
+
415
+ activity_types = [activity_types] if activity_types.is_a? String
416
+
417
+ request[:activity_filter] = {}
418
+ request[:activity_filter][:include_types] = {}
419
+ request[:activity_filter][:include_types] = activity_types
420
+
421
+ end
422
+
423
+ # Check if any date options were added and if so add the date hash.
424
+ if config[:last_created_at] || config[:activity_created_at] || config[:latest_created_at] || config[:oldest_created_at]
425
+ request[:start_position] = {}
426
+ end
427
+
428
+ request[:start_position][:last_created_at] = StringWizard.time(config[:last_created_at]) if config[:last_created_at]
429
+ request[:start_position][:activity_created_at] = StringWizard.time(config[:activity_created_at]) if config[:activity_created_at]
430
+ request[:start_position][:latest_created_at] = StringWizard.time(config[:latest_created_at]) if config[:latest_created_at]
431
+ request[:start_position][:oldest_created_at] = StringWizard.time(config[:oldest_created_at]) if config[:oldest_created_at]
432
+
433
+ c = client.stream(:get_lead_activity, request)
434
+
435
+ leads_from_activities_call = []
436
+ begin
437
+ c.storage.each do |request|
438
+ request[:activity_record_list][:activity_record].each { |activity|
439
+ l = GoodDataMarketo::Activity.new activity
440
+ leads_from_activities_call << l
441
+ }
442
+ end
443
+ rescue
444
+ puts "#{Time.now} => Marketo (0) results for query. Adjust the configuration of the request or confirm there has not been changes to the structure of response from Marketo."
445
+ end
446
+
447
+ client.load.log('RESPONSE') if client.load
448
+
449
+ leads_from_activities_call
450
+
451
+ end
452
+
453
+ alias :get_activity :get_activities
454
+
455
+ def get_activity_by_email email
456
+ config = {}
457
+ config[:type] = 'EMAIL'
458
+ config[:value] = email
459
+
460
+ self.get_activities config
461
+
462
+ end
463
+
464
+ def get_activity_by_id id
465
+ config = {}
466
+ config[:type] = 'IDNUM'
467
+ config[:value] = id
468
+
469
+ self.get_activity config
470
+
471
+ end
472
+
473
+ def get_multiple_by_email emails, config = {}
474
+ config[:type] = 'EMAIL'
475
+ config[:ids] = emails
476
+ self.get_multiple config
477
+ end
478
+
479
+ def get_multiple_by_id ids, config = {}
480
+ config[:type] = 'IDNUM'
481
+ config[:ids] = ids
482
+ self.get_multiple config
483
+ end
484
+
485
+ def types
486
+ m = self.methods - Object.methods
487
+ m.delete(:types)
488
+ m
489
+ end
490
+
491
+ end
492
+
493
+