gooddata_marketo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+