embulk-input-marketo 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +25 -6
- data/Rakefile +53 -1
- data/embulk-input-marketo.gemspec +1 -1
- data/lib/embulk/input/marketo/activity_log.rb +55 -0
- data/lib/embulk/input/marketo/base.rb +89 -0
- data/lib/embulk/input/marketo/lead.rb +73 -0
- data/lib/embulk/input/marketo_api.rb +14 -3
- data/lib/embulk/input/marketo_api/soap/activity_log.rb +90 -0
- data/lib/embulk/input/marketo_api/soap/base.rb +55 -0
- data/lib/embulk/input/marketo_api/soap/lead.rb +75 -0
- data/test/activity_log_fixtures.rb +155 -0
- data/test/embulk/input/marketo/test_activity_log.rb +201 -0
- data/test/embulk/input/marketo/test_base.rb +63 -0
- data/test/embulk/input/marketo/test_lead.rb +172 -0
- data/test/embulk/input/marketo_api/soap/test_activity_log.rb +109 -0
- data/test/embulk/input/marketo_api/soap/test_base.rb +43 -0
- data/test/embulk/input/marketo_api/soap/test_lead.rb +110 -0
- data/test/embulk/input/test_marketo_api.rb +28 -0
- metadata +24 -8
- data/lib/embulk/input/marketo.rb +0 -138
- data/lib/embulk/input/marketo_api/soap.rb +0 -112
- data/test/embulk/input/marketo_api/test_soap.rb +0 -123
- data/test/embulk/input/test_marketo.rb +0 -176
@@ -0,0 +1,28 @@
|
|
1
|
+
require "embulk/input/marketo_api"
|
2
|
+
|
3
|
+
module Embulk
|
4
|
+
module Input
|
5
|
+
class MarketoApiTest < Test::Unit::TestCase
|
6
|
+
class SoapClientTest < self
|
7
|
+
data do
|
8
|
+
{
|
9
|
+
lead: [:lead, MarketoApi::Soap::Lead],
|
10
|
+
activity_log: [:activity_log, MarketoApi::Soap::ActivityLog],
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_valid_target(data)
|
15
|
+
target, expected = data
|
16
|
+
actual = MarketoApi.soap_client({}, target)
|
17
|
+
assert_equal(expected, actual.class)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_unknown_target
|
21
|
+
assert_raise do
|
22
|
+
MarketoApi.soap_client({}, "unknown")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-input-marketo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- uu59
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-07-
|
12
|
+
date: 2015-07-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,11 +146,21 @@ files:
|
|
146
146
|
- README.md
|
147
147
|
- Rakefile
|
148
148
|
- embulk-input-marketo.gemspec
|
149
|
-
- lib/embulk/input/marketo.rb
|
149
|
+
- lib/embulk/input/marketo/activity_log.rb
|
150
|
+
- lib/embulk/input/marketo/base.rb
|
151
|
+
- lib/embulk/input/marketo/lead.rb
|
150
152
|
- lib/embulk/input/marketo_api.rb
|
151
|
-
- lib/embulk/input/marketo_api/soap.rb
|
152
|
-
-
|
153
|
-
-
|
153
|
+
- lib/embulk/input/marketo_api/soap/activity_log.rb
|
154
|
+
- lib/embulk/input/marketo_api/soap/base.rb
|
155
|
+
- lib/embulk/input/marketo_api/soap/lead.rb
|
156
|
+
- test/activity_log_fixtures.rb
|
157
|
+
- test/embulk/input/marketo/test_activity_log.rb
|
158
|
+
- test/embulk/input/marketo/test_base.rb
|
159
|
+
- test/embulk/input/marketo/test_lead.rb
|
160
|
+
- test/embulk/input/marketo_api/soap/test_activity_log.rb
|
161
|
+
- test/embulk/input/marketo_api/soap/test_base.rb
|
162
|
+
- test/embulk/input/marketo_api/soap/test_lead.rb
|
163
|
+
- test/embulk/input/test_marketo_api.rb
|
154
164
|
- test/lead_fixtures.rb
|
155
165
|
- test/prepare_embulk.rb
|
156
166
|
- test/run-test.rb
|
@@ -179,8 +189,14 @@ signing_key:
|
|
179
189
|
specification_version: 4
|
180
190
|
summary: Marketo input plugin for Embulk
|
181
191
|
test_files:
|
182
|
-
- test/
|
183
|
-
- test/embulk/input/
|
192
|
+
- test/activity_log_fixtures.rb
|
193
|
+
- test/embulk/input/marketo/test_activity_log.rb
|
194
|
+
- test/embulk/input/marketo/test_base.rb
|
195
|
+
- test/embulk/input/marketo/test_lead.rb
|
196
|
+
- test/embulk/input/marketo_api/soap/test_activity_log.rb
|
197
|
+
- test/embulk/input/marketo_api/soap/test_base.rb
|
198
|
+
- test/embulk/input/marketo_api/soap/test_lead.rb
|
199
|
+
- test/embulk/input/test_marketo_api.rb
|
184
200
|
- test/lead_fixtures.rb
|
185
201
|
- test/prepare_embulk.rb
|
186
202
|
- test/run-test.rb
|
data/lib/embulk/input/marketo.rb
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
require "embulk/input/marketo_api"
|
2
|
-
|
3
|
-
module Embulk
|
4
|
-
module Input
|
5
|
-
class Marketo < InputPlugin
|
6
|
-
PREVIEW_COUNT = 15
|
7
|
-
|
8
|
-
Plugin.register_input("marketo", self)
|
9
|
-
|
10
|
-
def self.transaction(config, &control)
|
11
|
-
endpoint_url = config.param(:endpoint, :string)
|
12
|
-
|
13
|
-
task = {
|
14
|
-
endpoint_url: endpoint_url,
|
15
|
-
wsdl_url: config.param(:wsdl, :string, default: "#{endpoint_url}?WSDL"),
|
16
|
-
user_id: config.param(:user_id, :string),
|
17
|
-
encryption_key: config.param(:encryption_key, :string),
|
18
|
-
last_updated_at: config.param(:last_updated_at, :string),
|
19
|
-
columns: config.param(:columns, :array)
|
20
|
-
}
|
21
|
-
|
22
|
-
columns = []
|
23
|
-
|
24
|
-
task[:columns].each do |column|
|
25
|
-
name = column["name"]
|
26
|
-
type = column["type"].to_sym
|
27
|
-
|
28
|
-
columns << Column.new(nil, name, type, column["format"])
|
29
|
-
end
|
30
|
-
|
31
|
-
resume(task, columns, 1, &control)
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.resume(task, columns, count, &control)
|
35
|
-
commit_reports = yield(task, columns, count)
|
36
|
-
|
37
|
-
next_config_diff = {}
|
38
|
-
return next_config_diff
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.guess(config)
|
42
|
-
client = soap_client(config)
|
43
|
-
metadata = client.lead_metadata
|
44
|
-
|
45
|
-
return {"columns" => generate_columns(metadata)}
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.soap_client(config)
|
49
|
-
@soap ||=
|
50
|
-
begin
|
51
|
-
endpoint_url = config.param(:endpoint, :string),
|
52
|
-
soap_config = {
|
53
|
-
endpoint_url: endpoint_url,
|
54
|
-
wsdl_url: config.param(:wsdl, :string, default: "#{endpoint_url}?WSDL"),
|
55
|
-
user_id: config.param(:user_id, :string),
|
56
|
-
encryption_key: config.param(:encryption_key, :string),
|
57
|
-
}
|
58
|
-
|
59
|
-
MarketoApi.soap_client(soap_config)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.generate_columns(metadata)
|
64
|
-
columns = [
|
65
|
-
{name: "id", type: "long"},
|
66
|
-
{name: "email", type: "string"},
|
67
|
-
]
|
68
|
-
|
69
|
-
metadata.each do |field|
|
70
|
-
type =
|
71
|
-
case field[:data_type]
|
72
|
-
when "integer"
|
73
|
-
"long"
|
74
|
-
when "dateTime", "date"
|
75
|
-
"timestamp"
|
76
|
-
when "string", "text", "phone", "currency"
|
77
|
-
"string"
|
78
|
-
when "boolean"
|
79
|
-
"boolean"
|
80
|
-
when "float"
|
81
|
-
"double"
|
82
|
-
else
|
83
|
-
"string"
|
84
|
-
end
|
85
|
-
|
86
|
-
columns << {name: field[:name], type: type}
|
87
|
-
end
|
88
|
-
|
89
|
-
columns
|
90
|
-
end
|
91
|
-
|
92
|
-
def init
|
93
|
-
@last_updated_at = task[:last_updated_at]
|
94
|
-
@columns = task[:columns]
|
95
|
-
@soap = MarketoApi.soap_client(task)
|
96
|
-
end
|
97
|
-
|
98
|
-
def run
|
99
|
-
# TODO: preview
|
100
|
-
count = 0
|
101
|
-
@soap.each_lead(@last_updated_at) do |lead|
|
102
|
-
values = @columns.map do |column|
|
103
|
-
name = column["name"].to_s
|
104
|
-
(lead[name] || {})[:value]
|
105
|
-
end
|
106
|
-
|
107
|
-
page_builder.add(values)
|
108
|
-
|
109
|
-
count += 1
|
110
|
-
break if preview? && count >= PREVIEW_COUNT
|
111
|
-
end
|
112
|
-
|
113
|
-
page_builder.finish
|
114
|
-
|
115
|
-
commit_report = {}
|
116
|
-
return commit_report
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.logger
|
120
|
-
Embulk.logger
|
121
|
-
end
|
122
|
-
|
123
|
-
def logger
|
124
|
-
self.class.logger
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def preview?
|
130
|
-
begin
|
131
|
-
org.embulk.spi.Exec.isPreview()
|
132
|
-
rescue java.lang.NullPointerException => e
|
133
|
-
false
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
@@ -1,112 +0,0 @@
|
|
1
|
-
require "savon"
|
2
|
-
|
3
|
-
module Embulk
|
4
|
-
module Input
|
5
|
-
module MarketoApi
|
6
|
-
class Soap
|
7
|
-
attr_reader :endpoint, :wsdl, :user_id, :encryption_key
|
8
|
-
|
9
|
-
def initialize(endpoint, wsdl, user_id, encryption_key)
|
10
|
-
@endpoint = endpoint
|
11
|
-
@wsdl = wsdl
|
12
|
-
@user_id = user_id
|
13
|
-
@encryption_key = encryption_key
|
14
|
-
end
|
15
|
-
|
16
|
-
def lead_metadata
|
17
|
-
# http://developers.marketo.com/documentation/soap/describemobject/
|
18
|
-
response = savon.call(:describe_m_object, message: {object_name: "LeadRecord"})
|
19
|
-
response.body[:success_describe_m_object][:result][:metadata][:field_list][:field]
|
20
|
-
end
|
21
|
-
|
22
|
-
def each_lead(last_updated_at, &block)
|
23
|
-
# http://developers.marketo.com/documentation/soap/getmultipleleads/
|
24
|
-
|
25
|
-
last_updated_at = Time.parse(last_updated_at).iso8601
|
26
|
-
request = {
|
27
|
-
lead_selector: {
|
28
|
-
oldest_updated_at: last_updated_at,
|
29
|
-
},
|
30
|
-
attributes!: {
|
31
|
-
lead_selector: {"xsi:type" => "ns1:LastUpdateAtSelector"}
|
32
|
-
},
|
33
|
-
batch_size: 1000,
|
34
|
-
}
|
35
|
-
|
36
|
-
stream_position = fetch_leads(request, &block)
|
37
|
-
|
38
|
-
while stream_position
|
39
|
-
stream_position = fetch_leads(request.merge(stream_position: stream_position), &block)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def fetch_leads(request = {}, &block)
|
46
|
-
response = savon.call(:get_multiple_leads, message: request)
|
47
|
-
|
48
|
-
remaining = response.xpath('//remainingCount').text.to_i
|
49
|
-
Embulk.logger.info "Remaining records: #{remaining}"
|
50
|
-
response.xpath('//leadRecordList/leadRecord').each do |lead|
|
51
|
-
record = {
|
52
|
-
"id" => {type: :integer, value: lead.xpath('Id').text.to_i},
|
53
|
-
"email" => {type: :string, value: lead.xpath('Email').text}
|
54
|
-
}
|
55
|
-
lead.xpath('leadAttributeList/attribute').each do |attr|
|
56
|
-
name = attr.xpath('attrName').text
|
57
|
-
type = attr.xpath('attrType').text
|
58
|
-
value = attr.xpath('attrValue').text
|
59
|
-
record = record.merge(
|
60
|
-
name => {
|
61
|
-
type: type,
|
62
|
-
value: value
|
63
|
-
}
|
64
|
-
)
|
65
|
-
end
|
66
|
-
|
67
|
-
block.call(record)
|
68
|
-
end
|
69
|
-
|
70
|
-
if remaining > 0
|
71
|
-
response.xpath('//newStreamPosition').text
|
72
|
-
else
|
73
|
-
nil
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def savon
|
78
|
-
headers = {
|
79
|
-
'ns1:AuthenticationHeader' => {
|
80
|
-
"mktowsUserId" => user_id,
|
81
|
-
}.merge(signature)
|
82
|
-
}
|
83
|
-
# NOTE: Do not memoize this to use always fresh signature (avoid 20016 error)
|
84
|
-
# ref. https://jira.talendforge.org/secure/attachmentzip/unzip/167201/49761%5B1%5D/Marketo%20Enterprise%20API%202%200.pdf (41 page)
|
85
|
-
Savon.client(
|
86
|
-
log: true,
|
87
|
-
logger: Embulk.logger,
|
88
|
-
wsdl: wsdl,
|
89
|
-
soap_header: headers,
|
90
|
-
endpoint: endpoint,
|
91
|
-
open_timeout: 90,
|
92
|
-
read_timeout: 300,
|
93
|
-
raise_errors: true,
|
94
|
-
namespace_identifier: :ns1,
|
95
|
-
env_namespace: 'SOAP-ENV'
|
96
|
-
)
|
97
|
-
end
|
98
|
-
|
99
|
-
def signature
|
100
|
-
timestamp = Time.now.to_s
|
101
|
-
encryption_string = timestamp + user_id
|
102
|
-
digest = OpenSSL::Digest.new('sha1')
|
103
|
-
hashed_signature = OpenSSL::HMAC.hexdigest(digest, encryption_key, encryption_string)
|
104
|
-
{
|
105
|
-
'requestTimestamp' => timestamp,
|
106
|
-
'requestSignature' => hashed_signature.to_s
|
107
|
-
}
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
@@ -1,123 +0,0 @@
|
|
1
|
-
require "embulk/input/marketo_api/soap"
|
2
|
-
require "lead_fixtures"
|
3
|
-
|
4
|
-
module Embulk
|
5
|
-
module Input
|
6
|
-
module MarketoApi
|
7
|
-
class SoapTest < Test::Unit::TestCase
|
8
|
-
include LeadFixtures
|
9
|
-
|
10
|
-
class TestSignature < self
|
11
|
-
def setup
|
12
|
-
@signature = soap.__send__(:signature)
|
13
|
-
end
|
14
|
-
|
15
|
-
def test_sigature_keys
|
16
|
-
assert_equal(%w(requestTimestamp requestSignature).sort, @signature.keys.sort)
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_is_hash
|
20
|
-
assert_equal(Hash, @signature.class)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_each_lead
|
25
|
-
stub(Embulk).logger { ::Logger.new(IO::NULL) }
|
26
|
-
last_updated_at = "2015-07-06"
|
27
|
-
|
28
|
-
request = {
|
29
|
-
lead_selector: {oldest_updated_at: Time.parse(last_updated_at).iso8601},
|
30
|
-
attributes!: {lead_selector: {"xsi:type"=>"ns1:LastUpdateAtSelector"}},
|
31
|
-
batch_size: 1000
|
32
|
-
}
|
33
|
-
|
34
|
-
any_instance_of(Savon::Client) do |klass|
|
35
|
-
mock(klass).call(:get_multiple_leads, message: request) do
|
36
|
-
next_stream_leads_response
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
proc = proc{ "" }
|
41
|
-
leads_count = next_stream_leads_response.xpath('//leadRecord').length
|
42
|
-
mock(proc).call(anything).times(leads_count)
|
43
|
-
|
44
|
-
soap.each_lead(last_updated_at, &proc)
|
45
|
-
end
|
46
|
-
|
47
|
-
class TestLeadMetadata < self
|
48
|
-
def setup
|
49
|
-
@savon = soap.__send__(:savon)
|
50
|
-
stub(soap).savon { @savon } # Pin savon instance for each call soap.savon for mocking/stubbing
|
51
|
-
end
|
52
|
-
|
53
|
-
def test_savon_call
|
54
|
-
mock(@savon).call(:describe_m_object, message: {object_name: "LeadRecord"}) {
|
55
|
-
Struct.new(:body).new(body)
|
56
|
-
}
|
57
|
-
soap.lead_metadata
|
58
|
-
end
|
59
|
-
|
60
|
-
def test_return_fields
|
61
|
-
stub(@savon).call(:describe_m_object, message: {object_name: "LeadRecord"}) {
|
62
|
-
Struct.new(:body).new(body)
|
63
|
-
}
|
64
|
-
assert_equal(fields, soap.lead_metadata)
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
|
-
def body
|
70
|
-
{
|
71
|
-
success_describe_m_object: {
|
72
|
-
result: {
|
73
|
-
metadata: {
|
74
|
-
field_list: {
|
75
|
-
field: fields
|
76
|
-
}
|
77
|
-
}
|
78
|
-
}
|
79
|
-
}
|
80
|
-
}
|
81
|
-
end
|
82
|
-
|
83
|
-
def fields
|
84
|
-
[
|
85
|
-
{
|
86
|
-
name: "FieldName",
|
87
|
-
description: nil,
|
88
|
-
display_name: "The Name of Field",
|
89
|
-
source_object: "Lead",
|
90
|
-
data_type: "datetime",
|
91
|
-
size: nil,
|
92
|
-
is_readonly: false,
|
93
|
-
is_update_blocked: false,
|
94
|
-
is_name: nil,
|
95
|
-
is_primary_key: false,
|
96
|
-
is_custom: true,
|
97
|
-
is_dynamic: true,
|
98
|
-
dynamic_field_ref: "leadAttributeList",
|
99
|
-
updated_at: DateTime.parse("2000-01-01 22:22:22")
|
100
|
-
}
|
101
|
-
]
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
|
107
|
-
def soap
|
108
|
-
@soap ||= Soap.new(settings[:endpoint], settings[:wsdl], settings[:user_id], settings[:encryption_key])
|
109
|
-
end
|
110
|
-
|
111
|
-
def settings
|
112
|
-
{
|
113
|
-
endpoint: "https://marketo.example.com",
|
114
|
-
wsdl: "https://marketo.example.com/?wsdl",
|
115
|
-
user_id: "user_id",
|
116
|
-
encryption_key: "TOPSECRET",
|
117
|
-
}
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|