salesforce_bulk_api 0.0.11 → 0.0.12
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/.gitignore +1 -0
- data/README.md +12 -7
- data/example_auth_credentials.yml +2 -1
- data/lib/salesforce_bulk_api.rb +34 -9
- data/lib/salesforce_bulk_api/concerns/throttling.rb +60 -0
- data/lib/salesforce_bulk_api/connection.rb +25 -4
- data/lib/salesforce_bulk_api/job.rb +8 -9
- data/lib/salesforce_bulk_api/version.rb +1 -1
- data/salesforce_bulk_api.gemspec +7 -8
- data/spec/salesforce_bulk_api/salesforce_bulk_api_spec.rb +37 -12
- data/spec/spec_helper.rb +0 -8
- metadata +10 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec07d0cc7d5f0c5795459e444948d45ed308dde1
|
4
|
+
data.tar.gz: 0faae2875e7ec75bf9a8bb8dfe54025b607cedb6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c68f01bc8d22feb69025bed933e52049e7227e70a8df2e4496ac89f224e6a7571199510ac2a5c61e9764e8f828a6d776a8b05bd78d88560f8baf1bde5ccc18a2
|
7
|
+
data.tar.gz: d91276c381a0f188f105af7442a95ef5c4e75f399f4d55a37906b3e5c1bfa0f6bcaecc6c2d8fde967a10ac8ea15e01913b71a426f7e71a216e18023d9e7d7ecb
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -50,7 +50,7 @@ OR
|
|
50
50
|
salesforce = SalesforceBulkApi::Api.new(client)
|
51
51
|
|
52
52
|
|
53
|
-
Sample operations:
|
53
|
+
### Sample operations:
|
54
54
|
|
55
55
|
# Insert/Create
|
56
56
|
# Add as many fields per record as needed.
|
@@ -82,21 +82,26 @@ Sample operations:
|
|
82
82
|
# Query
|
83
83
|
res = salesforce.query("Account", "select id, name, createddate from Account limit 3") # We just need to pass the sobject name and the query string
|
84
84
|
|
85
|
-
Helpful methods:
|
85
|
+
### Helpful methods:
|
86
86
|
|
87
87
|
# Check status of a job via #job_from_id
|
88
88
|
job = salesforce.job_from_id('a00A0001009zA2m') # Returns a SalesforceBulkApi::Job instance
|
89
89
|
puts "status is: #{job.check_job_status.inspect}"
|
90
90
|
|
91
|
-
|
92
|
-
Listening to events:
|
91
|
+
### Listening to events:
|
93
92
|
|
94
93
|
# A job is created
|
95
94
|
# Useful when you need to store the job_id before any work begins, then if you fail during a complex load scenario, you can wait for your
|
96
95
|
# previous job(s) to finish.
|
97
|
-
|
98
|
-
|
99
|
-
|
96
|
+
salesforce.on_job_created do |job|
|
97
|
+
puts "Job #{job.job_id} created!"
|
98
|
+
end
|
99
|
+
|
100
|
+
### Throttling API calls:
|
101
|
+
|
102
|
+
# By default, this gem (and maybe your app driving it) will query job/batch statuses at an unbounded rate. We
|
103
|
+
# can fix that, e.g.:
|
104
|
+
salesforce.connection.set_status_throttle(30) # only check status of individual jobs/batches every 30 seconds
|
100
105
|
|
101
106
|
## Installation
|
102
107
|
|
data/lib/salesforce_bulk_api.rb
CHANGED
@@ -1,44 +1,59 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler'
|
3
3
|
Bundler.require()
|
4
|
-
require
|
4
|
+
require 'salesforce_bulk_api/version'
|
5
5
|
require 'net/https'
|
6
6
|
require 'xmlsimple'
|
7
7
|
require 'csv'
|
8
|
+
require 'salesforce_bulk_api/concerns/throttling'
|
8
9
|
require 'salesforce_bulk_api/job'
|
9
10
|
require 'salesforce_bulk_api/connection'
|
10
11
|
|
11
12
|
module SalesforceBulkApi
|
12
13
|
|
13
14
|
class Api
|
15
|
+
attr_reader :connection
|
14
16
|
|
15
17
|
@@SALESFORCE_API_VERSION = '32.0'
|
16
18
|
|
17
19
|
def initialize(client)
|
18
|
-
@connection = SalesforceBulkApi::Connection.new(@@SALESFORCE_API_VERSION,client)
|
20
|
+
@connection = SalesforceBulkApi::Connection.new(@@SALESFORCE_API_VERSION, client)
|
19
21
|
@listeners = { job_created: [] }
|
20
22
|
end
|
21
23
|
|
22
24
|
def upsert(sobject, records, external_field, get_response = false, send_nulls = false, no_null_list = [], batch_size = 10000, timeout = 1500)
|
23
|
-
|
25
|
+
do_operation('upsert', sobject, records, external_field, get_response, timeout, batch_size, send_nulls, no_null_list)
|
24
26
|
end
|
25
27
|
|
26
28
|
def update(sobject, records, get_response = false, send_nulls = false, no_null_list = [], batch_size = 10000, timeout = 1500)
|
27
|
-
|
29
|
+
do_operation('update', sobject, records, nil, get_response, timeout, batch_size, send_nulls, no_null_list)
|
28
30
|
end
|
29
31
|
|
30
32
|
def create(sobject, records, get_response = false, send_nulls = false, batch_size = 10000, timeout = 1500)
|
31
|
-
|
33
|
+
do_operation('insert', sobject, records, nil, get_response, timeout, batch_size, send_nulls)
|
32
34
|
end
|
33
35
|
|
34
36
|
def delete(sobject, records, get_response = false, batch_size = 10000, timeout = 1500)
|
35
|
-
|
37
|
+
do_operation('delete', sobject, records, nil, get_response, timeout, batch_size)
|
36
38
|
end
|
37
39
|
|
38
40
|
def query(sobject, query, batch_size = 10000, timeout = 1500)
|
39
|
-
|
41
|
+
do_operation('query', sobject, query, nil, true, timeout, batch_size)
|
40
42
|
end
|
41
43
|
|
44
|
+
def counters
|
45
|
+
{
|
46
|
+
http_get: @connection.counters[:get],
|
47
|
+
http_post: @connection.counters[:post],
|
48
|
+
upsert: get_counters[:upsert],
|
49
|
+
update: get_counters[:update],
|
50
|
+
create: get_counters[:create],
|
51
|
+
delete: get_counters[:delete],
|
52
|
+
query: get_counters[:query]
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
|
42
57
|
##
|
43
58
|
# Allows you to attach a listener that accepts the created job (which has a useful #job_id field). This is useful
|
44
59
|
# for recording a job ID persistently before you begin batch work (i.e. start modifying the salesforce database),
|
@@ -52,9 +67,9 @@ module SalesforceBulkApi
|
|
52
67
|
SalesforceBulkApi::Job.new(job_id: job_id, connection: @connection)
|
53
68
|
end
|
54
69
|
|
55
|
-
#private
|
56
|
-
|
57
70
|
def do_operation(operation, sobject, records, external_field, get_response, timeout, batch_size, send_nulls = false, no_null_list = [])
|
71
|
+
count operation.to_sym
|
72
|
+
|
58
73
|
job = SalesforceBulkApi::Job.new(operation: operation, sobject: sobject, records: records, external_field: external_field, connection: @connection)
|
59
74
|
|
60
75
|
job.create_job(batch_size, send_nulls, no_null_list)
|
@@ -64,5 +79,15 @@ module SalesforceBulkApi
|
|
64
79
|
response.merge!({'batches' => job.get_job_result(get_response, timeout)}) if get_response == true
|
65
80
|
response
|
66
81
|
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def get_counters
|
85
|
+
@counters ||= Hash.new(0)
|
86
|
+
end
|
87
|
+
|
88
|
+
def count(name)
|
89
|
+
get_counters[name] += 1
|
90
|
+
end
|
91
|
+
|
67
92
|
end
|
68
93
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module SalesforceBulkApi::Concerns
|
2
|
+
module Throttling
|
3
|
+
|
4
|
+
def throttles
|
5
|
+
@throttles.dup
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_throttle(&throttling_callback)
|
9
|
+
@throttles ||= []
|
10
|
+
@throttles << throttling_callback
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_status_throttle(limit_seconds)
|
14
|
+
set_throttle_limit_in_seconds(limit_seconds, [:http_method, :path], ->(details) { details[:path] == :get })
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_throttle_limit_in_seconds(limit_seconds, throttle_by_keys, only_if)
|
18
|
+
add_throttle do |details|
|
19
|
+
limit_log = get_limit_log(Time.now - limit_seconds)
|
20
|
+
key = extract_constraint_key_from(details, throttle_by_keys)
|
21
|
+
last_request = limit_log[key]
|
22
|
+
|
23
|
+
if !last_request.nil? && only_if.call(details)
|
24
|
+
seconds_since_last_request = Time.now.to_f - last_request.to_f
|
25
|
+
need_to_wait_seconds = limit_seconds - seconds_since_last_request
|
26
|
+
sleep(need_to_wait_seconds) if need_to_wait_seconds > 0
|
27
|
+
end
|
28
|
+
|
29
|
+
limit_log[key] = Time.now
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def extract_constraint_key_from(details, throttle_by_keys)
|
36
|
+
hash = {}
|
37
|
+
throttle_by_keys.each { |k| hash[k] = details[k] }
|
38
|
+
hash
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_limit_log(prune_older_than)
|
42
|
+
@limits ||= Hash.new(0)
|
43
|
+
|
44
|
+
@limits.delete_if do |k, v|
|
45
|
+
v < prune_older_than
|
46
|
+
end
|
47
|
+
|
48
|
+
@limits
|
49
|
+
end
|
50
|
+
|
51
|
+
def throttle(details={})
|
52
|
+
(@throttles || []).each do |callback|
|
53
|
+
args = [details]
|
54
|
+
args = args[0..callback.arity]
|
55
|
+
callback.call(*args)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -2,6 +2,7 @@ module SalesforceBulkApi
|
|
2
2
|
require 'timeout'
|
3
3
|
|
4
4
|
class Connection
|
5
|
+
include Concerns::Throttling
|
5
6
|
|
6
7
|
@@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
|
7
8
|
@@API_VERSION = nil
|
@@ -20,8 +21,6 @@ require 'timeout'
|
|
20
21
|
login()
|
21
22
|
end
|
22
23
|
|
23
|
-
#private
|
24
|
-
|
25
24
|
def login()
|
26
25
|
client_type = @client.class.to_s
|
27
26
|
case client_type
|
@@ -39,11 +38,13 @@ require 'timeout'
|
|
39
38
|
def post_xml(host, path, xml, headers)
|
40
39
|
host = host || @@INSTANCE_HOST
|
41
40
|
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
42
|
-
headers['X-SFDC-Session'] = @session_id
|
41
|
+
headers['X-SFDC-Session'] = @session_id
|
43
42
|
path = "#{@@PATH_PREFIX}#{path}"
|
44
43
|
end
|
45
44
|
i = 0
|
46
45
|
begin
|
46
|
+
count :post
|
47
|
+
throttle(http_method: :post, path: path)
|
47
48
|
https(host).post(path, xml, headers).body
|
48
49
|
rescue
|
49
50
|
i += 1
|
@@ -63,6 +64,9 @@ require 'timeout'
|
|
63
64
|
if host != @@LOGIN_HOST # Not login, need to add session id to header
|
64
65
|
headers['X-SFDC-Session'] = @session_id;
|
65
66
|
end
|
67
|
+
|
68
|
+
count :get
|
69
|
+
throttle(http_method: :get, path: path)
|
66
70
|
https(host).get(path, headers).body
|
67
71
|
end
|
68
72
|
|
@@ -74,11 +78,28 @@ require 'timeout'
|
|
74
78
|
end
|
75
79
|
|
76
80
|
def parse_instance()
|
77
|
-
@instance
|
81
|
+
@instance = @server_url.match(/https:\/\/[a-z]{2}[0-9]{1,2}/).to_s.gsub("https://","")
|
78
82
|
@instance = @server_url.split(".salesforce.com")[0].split("://")[1] if @instance.nil? || @instance.empty?
|
79
83
|
return @instance
|
80
84
|
end
|
81
85
|
|
86
|
+
def counters
|
87
|
+
{
|
88
|
+
get: get_counters[:get],
|
89
|
+
post: get_counters[:post]
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def get_counters
|
96
|
+
@counters ||= Hash.new(0)
|
97
|
+
end
|
98
|
+
|
99
|
+
def count(http_method)
|
100
|
+
get_counters[http_method] += 1
|
101
|
+
end
|
102
|
+
|
82
103
|
end
|
83
104
|
|
84
105
|
end
|
@@ -5,13 +5,13 @@ module SalesforceBulkApi
|
|
5
5
|
|
6
6
|
class SalesforceException < StandardError; end
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@job_id = job_id
|
10
|
-
@operation = operation
|
11
|
-
@sobject = sobject
|
12
|
-
@external_field = external_field
|
13
|
-
@records = records
|
14
|
-
@connection = connection
|
8
|
+
def initialize(args)
|
9
|
+
@job_id = args[:job_id]
|
10
|
+
@operation = args[:operation]
|
11
|
+
@sobject = args[:sobject]
|
12
|
+
@external_field = args[:external_field]
|
13
|
+
@records = args[:records]
|
14
|
+
@connection = args[:connection]
|
15
15
|
@batch_ids = []
|
16
16
|
@XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>'
|
17
17
|
end
|
@@ -69,7 +69,7 @@ module SalesforceBulkApi
|
|
69
69
|
|
70
70
|
def add_batches
|
71
71
|
raise 'Records must be an array of hashes.' unless @records.is_a? Array
|
72
|
-
keys = @records.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}.keys
|
72
|
+
keys = @records.reduce({}) {|h, pairs| pairs.each {|k, v| (h[k] ||= []) << v}; h}.keys
|
73
73
|
|
74
74
|
@records_dup = @records.clone
|
75
75
|
|
@@ -103,7 +103,6 @@ module SalesforceBulkApi
|
|
103
103
|
if k.is_a?(Hash)
|
104
104
|
xml += build_sobject(k)
|
105
105
|
elsif data[k] != :type
|
106
|
-
#xml += "<type>#{data[:type]}</type>"
|
107
106
|
xml += "<#{k}>#{data[k]}</#{k}>"
|
108
107
|
end
|
109
108
|
end
|
data/salesforce_bulk_api.gemspec
CHANGED
@@ -14,15 +14,14 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.rubyforge_project = 'salesforce_bulk_api'
|
16
16
|
|
17
|
-
s.add_dependency(
|
18
|
-
s.add_dependency(
|
19
|
-
|
17
|
+
s.add_dependency('json', ['>= 0'])
|
18
|
+
s.add_dependency('xml-simple', ['>= 0'])
|
19
|
+
|
20
20
|
s.add_development_dependency 'rspec'
|
21
|
-
s.add_development_dependency
|
22
|
-
s.add_development_dependency
|
23
|
-
s.add_development_dependency '
|
24
|
-
|
25
|
-
|
21
|
+
s.add_development_dependency 'restforce', '~> 1.5.1'
|
22
|
+
s.add_development_dependency 'rake', '~> 10.4.2'
|
23
|
+
s.add_development_dependency 'pry'
|
24
|
+
|
26
25
|
s.files = `git ls-files`.split("\n")
|
27
26
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
28
27
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -1,14 +1,20 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'yaml'
|
3
|
-
require '
|
3
|
+
require 'restforce'
|
4
4
|
|
5
5
|
describe SalesforceBulkApi do
|
6
6
|
|
7
7
|
before :each do
|
8
|
-
auth_hash = YAML.
|
9
|
-
|
10
|
-
|
11
|
-
@sf_client.
|
8
|
+
auth_hash = YAML.load_file('auth_credentials.yml')
|
9
|
+
sfdc_auth_hash = auth_hash['salesforce']
|
10
|
+
|
11
|
+
@sf_client = Restforce.new(
|
12
|
+
username: sfdc_auth_hash['user'],
|
13
|
+
password: sfdc_auth_hash['passwordandtoken'],
|
14
|
+
client_id: sfdc_auth_hash['client_id'],
|
15
|
+
client_secret: sfdc_auth_hash['client_secret'],
|
16
|
+
host: sfdc_auth_hash['host'])
|
17
|
+
@sf_client.authenticate!
|
12
18
|
|
13
19
|
@account_id = auth_hash['salesforce']['test_account_id']
|
14
20
|
|
@@ -22,7 +28,7 @@ describe SalesforceBulkApi do
|
|
22
28
|
describe 'upsert' do
|
23
29
|
|
24
30
|
context 'when not passed get_result' do
|
25
|
-
it "doesn't return the batches array" do
|
31
|
+
it "doesn't return the batches array" do
|
26
32
|
res = @api.upsert('Account', [{:Id => @account_id, :Website => 'www.test.com'}], 'Id')
|
27
33
|
res['batches'].should be_nil
|
28
34
|
end
|
@@ -139,13 +145,10 @@ describe SalesforceBulkApi do
|
|
139
145
|
res['batches'][0]['response'].length.should > 1
|
140
146
|
res['batches'][0]['response'][0]['Id'].should_not be_nil
|
141
147
|
end
|
148
|
+
|
142
149
|
context 'and there are multiple batches' do
|
143
|
-
|
144
|
-
|
145
|
-
res = @api.query('Account', "SELECT id, Name From Account WHERE Name LIKE 'Test%'")
|
146
|
-
res['batches'][0]['response'].length.should > 1
|
147
|
-
res['batches'][0]['response'][0]['Id'].should_not be_nil
|
148
|
-
end
|
150
|
+
# need dev to create > 10k records in dev organization
|
151
|
+
it 'returns the query results in a merged hash'
|
149
152
|
end
|
150
153
|
end
|
151
154
|
|
@@ -165,4 +168,26 @@ describe SalesforceBulkApi do
|
|
165
168
|
|
166
169
|
end
|
167
170
|
|
171
|
+
describe 'counters' do
|
172
|
+
context 'when read operations are called' do
|
173
|
+
it 'increments operation count and http GET count' do
|
174
|
+
@api.counters[:http_get].should eq 0
|
175
|
+
@api.counters[:query].should eq 0
|
176
|
+
@api.query('Account', "SELECT Website, Phone From Account WHERE Id = '#{@account_id}'")
|
177
|
+
@api.counters[:http_get].should eq 1
|
178
|
+
@api.counters[:query].should eq 1
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'when update operations are called' do
|
183
|
+
it 'increments operation count and http POST count' do
|
184
|
+
@api.counters[:http_post].should eq 0
|
185
|
+
@api.counters[:update].should eq 0
|
186
|
+
@api.update('Account', [{:Id => @account_id, :Website => 'abc123', :Phone => '5678'}], true)
|
187
|
+
@api.counters[:http_post].should eq 1
|
188
|
+
@api.counters[:update].should eq 1
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
168
193
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,16 +1,8 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler/setup'
|
3
|
-
#require 'webmock/rspec'
|
4
|
-
#require 'vcr'
|
5
3
|
require 'salesforce_bulk_api'
|
6
4
|
|
7
5
|
RSpec.configure do |c|
|
8
6
|
c.filter_run :focus => true
|
9
7
|
c.run_all_when_everything_filtered = true
|
10
8
|
end
|
11
|
-
|
12
|
-
# enable this and record the test requests using a SF developer org.
|
13
|
-
# VCR.configure do |c|
|
14
|
-
# c.cassette_library_dir = 'spec/cassettes'
|
15
|
-
# c.hook_into :webmock
|
16
|
-
# end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: salesforce_bulk_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yatish Mehta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -53,35 +53,35 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: restforce
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 1.5.1
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 1.5.1
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 10.4.2
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 10.4.2
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: pry
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- Rakefile
|
109
109
|
- example_auth_credentials.yml
|
110
110
|
- lib/salesforce_bulk_api.rb
|
111
|
+
- lib/salesforce_bulk_api/concerns/throttling.rb
|
111
112
|
- lib/salesforce_bulk_api/connection.rb
|
112
113
|
- lib/salesforce_bulk_api/job.rb
|
113
114
|
- lib/salesforce_bulk_api/version.rb
|