embulk-input-marketo 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +15 -2
- data/embulk-input-marketo.gemspec +1 -1
- data/lib/embulk/input/marketo/base.rb +4 -1
- data/lib/embulk/input/marketo/lead.rb +19 -11
- data/lib/embulk/input/marketo/timeslice.rb +60 -0
- data/lib/embulk/input/marketo_api/soap/activity_log.rb +1 -1
- data/lib/embulk/input/marketo_api/soap/base.rb +37 -0
- data/lib/embulk/input/marketo_api/soap/lead.rb +32 -21
- data/lib/embulk/input/marketo_api/soap/timeslice.rb +44 -0
- data/test/embulk/input/marketo/test_base.rb +23 -0
- data/test/embulk/input/marketo/test_lead.rb +113 -8
- data/test/embulk/input/marketo_api/soap/test_lead.rb +81 -15
- data/test/mute_logger.rb +7 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcfd15d051ac950ef7529b31662348b01f6c5389
|
4
|
+
data.tar.gz: 1161b4da7811e5a0dd29454d5bd44b2ee6be8e73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 263729220d75599942c77113e4fed6f481453ed2b079fae30b26f63b12eb2e8ca96a667294ec378594241ae24d7e77a319ae9c5f9c6bb579c40fac58e90d6d4b
|
7
|
+
data.tar.gz: 5f8ee7bc9eaa36e1d08ced80eed97bb5bfd0e581250204a41369bfbe19eb240ad4c05ec6b2a2236bc4ea347f05daa3c1c690d4a0c51c20ff27c7b39f3fe5add6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 0.2.0 - 2015-08-27
|
2
|
+
|
3
|
+
This version breaks backword compatibility of marketo/lead. Please check README.md to modify your config.
|
4
|
+
|
5
|
+
* [enhancement] Avoid timeout for marketo/lead input [#25](https://github.com/treasure-data/embulk-input-marketo/pull/25)
|
6
|
+
* [fixed] Fix timestamp column [#24](https://github.com/treasure-data/embulk-input-marketo/pull/24)
|
7
|
+
* [enhancement] Raise ConfigError if unretryable error occured [#23](https://github.com/treasure-data/embulk-input-marketo/pull/23)
|
8
|
+
* [fixed] Fix the bug that the default value for wsdl is broken [#22](https://github.com/treasure-data/embulk-input-marketo/pull/22)
|
9
|
+
|
1
10
|
## 0.1.1 - 2015-08-19
|
2
11
|
|
3
12
|
* [enhanement] Support scheduled execution [#20](https://github.com/treasure-data/embulk-input-marketo/pull/20) [[Reported by @muga](https://github.com/treasure-data/embulk-input-marketo/issues/18). Thanks!]
|
data/README.md
CHANGED
@@ -18,7 +18,7 @@ This plugin uses Marketo SOAP API.
|
|
18
18
|
Required Embulk version >= 0.6.13.
|
19
19
|
|
20
20
|
* **Plugin type**: input
|
21
|
-
* **Resume supported**: no
|
21
|
+
* **Resume supported**: yes for `marketo/lead`, no for `marketo/activity_log`
|
22
22
|
* **Cleanup supported**: no
|
23
23
|
* **Guess supported**: yes
|
24
24
|
|
@@ -34,6 +34,19 @@ $ embulk gem install embulk-input-marketo
|
|
34
34
|
|
35
35
|
Below parameters are shown in "Admin" > "Web Services" page in Marketo.
|
36
36
|
|
37
|
+
### market/lead
|
38
|
+
|
39
|
+
**NOTE: If you use feature of scheduled execution (resume) with marketo/read, you should not specify until\_at because this plugin can't place new to_datetime (can't know the date to run with new config.**
|
40
|
+
|
41
|
+
- **endpoint** SOAP endpoint URL for your account (string, required)
|
42
|
+
- **wsdl** SOAP endpoint URL for your account (string, default: endpoint + "?WSDL")
|
43
|
+
- **user_id** Your user id (string, reqiured)
|
44
|
+
- **encryption_key** Your encryption key (string, reqiured)
|
45
|
+
- **from_datetime** Fetch leads since this time (string, required)
|
46
|
+
- **to_datetime** Fetch leads until this time (string, default: Time.now)
|
47
|
+
|
48
|
+
### market/activity_log
|
49
|
+
|
37
50
|
- **endpoint** SOAP endpoint URL for your account (string, required)
|
38
51
|
- **wsdl** SOAP endpoint URL for your account (string, default: endpoint + "?WSDL")
|
39
52
|
- **user_id** Your user id (string, reqiured)
|
@@ -56,7 +69,7 @@ in:
|
|
56
69
|
wsdl: https://wsdl-url.mktoapi.com/?WSDL
|
57
70
|
user_id: user_ABC123
|
58
71
|
encryption_key: TOPSECRET
|
59
|
-
|
72
|
+
from_datetime: "2015-06-30"
|
60
73
|
out:
|
61
74
|
type: stdout
|
62
75
|
```
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "embulk/input/marketo/timeslice"
|
1
2
|
require "embulk/input/marketo_api"
|
2
3
|
|
3
4
|
module Embulk
|
@@ -6,6 +7,8 @@ module Embulk
|
|
6
7
|
class Base < InputPlugin
|
7
8
|
PREVIEW_COUNT = 15
|
8
9
|
|
10
|
+
attr_reader :soap
|
11
|
+
|
9
12
|
def self.target
|
10
13
|
raise NotImplementedError
|
11
14
|
end
|
@@ -46,7 +49,7 @@ module Embulk
|
|
46
49
|
def self.soap_client(config)
|
47
50
|
@soap ||=
|
48
51
|
begin
|
49
|
-
endpoint_url = config.param(:endpoint, :string)
|
52
|
+
endpoint_url = config.param(:endpoint, :string)
|
50
53
|
soap_config = {
|
51
54
|
endpoint_url: endpoint_url,
|
52
55
|
wsdl_url: config.param(:wsdl, :string, default: "#{endpoint_url}?WSDL"),
|
@@ -4,6 +4,8 @@ module Embulk
|
|
4
4
|
module Input
|
5
5
|
module Marketo
|
6
6
|
class Lead < Base
|
7
|
+
include Timeslice
|
8
|
+
|
7
9
|
PREVIEW_COUNT = 15
|
8
10
|
|
9
11
|
Plugin.register_input("marketo/lead", self)
|
@@ -12,13 +14,6 @@ module Embulk
|
|
12
14
|
:lead
|
13
15
|
end
|
14
16
|
|
15
|
-
def self.guess(config)
|
16
|
-
client = soap_client(config)
|
17
|
-
metadata = client.metadata
|
18
|
-
|
19
|
-
return {"columns" => generate_columns(metadata)}
|
20
|
-
end
|
21
|
-
|
22
17
|
def self.generate_columns(metadata)
|
23
18
|
columns = [
|
24
19
|
{name: "id", type: "long"},
|
@@ -30,7 +25,7 @@ module Embulk
|
|
30
25
|
case field[:data_type]
|
31
26
|
when "integer"
|
32
27
|
"long"
|
33
|
-
when "
|
28
|
+
when "datetime", "date"
|
34
29
|
"timestamp"
|
35
30
|
when "string", "text", "phone", "currency"
|
36
31
|
"string"
|
@@ -50,10 +45,23 @@ module Embulk
|
|
50
45
|
|
51
46
|
def run
|
52
47
|
count = 0
|
53
|
-
|
48
|
+
from_datetime = task[:from_datetime]
|
49
|
+
to_datetime = task[:to_datetime]
|
50
|
+
options = {}
|
51
|
+
options[:batch_size] = PREVIEW_COUNT if preview?
|
52
|
+
|
53
|
+
soap.each(from_datetime, to_datetime, options) do |lead|
|
54
54
|
values = @columns.map do |column|
|
55
55
|
name = column["name"].to_s
|
56
|
-
(lead[name] || {})[:value]
|
56
|
+
value = (lead[name] || {})[:value]
|
57
|
+
next unless value
|
58
|
+
|
59
|
+
case column["type"]
|
60
|
+
when "timestamp"
|
61
|
+
Time.parse(value)
|
62
|
+
else
|
63
|
+
value
|
64
|
+
end
|
57
65
|
end
|
58
66
|
|
59
67
|
page_builder.add(values)
|
@@ -64,7 +72,7 @@ module Embulk
|
|
64
72
|
|
65
73
|
page_builder.finish
|
66
74
|
|
67
|
-
commit_report = {}
|
75
|
+
commit_report = {from_datetime: to_datetime}
|
68
76
|
return commit_report
|
69
77
|
end
|
70
78
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Embulk
|
2
|
+
module Input
|
3
|
+
module Marketo
|
4
|
+
module Timeslice
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def guess(config)
|
11
|
+
if config.param(:last_updated_at, :string, default: nil)
|
12
|
+
Embulk.logger.warn "config: last_updated_at is deprecated. Use from_datetime/to_datetime"
|
13
|
+
end
|
14
|
+
|
15
|
+
client = soap_client(config)
|
16
|
+
metadata = client.metadata
|
17
|
+
|
18
|
+
return {"columns" => generate_columns(metadata)}
|
19
|
+
end
|
20
|
+
|
21
|
+
def transaction(config, &control)
|
22
|
+
endpoint_url = config.param(:endpoint, :string)
|
23
|
+
|
24
|
+
if config.param(:last_updated_at, :string, default: nil)
|
25
|
+
Embulk.logger.warn "config: last_updated_at is deprecated. Use from_datetime/to_datetime"
|
26
|
+
end
|
27
|
+
|
28
|
+
from_datetime = config.param(:from_datetime, :string)
|
29
|
+
to_datetime = config.param(:to_datetime, :string, default: Time.now.to_s)
|
30
|
+
|
31
|
+
if Time.parse(from_datetime) > Time.parse(to_datetime)
|
32
|
+
raise ConfigError, "config: from_datetime '#{from_datetime}' is later than '#{to_datetime}'."
|
33
|
+
end
|
34
|
+
|
35
|
+
task = {
|
36
|
+
endpoint_url: endpoint_url,
|
37
|
+
wsdl_url: config.param(:wsdl, :string, default: "#{endpoint_url}?WSDL"),
|
38
|
+
user_id: config.param(:user_id, :string),
|
39
|
+
encryption_key: config.param(:encryption_key, :string),
|
40
|
+
from_datetime: from_datetime,
|
41
|
+
to_datetime: to_datetime,
|
42
|
+
columns: config.param(:columns, :array)
|
43
|
+
}
|
44
|
+
|
45
|
+
columns = []
|
46
|
+
|
47
|
+
task[:columns].each do |column|
|
48
|
+
name = column["name"]
|
49
|
+
type = column["type"].to_sym
|
50
|
+
|
51
|
+
columns << Column.new(nil, name, type, column["format"])
|
52
|
+
end
|
53
|
+
|
54
|
+
resume(task, columns, 1, &control)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -53,7 +53,7 @@ module Embulk
|
|
53
53
|
def fetch(request, options={}, &block)
|
54
54
|
request[:batch_size] = options[:batch_size] || 100
|
55
55
|
|
56
|
-
response =
|
56
|
+
response = savon_call(:get_lead_changes, message: request)
|
57
57
|
remaining = response.body[:success_get_lead_changes][:result][:remaining_count].to_i
|
58
58
|
Embulk.logger.info "Remaining records: #{remaining}"
|
59
59
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "savon"
|
2
|
+
require "embulk/input/marketo_api/soap/timeslice"
|
2
3
|
|
3
4
|
module Embulk
|
4
5
|
module Input
|
@@ -38,6 +39,12 @@ module Embulk
|
|
38
39
|
)
|
39
40
|
end
|
40
41
|
|
42
|
+
def savon_call(*args)
|
43
|
+
catch_unretryable_error do
|
44
|
+
savon.call(*args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
41
48
|
def signature
|
42
49
|
timestamp = Time.now.to_s
|
43
50
|
encryption_string = timestamp + user_id
|
@@ -48,6 +55,36 @@ module Embulk
|
|
48
55
|
'requestSignature' => hashed_signature.to_s
|
49
56
|
}
|
50
57
|
end
|
58
|
+
|
59
|
+
def catch_unretryable_error(&block)
|
60
|
+
yield
|
61
|
+
rescue Savon::SOAPFault => e
|
62
|
+
Embulk.logger.debug "#{e.class}: #{e.to_hash}"
|
63
|
+
if e.to_hash[:fault][:faultcode].to_str == "SOAP-ENV:Client"
|
64
|
+
raise ConfigError, e.message
|
65
|
+
end
|
66
|
+
rescue Savon::HTTPError => e
|
67
|
+
# NOTE: Marketo API always return error as HTTP 500
|
68
|
+
# ref. https://jira.talendforge.org/secure/attachmentzip/unzip/167201/49761%5B1%5D/Marketo%20Enterprise%20API%202%200.pdf
|
69
|
+
Embulk.logger.debug "#{e.class}: #{e.http.body}"
|
70
|
+
soap_code = e.http.body[%r|<code>(.*?)</code>|, 1]
|
71
|
+
soap_message = e.http.body[%r|<message>(.*?)</message>|, 1]
|
72
|
+
case soap_code
|
73
|
+
when "10001", "20011"
|
74
|
+
# Internal Error
|
75
|
+
raise e
|
76
|
+
when "20015"
|
77
|
+
# Request Limit Exceeded
|
78
|
+
raise e
|
79
|
+
else
|
80
|
+
# unretryable error such as Authentication Failed, Invalid Request, etc.
|
81
|
+
raise ConfigError, soap_message
|
82
|
+
end
|
83
|
+
rescue SocketError => e
|
84
|
+
# maybe endpoint/wsdl domain was wrong
|
85
|
+
Embulk.logger.debug "SocketError: endpoint=#{endpoint} wsdl=#{wsdl}"
|
86
|
+
raise ConfigError, "SocketError: #{e.message} (endpoint is '#{endpoint}')"
|
87
|
+
end
|
51
88
|
end
|
52
89
|
end
|
53
90
|
end
|
@@ -5,44 +5,55 @@ module Embulk
|
|
5
5
|
module MarketoApi
|
6
6
|
module Soap
|
7
7
|
class Lead < Base
|
8
|
+
include Timeslice
|
9
|
+
|
10
|
+
# NOTE: batch_size is allowed at 1000, but that takes 2 minutes in 1 request.
|
11
|
+
# We use 250 for the default (about 30 seconds)
|
12
|
+
BATCH_SIZE_DEFAULT = 250
|
13
|
+
|
8
14
|
def metadata
|
9
15
|
# http://developers.marketo.com/documentation/soap/describemobject/
|
10
|
-
response =
|
16
|
+
response = savon_call(:describe_m_object, message: {object_name: "LeadRecord"})
|
11
17
|
response.body[:success_describe_m_object][:result][:metadata][:field_list][:field]
|
12
18
|
end
|
13
19
|
|
14
|
-
def each(
|
20
|
+
def each(from_datetime, to_datetime, options = {}, &block)
|
15
21
|
# http://developers.marketo.com/documentation/soap/getmultipleleads/
|
22
|
+
to_datetime ||= Time.now
|
16
23
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
}
|
28
|
-
|
29
|
-
}
|
24
|
+
generate_time_range(from_datetime, to_datetime).each do |range|
|
25
|
+
request = {
|
26
|
+
lead_selector: {
|
27
|
+
oldest_updated_at: range[:from].iso8601,
|
28
|
+
latest_updated_at: range[:to].iso8601,
|
29
|
+
},
|
30
|
+
attributes!: {
|
31
|
+
lead_selector: {"xsi:type" => "ns1:LastUpdateAtSelector"}
|
32
|
+
},
|
33
|
+
batch_size: options[:batch_size] || BATCH_SIZE_DEFAULT,
|
34
|
+
}
|
35
|
+
Embulk.logger.info "Fetching from '#{range[:from]}' to '#{range[:to]}'..."
|
30
36
|
|
31
|
-
|
37
|
+
stream_position = fetch(request, &block)
|
32
38
|
|
33
|
-
|
34
|
-
|
39
|
+
while stream_position
|
40
|
+
stream_position = fetch(request.merge(stream_position: stream_position), &block)
|
41
|
+
end
|
35
42
|
end
|
36
43
|
end
|
37
44
|
|
38
45
|
private
|
39
46
|
|
40
47
|
def fetch(request = {}, &block)
|
41
|
-
|
48
|
+
start = Time.now
|
49
|
+
response = savon_call(:get_multiple_leads, message: request)
|
50
|
+
Embulk.logger.info "Fetched in #{Time.now - start} seconds"
|
42
51
|
|
52
|
+
records = response.xpath('//leadRecordList/leadRecord')
|
43
53
|
remaining = response.xpath('//remainingCount').text.to_i
|
44
|
-
Embulk.logger.info "
|
45
|
-
|
54
|
+
Embulk.logger.info "Fetched records in the range: #{records.size}"
|
55
|
+
Embulk.logger.info "Remaining records in the range: #{remaining}"
|
56
|
+
records.each do |lead|
|
46
57
|
record = {
|
47
58
|
"id" => {type: :integer, value: lead.xpath('Id').text.to_i},
|
48
59
|
"email" => {type: :string, value: lead.xpath('Email').text}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Embulk
|
2
|
+
module Input
|
3
|
+
module MarketoApi
|
4
|
+
module Soap
|
5
|
+
module Timeslice
|
6
|
+
private
|
7
|
+
|
8
|
+
def generate_time_range(from, to)
|
9
|
+
# e.g. from = 2010-01-01 15:00, to = 2010-01-03 09:30
|
10
|
+
# convert to such array:
|
11
|
+
# [
|
12
|
+
# {from: 2010-01-01 15:00, to: 2010-01-01 16:00},
|
13
|
+
# {from: 2010-01-01 16:00, to: 2010-01-01 17:00},
|
14
|
+
# ...
|
15
|
+
# {from: 2010-01-03 08:00, to: 2010-01-03 09:00},
|
16
|
+
# {from: 2010-01-03 09:00, to: 2010-01-03 09:30},
|
17
|
+
# ]
|
18
|
+
# to fetch data from Marketo API with each day as
|
19
|
+
# desribed on official blog:
|
20
|
+
# http://developers.marketo.com/blog/performance-tuning-api-requests/
|
21
|
+
to ||= Time.now
|
22
|
+
from = Time.parse(from) unless from.is_a?(Time)
|
23
|
+
to = Time.parse(to) unless to.is_a?(Time)
|
24
|
+
|
25
|
+
result = []
|
26
|
+
since = from
|
27
|
+
while since < to
|
28
|
+
next_since = since + 3600
|
29
|
+
if to < next_since
|
30
|
+
next_since = to
|
31
|
+
end
|
32
|
+
result << {
|
33
|
+
from: since,
|
34
|
+
to: next_since
|
35
|
+
}
|
36
|
+
since = next_since
|
37
|
+
end
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -32,6 +32,29 @@ module Embulk
|
|
32
32
|
assert_equal(next_config_diff, actual)
|
33
33
|
end
|
34
34
|
|
35
|
+
class SoapClientTest < self
|
36
|
+
def setup
|
37
|
+
stub(Base).target { :lead }
|
38
|
+
@client = Base.soap_client(config)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_endpoint
|
42
|
+
assert_equal(settings[:endpoint], @client.endpoint)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_wsdl
|
46
|
+
assert_equal(settings[:wsdl], @client.wsdl)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_user_id
|
50
|
+
assert_equal(settings[:user_id], @client.user_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_encryption_key
|
54
|
+
assert_equal(settings[:encryption_key], @client.encryption_key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
35
58
|
private
|
36
59
|
|
37
60
|
def config
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "prepare_embulk"
|
2
2
|
require "lead_fixtures"
|
3
|
+
require "mute_logger"
|
3
4
|
require "embulk/input/marketo/lead"
|
4
5
|
|
5
6
|
module Embulk
|
@@ -7,6 +8,7 @@ module Embulk
|
|
7
8
|
module Marketo
|
8
9
|
class LeadTest < Test::Unit::TestCase
|
9
10
|
include LeadFixtures
|
11
|
+
include MuteLogger
|
10
12
|
|
11
13
|
def test_target
|
12
14
|
assert_equal(:lead, Lead.target)
|
@@ -21,7 +23,28 @@ module Embulk
|
|
21
23
|
def setup_plugin
|
22
24
|
@page_builder = Object.new
|
23
25
|
@plugin = Lead.new(task, nil, nil, @page_builder)
|
24
|
-
|
26
|
+
mute_logger
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_invalid_from_datetime_to_datetime
|
30
|
+
control = proc {} # dummy
|
31
|
+
|
32
|
+
settings = {
|
33
|
+
endpoint: "https://marketo.example.com",
|
34
|
+
wsdl: "https://marketo.example.com/?wsdl",
|
35
|
+
user_id: "user_id",
|
36
|
+
encryption_key: "TOPSECRET",
|
37
|
+
columns: [
|
38
|
+
{"name" => "Name", "type" => "string"},
|
39
|
+
],
|
40
|
+
from_datetime: Time.now + 3600,
|
41
|
+
to_datetime: Time.now,
|
42
|
+
}
|
43
|
+
config = DataSource[settings.to_a]
|
44
|
+
|
45
|
+
assert_raise(ConfigError) do
|
46
|
+
Lead.transaction(config, &control)
|
47
|
+
end
|
25
48
|
end
|
26
49
|
|
27
50
|
class RunTest < self
|
@@ -51,11 +74,20 @@ module Embulk
|
|
51
74
|
@plugin.run
|
52
75
|
end
|
53
76
|
|
77
|
+
def test_run_commit_report
|
78
|
+
# do not requests
|
79
|
+
stub(@page_builder).finish
|
80
|
+
stub(@plugin.soap).each { }
|
81
|
+
|
82
|
+
commit_report = @plugin.run
|
83
|
+
assert_equal to_datetime, commit_report[:from_datetime]
|
84
|
+
end
|
85
|
+
|
54
86
|
def test_preview_through
|
55
87
|
stub(@plugin).preview? { true }
|
56
88
|
|
57
89
|
any_instance_of(Savon::Client) do |klass|
|
58
|
-
mock(klass).call(:get_multiple_leads, message: request) do
|
90
|
+
mock(klass).call(:get_multiple_leads, message: request.merge(batch_size: Lead::PREVIEW_COUNT)) do
|
59
91
|
preview_leads_response
|
60
92
|
end
|
61
93
|
end
|
@@ -68,13 +100,75 @@ module Embulk
|
|
68
100
|
@plugin.run
|
69
101
|
end
|
70
102
|
|
103
|
+
class SavonCallTest < self
|
104
|
+
def test_soap_error
|
105
|
+
assert_raise(Embulk::ConfigError) do
|
106
|
+
@soap.send(:catch_unretryable_error) do
|
107
|
+
raise Savon::SOAPFault.new(nil, Nori.new(default_nori_options), xml)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_http_error_on_client
|
113
|
+
assert_raise(Embulk::ConfigError) do
|
114
|
+
@soap.send(:catch_unretryable_error) do
|
115
|
+
raise Savon::HTTPError.new(HTTPI::Response.new(500, {}, xml("20000")))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_http_error_on_server
|
121
|
+
assert_raise(Savon::HTTPError) do
|
122
|
+
@soap.send(:catch_unretryable_error) do
|
123
|
+
# Internal Error
|
124
|
+
raise Savon::HTTPError.new(HTTPI::Response.new(500, {}, xml("10001")))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_socket_error
|
130
|
+
stub(@soap).endpoint { "http://192.0.2.0/" }
|
131
|
+
|
132
|
+
assert_raise(Embulk::ConfigError) do
|
133
|
+
@plugin.run
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def xml(code = nil, message = nil)
|
138
|
+
<<-XML
|
139
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
140
|
+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault>
|
141
|
+
<faultcode>SOAP-ENV:Client</faultcode>
|
142
|
+
<faultstring>#{message || "20014 - Authentication failed"}</faultstring>
|
143
|
+
<detail>
|
144
|
+
<ns1:serviceException xmlns:ns1="http://www.marketo.com/mktows/">
|
145
|
+
<name>mktServiceException</name>
|
146
|
+
<message>#{message || "Authentication failed (20014)"}</message>
|
147
|
+
<code>#{code || "20014"}</code>
|
148
|
+
</ns1:serviceException></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
|
149
|
+
XML
|
150
|
+
end
|
151
|
+
|
152
|
+
def default_nori_options
|
153
|
+
# https://github.com/savonrb/savon/blob/v2.11.1/lib/savon/options.rb#L75-L94
|
154
|
+
{
|
155
|
+
:strip_namespaces => true,
|
156
|
+
:convert_tags_to => lambda { |tag| tag.snakecase.to_sym},
|
157
|
+
:convert_attributes_to => lambda { |k,v| [k,v] },
|
158
|
+
}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
71
162
|
private
|
72
163
|
|
73
164
|
def request
|
74
165
|
{
|
75
|
-
lead_selector: {
|
166
|
+
lead_selector: {
|
167
|
+
oldest_updated_at: timerange.first[:from].iso8601,
|
168
|
+
latest_updated_at: timerange.first[:to].iso8601,
|
169
|
+
},
|
76
170
|
attributes!: {lead_selector: {"xsi:type"=>"ns1:LastUpdateAtSelector"}},
|
77
|
-
batch_size:
|
171
|
+
batch_size: MarketoApi::Soap::Lead::BATCH_SIZE_DEFAULT,
|
78
172
|
}
|
79
173
|
end
|
80
174
|
end
|
@@ -114,24 +208,35 @@ module Embulk
|
|
114
208
|
wsdl: "https://marketo.example.com/?wsdl",
|
115
209
|
user_id: "user_id",
|
116
210
|
encryption_key: "TOPSECRET",
|
117
|
-
|
211
|
+
from_datetime: from_datetime,
|
212
|
+
to_datetime: to_datetime,
|
118
213
|
columns: [
|
119
214
|
{"name" => "Name", "type" => "string"},
|
120
215
|
]
|
121
216
|
}
|
122
217
|
end
|
123
218
|
|
124
|
-
def
|
219
|
+
def from_datetime
|
125
220
|
"2015-07-01 00:00:00+00:00"
|
126
221
|
end
|
127
222
|
|
223
|
+
def to_datetime
|
224
|
+
"2015-07-01 00:00:05+00:00"
|
225
|
+
end
|
226
|
+
|
227
|
+
def timerange
|
228
|
+
soap = MarketoApi::Soap::Lead.new(settings[:endpoint], settings[:wsdl], settings[:user_id], settings[:encryption_key])
|
229
|
+
soap.send(:generate_time_range, from_datetime, to_datetime)
|
230
|
+
end
|
231
|
+
|
128
232
|
def task
|
129
233
|
{
|
130
234
|
endpoint_url: "https://marketo.example.com",
|
131
235
|
wsdl_url: "https://marketo.example.com/?wsdl",
|
132
236
|
user_id: "user_id",
|
133
237
|
encryption_key: "TOPSECRET",
|
134
|
-
|
238
|
+
from_datetime: from_datetime,
|
239
|
+
to_datetime: to_datetime,
|
135
240
|
columns: [
|
136
241
|
{"name" => "Name", "type" => "string"},
|
137
242
|
]
|
@@ -163,7 +268,7 @@ module Embulk
|
|
163
268
|
[
|
164
269
|
{name: "id", type: "long"},
|
165
270
|
{name: "email", type: "string"},
|
166
|
-
{name: "FieldName", type: "
|
271
|
+
{name: "FieldName", type: "timestamp"},
|
167
272
|
]
|
168
273
|
end
|
169
274
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "embulk/input/marketo_api/soap/lead"
|
2
2
|
require "lead_fixtures"
|
3
|
+
require "mute_logger"
|
3
4
|
|
4
5
|
module Embulk
|
5
6
|
module Input
|
@@ -7,32 +8,97 @@ module Embulk
|
|
7
8
|
module Soap
|
8
9
|
class LeadTest < Test::Unit::TestCase
|
9
10
|
include LeadFixtures
|
11
|
+
include MuteLogger
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
13
|
+
def setup
|
14
|
+
mute_logger
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
class TestEach < self
|
18
|
+
def setup
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_each_invoke_fetch
|
23
|
+
from_datetime = "2015-07-06"
|
24
|
+
to_datetime = "2015-07-07"
|
25
|
+
timerange = soap.send(:generate_time_range, from_datetime, to_datetime)
|
26
|
+
|
27
|
+
stub(soap).fetch { nil }
|
28
|
+
mock(soap).fetch(anything).times(timerange.length)
|
29
|
+
|
30
|
+
soap.each(from_datetime, to_datetime) { }
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_each_invoke_fetch_with_specified_time
|
34
|
+
from_datetime = "2015-07-06"
|
35
|
+
to_datetime = "2015-07-07"
|
36
|
+
timerange = soap.send(:generate_time_range, from_datetime, to_datetime)
|
37
|
+
|
38
|
+
request = {
|
39
|
+
lead_selector: {
|
40
|
+
oldest_updated_at: timerange.first[:from].iso8601,
|
41
|
+
latest_updated_at: timerange.first[:to].iso8601,
|
42
|
+
},
|
43
|
+
attributes!: {lead_selector: {"xsi:type"=>"ns1:LastUpdateAtSelector"}},
|
44
|
+
batch_size: Lead::BATCH_SIZE_DEFAULT,
|
45
|
+
}
|
46
|
+
|
47
|
+
stub(soap).fetch { nil }
|
48
|
+
mock(soap).fetch(request)
|
49
|
+
|
50
|
+
soap.each(from_datetime, to_datetime) { }
|
51
|
+
end
|
20
52
|
|
21
|
-
|
22
|
-
|
23
|
-
|
53
|
+
def test_each_fetch_next_page
|
54
|
+
from_datetime = "2015-07-06 00:00:00"
|
55
|
+
to_datetime = "2015-07-06 00:00:01"
|
56
|
+
|
57
|
+
any_instance_of(Savon::Client) do |klass|
|
58
|
+
mock(klass).call(:get_multiple_leads, anything) do
|
59
|
+
next_stream_leads_response
|
60
|
+
end
|
24
61
|
end
|
62
|
+
|
63
|
+
proc = proc{ "" }
|
64
|
+
leads_count = next_stream_leads_response.xpath('//leadRecord').length
|
65
|
+
mock(proc).call(anything).times(leads_count)
|
66
|
+
|
67
|
+
soap.each(from_datetime, to_datetime, &proc)
|
25
68
|
end
|
69
|
+
end
|
26
70
|
|
27
|
-
|
28
|
-
|
29
|
-
|
71
|
+
class TestGenerateTime < self
|
72
|
+
def setup
|
73
|
+
mute_logger
|
74
|
+
end
|
30
75
|
|
31
|
-
|
76
|
+
data do
|
77
|
+
{
|
78
|
+
"8/1 to 8/2" => ["2015-08-01 00:00:00", "2015-08-02 00:00:00", 24],
|
79
|
+
"over the days" => ["2015-08-01 19:00:00", "2015-08-03 05:00:00", 34],
|
80
|
+
"odd times" => ["2015-08-01 11:11:11", "2015-08-01 22:22:22", 12],
|
81
|
+
}
|
82
|
+
end
|
83
|
+
def test_generate_time_range_by_1hour(data)
|
84
|
+
from, to, count = data
|
85
|
+
range = soap.send(:generate_time_range, from, to)
|
86
|
+
assert_equal count, range.length
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_if_to_is_nil_use_time_now
|
90
|
+
from = "2000-01-01"
|
91
|
+
now = Time.now
|
92
|
+
stub(Time).now { now }
|
93
|
+
|
94
|
+
range = soap.send(:generate_time_range, from, nil)
|
95
|
+
assert_equal now, range.last[:to]
|
96
|
+
end
|
32
97
|
end
|
33
98
|
|
34
99
|
class TestMetadata < self
|
35
100
|
def setup
|
101
|
+
super
|
36
102
|
@savon = soap.__send__(:savon)
|
37
103
|
stub(soap).savon { @savon } # Pin savon instance for each call soap.savon for mocking/stubbing
|
38
104
|
end
|
data/test/mute_logger.rb
ADDED
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.
|
4
|
+
version: 0.2.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-08-
|
12
|
+
date: 2015-08-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,10 +175,12 @@ files:
|
|
175
175
|
- lib/embulk/input/marketo/activity_log.rb
|
176
176
|
- lib/embulk/input/marketo/base.rb
|
177
177
|
- lib/embulk/input/marketo/lead.rb
|
178
|
+
- lib/embulk/input/marketo/timeslice.rb
|
178
179
|
- lib/embulk/input/marketo_api.rb
|
179
180
|
- lib/embulk/input/marketo_api/soap/activity_log.rb
|
180
181
|
- lib/embulk/input/marketo_api/soap/base.rb
|
181
182
|
- lib/embulk/input/marketo_api/soap/lead.rb
|
183
|
+
- lib/embulk/input/marketo_api/soap/timeslice.rb
|
182
184
|
- test/activity_log_fixtures.rb
|
183
185
|
- test/embulk/input/marketo/test_activity_log.rb
|
184
186
|
- test/embulk/input/marketo/test_base.rb
|
@@ -188,6 +190,7 @@ files:
|
|
188
190
|
- test/embulk/input/marketo_api/soap/test_lead.rb
|
189
191
|
- test/embulk/input/test_marketo_api.rb
|
190
192
|
- test/lead_fixtures.rb
|
193
|
+
- test/mute_logger.rb
|
191
194
|
- test/prepare_embulk.rb
|
192
195
|
- test/run-test.rb
|
193
196
|
homepage: https://github.com/treasure-data/embulk-input-marketo
|
@@ -224,5 +227,6 @@ test_files:
|
|
224
227
|
- test/embulk/input/marketo_api/soap/test_lead.rb
|
225
228
|
- test/embulk/input/test_marketo_api.rb
|
226
229
|
- test/lead_fixtures.rb
|
230
|
+
- test/mute_logger.rb
|
227
231
|
- test/prepare_embulk.rb
|
228
232
|
- test/run-test.rb
|