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.
@@ -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.1
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-06 00:00:00.000000000 Z
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
- - test/embulk/input/marketo_api/test_soap.rb
153
- - test/embulk/input/test_marketo.rb
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/embulk/input/marketo_api/test_soap.rb
183
- - test/embulk/input/test_marketo.rb
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
@@ -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
-