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