embulk-input-marketo 0.0.1 → 0.1.0

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