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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9d395df72b2b9fafe6be7824001fb866c00f1f08
4
- data.tar.gz: b344e828ecd3eb921ba38f9bc3c6bf26a3271ebe
3
+ metadata.gz: bcfd15d051ac950ef7529b31662348b01f6c5389
4
+ data.tar.gz: 1161b4da7811e5a0dd29454d5bd44b2ee6be8e73
5
5
  SHA512:
6
- metadata.gz: 1893639b47f6268c154c4636fc432152b3a9061e2a8292edba2b03c0b32a51b85152f0d2c5f861ccb5404e4d5ca091670b92e411a9a25c3316470735483d2b07
7
- data.tar.gz: c75fdc17d13700f6aef74022e9d5849777f5b1d861bd2a62ed2d24008c87245ee8cf7ed53c17acb432b8788222a9174a725ac11368571a88d482f355c9c55a5f
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
- last_updated_at: "2015-06-30"
72
+ from_datetime: "2015-06-30"
60
73
  out:
61
74
  type: stdout
62
75
  ```
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "embulk-input-marketo"
3
- spec.version = "0.1.1"
3
+ spec.version = "0.2.0"
4
4
  spec.authors = ["uu59", "yoshihara"]
5
5
  spec.summary = "Marketo input plugin for Embulk"
6
6
  spec.description = "Loads records from Marketo."
@@ -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 "dateTime", "date"
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
- @soap.each(@last_updated_at) do |lead|
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 = savon.call(:get_lead_changes, message: request)
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 = savon.call(:describe_m_object, message: {object_name: "LeadRecord"})
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(last_updated_at, &block)
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
- last_updated_at = Time.parse(last_updated_at).iso8601
18
-
19
- # TODO: generate request in #fetch
20
- # TODO: use PREVIEW_COUNT as batch_size in preview
21
- request = {
22
- lead_selector: {
23
- oldest_updated_at: last_updated_at,
24
- },
25
- attributes!: {
26
- lead_selector: {"xsi:type" => "ns1:LastUpdateAtSelector"}
27
- },
28
- batch_size: 1000,
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
- stream_position = fetch(request, &block)
37
+ stream_position = fetch(request, &block)
32
38
 
33
- while stream_position
34
- stream_position = fetch(request.merge(stream_position: stream_position), &block)
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
- response = savon.call(:get_multiple_leads, message: request)
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 "Remaining records: #{remaining}"
45
- response.xpath('//leadRecordList/leadRecord').each do |lead|
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
- stub(Embulk).logger { ::Logger.new(File::NULL) }
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: {oldest_updated_at: Time.parse(last_updated_at).iso8601},
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: 1000
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
- last_updated_at: last_updated_at,
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 last_updated_at
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
- last_updated_at: last_updated_at,
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: "string"},
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 test_each
12
- stub(Embulk).logger { ::Logger.new(IO::NULL) }
13
- last_updated_at = "2015-07-06"
13
+ def setup
14
+ mute_logger
15
+ end
14
16
 
15
- request = {
16
- lead_selector: {oldest_updated_at: Time.parse(last_updated_at).iso8601},
17
- attributes!: {lead_selector: {"xsi:type"=>"ns1:LastUpdateAtSelector"}},
18
- batch_size: 1000
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
- any_instance_of(Savon::Client) do |klass|
22
- mock(klass).call(:get_multiple_leads, message: request) do
23
- next_stream_leads_response
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
- proc = proc{ "" }
28
- leads_count = next_stream_leads_response.xpath('//leadRecord').length
29
- mock(proc).call(anything).times(leads_count)
71
+ class TestGenerateTime < self
72
+ def setup
73
+ mute_logger
74
+ end
30
75
 
31
- soap.each(last_updated_at, &proc)
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
@@ -0,0 +1,7 @@
1
+ module MuteLogger
2
+ private
3
+
4
+ def mute_logger
5
+ stub(Embulk).logger { ::Logger.new(IO::NULL) }
6
+ end
7
+ 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.1.1
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-19 00:00:00.000000000 Z
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