embulk-input-marketo 0.1.1 → 0.2.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 +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
|