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
         
     |