rocketjob 3.5.2 → 4.0.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/README.md +63 -1
- data/bin/rocketjob +1 -0
- data/bin/rocketjob_batch_perf +11 -0
- data/lib/rocket_job/batch.rb +32 -0
- data/lib/rocket_job/batch/callbacks.rb +40 -0
- data/lib/rocket_job/batch/io.rb +154 -0
- data/lib/rocket_job/batch/logger.rb +57 -0
- data/lib/rocket_job/batch/lower_priority.rb +54 -0
- data/lib/rocket_job/batch/model.rb +157 -0
- data/lib/rocket_job/batch/performance.rb +99 -0
- data/lib/rocket_job/batch/result.rb +8 -0
- data/lib/rocket_job/batch/results.rb +9 -0
- data/lib/rocket_job/batch/state_machine.rb +102 -0
- data/lib/rocket_job/batch/statistics.rb +88 -0
- data/lib/rocket_job/batch/tabular.rb +56 -0
- data/lib/rocket_job/batch/tabular/input.rb +123 -0
- data/lib/rocket_job/batch/tabular/output.rb +59 -0
- data/lib/rocket_job/batch/throttle.rb +91 -0
- data/lib/rocket_job/batch/throttle_running_slices.rb +53 -0
- data/lib/rocket_job/batch/worker.rb +288 -0
- data/lib/rocket_job/cli.rb +29 -7
- data/lib/rocket_job/config.rb +1 -1
- data/lib/rocket_job/extensions/mongoid/clients/options.rb +37 -0
- data/lib/rocket_job/extensions/mongoid/contextual/mongo.rb +17 -0
- data/lib/rocket_job/extensions/mongoid/factory.rb +4 -4
- data/lib/rocket_job/extensions/mongoid_5/clients/options.rb +38 -0
- data/lib/rocket_job/extensions/mongoid_5/contextual/mongo.rb +64 -0
- data/lib/rocket_job/extensions/mongoid_5/factory.rb +13 -0
- data/lib/rocket_job/jobs/on_demand_batch_job.rb +127 -0
- data/lib/rocket_job/jobs/performance_job.rb +18 -0
- data/lib/rocket_job/jobs/upload_file_job.rb +2 -5
- data/lib/rocket_job/plugins/document.rb +2 -8
- data/lib/rocket_job/plugins/job/persistence.rb +6 -4
- data/lib/rocket_job/plugins/job/throttle.rb +3 -6
- data/lib/rocket_job/plugins/job/worker.rb +2 -2
- data/lib/rocket_job/server.rb +14 -3
- data/lib/rocket_job/sliced/input.rb +336 -0
- data/lib/rocket_job/sliced/output.rb +99 -0
- data/lib/rocket_job/sliced/slice.rb +166 -0
- data/lib/rocket_job/sliced/slices.rb +166 -0
- data/lib/rocket_job/sliced/writer/input.rb +60 -0
- data/lib/rocket_job/sliced/writer/output.rb +82 -0
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +2 -2
- data/lib/rocketjob.rb +28 -0
- metadata +51 -62
- data/test/config/database.yml +0 -5
- data/test/config/mongoid.yml +0 -88
- data/test/config_test.rb +0 -10
- data/test/dirmon_entry_test.rb +0 -313
- data/test/dirmon_job_test.rb +0 -216
- data/test/files/text.txt +0 -3
- data/test/job_test.rb +0 -71
- data/test/jobs/housekeeping_job_test.rb +0 -102
- data/test/jobs/on_demand_job_test.rb +0 -59
- data/test/jobs/upload_file_job_test.rb +0 -107
- data/test/plugins/cron_test.rb +0 -166
- data/test/plugins/job/callbacks_test.rb +0 -166
- data/test/plugins/job/defaults_test.rb +0 -53
- data/test/plugins/job/logger_test.rb +0 -56
- data/test/plugins/job/model_test.rb +0 -94
- data/test/plugins/job/persistence_test.rb +0 -94
- data/test/plugins/job/state_machine_test.rb +0 -116
- data/test/plugins/job/throttle_test.rb +0 -111
- data/test/plugins/job/worker_test.rb +0 -199
- data/test/plugins/processing_window_test.rb +0 -109
- data/test/plugins/restart_test.rb +0 -193
- data/test/plugins/retry_test.rb +0 -88
- data/test/plugins/singleton_test.rb +0 -92
- data/test/plugins/state_machine_event_callbacks_test.rb +0 -102
- data/test/plugins/state_machine_test.rb +0 -67
- data/test/plugins/transaction_test.rb +0 -84
- data/test/test_db.sqlite3 +0 -0
- data/test/test_helper.rb +0 -17
data/lib/rocket_job/cli.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
require 'optparse'
|
2
|
+
require 'json'
|
2
3
|
require 'semantic_logger'
|
3
4
|
require 'mongoid'
|
4
5
|
require 'rocketjob'
|
5
|
-
require '
|
6
|
+
require 'pathname'
|
6
7
|
module RocketJob
|
7
8
|
# Command Line Interface parser for Rocket Job
|
8
9
|
class CLI
|
9
10
|
include SemanticLogger::Loggable
|
10
11
|
attr_accessor :name, :workers, :environment, :pidfile, :directory, :quiet,
|
11
12
|
:log_level, :log_file, :mongo_config, :symmetric_encryption_config,
|
12
|
-
:
|
13
|
+
:include_filter, :exclude_filter, :where_filter
|
13
14
|
|
14
15
|
def initialize(argv)
|
15
16
|
@name = nil
|
@@ -22,7 +23,8 @@ module RocketJob
|
|
22
23
|
@log_file = nil
|
23
24
|
@mongo_config = nil
|
24
25
|
@symmetric_encryption_config = nil
|
25
|
-
@
|
26
|
+
@include_filter = nil
|
27
|
+
@exclude_filter = nil
|
26
28
|
parse(argv)
|
27
29
|
end
|
28
30
|
|
@@ -38,10 +40,13 @@ module RocketJob
|
|
38
40
|
# In case Rails did not load the Mongoid Config
|
39
41
|
RocketJob::Config.load!(environment, mongo_config, symmetric_encryption_config) if ::Mongoid::Config.clients.empty?
|
40
42
|
|
43
|
+
filter = build_filter
|
44
|
+
|
41
45
|
opts = {}
|
42
46
|
opts[:name] = name if name
|
43
47
|
opts[:max_workers] = workers if workers
|
44
|
-
opts[:filter] =
|
48
|
+
opts[:filter] = filter if filter
|
49
|
+
|
45
50
|
Server.run(opts)
|
46
51
|
end
|
47
52
|
|
@@ -91,7 +96,7 @@ module RocketJob
|
|
91
96
|
|
92
97
|
require 'rocketjob'
|
93
98
|
begin
|
94
|
-
require '
|
99
|
+
require 'rocketjob_batch'
|
95
100
|
rescue LoadError
|
96
101
|
nil
|
97
102
|
end
|
@@ -148,6 +153,17 @@ module RocketJob
|
|
148
153
|
end
|
149
154
|
end
|
150
155
|
|
156
|
+
# Returns [Hash] a where clause filter to apply to this server.
|
157
|
+
# Returns nil if no filter should be applied
|
158
|
+
def build_filter
|
159
|
+
raise(ArgumentError, 'Cannot supply both a filter and an exclusion filter') if include_filter && exclude_filter
|
160
|
+
|
161
|
+
filter = where_filter
|
162
|
+
(filter ||= {})['_type'] = include_filter if include_filter
|
163
|
+
(filter ||= {})['_type'] = {'$not' => exclude_filter} if exclude_filter
|
164
|
+
filter
|
165
|
+
end
|
166
|
+
|
151
167
|
# Parse command line options placing results in the corresponding instance variables
|
152
168
|
def parse(argv)
|
153
169
|
parser = OptionParser.new do |o|
|
@@ -161,8 +177,14 @@ module RocketJob
|
|
161
177
|
warn '-t and --threads are deprecated, use -w or --workers'
|
162
178
|
@workers = arg.to_i
|
163
179
|
end
|
164
|
-
o.on('-F', '--filter REGEXP', 'Limit this
|
165
|
-
@
|
180
|
+
o.on('-F', '--filter REGEXP', 'Limit this server to only those job classes that match this regular expression (case-insensitive). Example: "DirmonJob|WeeklyReportJob"') do |arg|
|
181
|
+
@include_filter = Regexp.new(arg, true)
|
182
|
+
end
|
183
|
+
o.on('-E', '--exclude REGEXP', 'Prevent this server from working on any job classes that match this regular expression (case-insensitive). Example: "DirmonJob|WeeklyReportJob"') do |arg|
|
184
|
+
@exclude_filter = Regexp.new(arg, true)
|
185
|
+
end
|
186
|
+
o.on('-W', '--where JSON', "Limit this server instance to the supplied mongo query filter. Supply as a string in JSON format. Example: '{\"priority\":{\"$lte\":25}}'") do |arg|
|
187
|
+
@where_filter = JSON.parse(arg)
|
166
188
|
end
|
167
189
|
o.on('-q', '--quiet', 'Do not write to stdout, only to logfile. Necessary when running as a daemon') do
|
168
190
|
@quiet = true
|
data/lib/rocket_job/config.rb
CHANGED
@@ -55,7 +55,7 @@ module RocketJob
|
|
55
55
|
raise(ArgumentError, "Mongo Configuration file: #{config_file} not found") unless config_file.file?
|
56
56
|
|
57
57
|
logger.debug "Reading Mongo configuration from: #{config_file}"
|
58
|
-
Mongoid.load!(config_file, environment)
|
58
|
+
::Mongoid.load!(config_file, environment)
|
59
59
|
|
60
60
|
# Load Encryption configuration file if present
|
61
61
|
return unless defined?(SymmetricEncryption)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mongoid/criteria'
|
2
|
+
require 'mongoid/document'
|
3
|
+
module RocketJob
|
4
|
+
module MongoidClients
|
5
|
+
module Options
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def with_collection(collection_name)
|
9
|
+
self.collection_name = collection_name
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def collection(parent = nil)
|
14
|
+
@collection_name ? mongo_client[@collection_name] : super(parent)
|
15
|
+
end
|
16
|
+
|
17
|
+
def collection_name
|
18
|
+
@collection_name || super
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection_name=(collection_name)
|
22
|
+
@collection_name = collection_name&.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def with_collection(collection_name)
|
29
|
+
all.with_collection(collection_name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
::Mongoid::Criteria.include(RocketJob::MongoidClients::Options)
|
37
|
+
::Mongoid::Document.include(RocketJob::MongoidClients::Options)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
::Mongoid::Contextual::Mongo
|
2
|
+
module Mongoid
|
3
|
+
module Contextual
|
4
|
+
class Mongo
|
5
|
+
def initialize(criteria)
|
6
|
+
@criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
|
7
|
+
# Only line changed is here, get collection name from criteria, not @klass
|
8
|
+
#@collection = @klass.collection
|
9
|
+
@collection = criteria.collection
|
10
|
+
|
11
|
+
criteria.send(:merge_type_selection)
|
12
|
+
@view = collection.find(criteria.selector, session: _session)
|
13
|
+
apply_options
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -3,10 +3,10 @@ require 'mongoid/factory'
|
|
3
3
|
module RocketJob
|
4
4
|
# Don't convert to Mongoid::Factory since it conflicts with Mongoid use.
|
5
5
|
module MongoidFactory
|
6
|
-
def from_db(klass, attributes = nil,
|
7
|
-
super
|
8
|
-
|
9
|
-
|
6
|
+
def from_db(klass, attributes = nil, criteria = nil)
|
7
|
+
obj = super(klass, attributes, criteria)
|
8
|
+
obj.collection_name = criteria.collection_name if criteria
|
9
|
+
obj
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'mongoid/criteria'
|
2
|
+
require 'mongoid/document'
|
3
|
+
module RocketJob
|
4
|
+
module Mongoid5Clients
|
5
|
+
module Options
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def with_collection(collection_name)
|
9
|
+
self.collection_name = collection_name
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def collection
|
14
|
+
return (@klass || self.class).with(persistence_options || {}).collection unless @collection_name
|
15
|
+
(@klass || self.class).mongo_client[@collection_name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def collection_name
|
19
|
+
@collection_name || super
|
20
|
+
end
|
21
|
+
|
22
|
+
def collection_name=(collection_name)
|
23
|
+
@collection_name = collection_name&.to_sym
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def with_collection(collection_name)
|
30
|
+
all.with_collection(collection_name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
::Mongoid::Criteria.include(RocketJob::Mongoid5Clients::Options)
|
38
|
+
::Mongoid::Document.include(RocketJob::Mongoid5Clients::Options)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
::Mongoid::Contextual::Mongo
|
2
|
+
module Mongoid
|
3
|
+
module Contextual
|
4
|
+
class Mongo
|
5
|
+
def initialize(criteria)
|
6
|
+
@criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
|
7
|
+
|
8
|
+
# Only line changed is here, get collection name from criteria, not @klass
|
9
|
+
#@collection = @klass.with(criteria.persistence_options || {}).collection
|
10
|
+
@collection = criteria.collection
|
11
|
+
|
12
|
+
criteria.send(:merge_type_selection)
|
13
|
+
@view = collection.find(criteria.selector)
|
14
|
+
apply_options
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Patches below add `criteria` as the last argument to `Factory.from_db`
|
19
|
+
#
|
20
|
+
def first
|
21
|
+
return documents.first if cached? && cache_loaded?
|
22
|
+
try_cache(:first) do
|
23
|
+
if raw_doc = view.limit(-1).first
|
24
|
+
doc = Factory.from_db(klass, raw_doc, criteria.options[:fields], criteria)
|
25
|
+
eager_load([doc]).first
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_first
|
31
|
+
return documents.first if cached? && cache_loaded?
|
32
|
+
if raw_doc = view.first
|
33
|
+
doc = Factory.from_db(klass, raw_doc, criteria.options[:fields], criteria)
|
34
|
+
eager_load([doc]).first
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def last
|
39
|
+
try_cache(:last) do
|
40
|
+
with_inverse_sorting do
|
41
|
+
if raw_doc = view.limit(-1).first
|
42
|
+
doc = Factory.from_db(klass, raw_doc, criteria.options[:fields], criteria)
|
43
|
+
eager_load([doc]).first
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def documents_for_iteration
|
50
|
+
return documents if cached? && !documents.empty?
|
51
|
+
return view unless eager_loadable?
|
52
|
+
docs = view.map{ |doc| Factory.from_db(klass, doc, criteria.options[:fields], criteria) }
|
53
|
+
eager_load(docs)
|
54
|
+
end
|
55
|
+
|
56
|
+
def yield_document(document, &block)
|
57
|
+
doc = document.respond_to?(:_id) ?
|
58
|
+
document : Factory.from_db(klass, document, criteria.options[:fields], criteria)
|
59
|
+
yield(doc)
|
60
|
+
documents.push(doc) if cacheable?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'mongoid/factory'
|
2
|
+
|
3
|
+
module RocketJob
|
4
|
+
module Mongoid5Factory
|
5
|
+
def from_db(klass, attributes = nil, selected_fields = nil, criteria = nil)
|
6
|
+
obj = super(klass, attributes, selected_fields)
|
7
|
+
obj.collection_name = criteria.collection_name if criteria
|
8
|
+
obj
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
::Mongoid::Factory.extend(RocketJob::Mongoid5Factory)
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Generalized Batch Job.
|
2
|
+
#
|
3
|
+
# Often used for data correction or cleansing.
|
4
|
+
#
|
5
|
+
# Example: Iterate over all rows in a table:
|
6
|
+
# code = <<-CODE
|
7
|
+
# if user = User.find(row)
|
8
|
+
# user.cleanse_attributes!
|
9
|
+
# user.save(validate: false)
|
10
|
+
# end
|
11
|
+
# CODE
|
12
|
+
# job = RocketJob::Jobs::OnDemandBatchJob.new(code: code, description: 'cleanse users')
|
13
|
+
# arel = User.unscoped.all.order('updated_at DESC')
|
14
|
+
# job.record_count = input.upload_arel(arel)
|
15
|
+
# job.save!
|
16
|
+
#
|
17
|
+
# Console Testing:
|
18
|
+
# code = <<-CODE
|
19
|
+
# if user = User.find(row)
|
20
|
+
# user.cleanse_attributes!
|
21
|
+
# user.save(validate: false)
|
22
|
+
# end
|
23
|
+
# CODE
|
24
|
+
# job = RocketJob::Jobs::OnDemandBatchJob.new(code: code, description: 'cleanse users')
|
25
|
+
#
|
26
|
+
# # Run against a sub-set using a limit
|
27
|
+
# arel = User.unscoped.all.order('updated_at DESC').limit(100)
|
28
|
+
# job.record_count = job.input.upload_arel(arel)
|
29
|
+
#
|
30
|
+
# # Run the subset directly within the console
|
31
|
+
# job.perform_now
|
32
|
+
# job.cleanup!
|
33
|
+
#
|
34
|
+
# By default output is not collected, add the option `collect_output: true` to collect output.
|
35
|
+
# Example:
|
36
|
+
# job = RocketJob::Jobs::OnDemandBatchJob(description: 'Fix data', code: code, throttle_running_slices: 5, priority: 30, collect_output: true)
|
37
|
+
#
|
38
|
+
# Example: Move the upload operation into a before_batch.
|
39
|
+
# upload_code = <<-CODE
|
40
|
+
# arel = User.unscoped.all.order('updated_at DESC')
|
41
|
+
# self.record_count = input.upload_arel(arel)
|
42
|
+
# CODE
|
43
|
+
#
|
44
|
+
# code = <<-CODE
|
45
|
+
# if user = User.find(row)
|
46
|
+
# user.cleanse_attributes!
|
47
|
+
# user.save(validate: false)
|
48
|
+
# end
|
49
|
+
# CODE
|
50
|
+
#
|
51
|
+
# RocketJob::Jobs::OnDemandBatchJob.create!(
|
52
|
+
# upload_code: upload_code,
|
53
|
+
# code: code,
|
54
|
+
# description: 'cleanse users'
|
55
|
+
# )
|
56
|
+
module RocketJob
|
57
|
+
module Jobs
|
58
|
+
class OnDemandBatchJob < RocketJob::Job
|
59
|
+
include RocketJob::Plugins::Cron
|
60
|
+
include RocketJob::Batch
|
61
|
+
include RocketJob::Batch::Statistics
|
62
|
+
|
63
|
+
self.priority = 90
|
64
|
+
self.description = 'Batch Job'
|
65
|
+
self.destroy_on_complete = false
|
66
|
+
|
67
|
+
# Code that is performed against every row / record.
|
68
|
+
field :code, type: String
|
69
|
+
|
70
|
+
# Optional code to execute before the batch is run.
|
71
|
+
# Usually to upload data into the job.
|
72
|
+
field :before_code, type: String
|
73
|
+
|
74
|
+
# Optional code to execute after the batch is run.
|
75
|
+
# Usually to upload data into the job.
|
76
|
+
field :after_code, type: String
|
77
|
+
|
78
|
+
# Data that is made available to the job during the perform.
|
79
|
+
# Be sure to store key names only as Strings, not Symbols.
|
80
|
+
field :data, type: Hash, default: {}
|
81
|
+
|
82
|
+
validates :code, presence: true
|
83
|
+
validate :validate_code
|
84
|
+
validate :validate_before_code
|
85
|
+
validate :validate_after_code
|
86
|
+
|
87
|
+
before_slice :load_perform_code
|
88
|
+
before_batch :run_before_code
|
89
|
+
after_batch :run_after_code
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def load_perform_code
|
94
|
+
instance_eval("def perform(row)\n#{code}\nend")
|
95
|
+
end
|
96
|
+
|
97
|
+
def run_before_code
|
98
|
+
instance_eval(before_code) if before_code
|
99
|
+
end
|
100
|
+
|
101
|
+
def run_after_code
|
102
|
+
instance_eval(after_code) if after_code
|
103
|
+
end
|
104
|
+
|
105
|
+
def validate_code
|
106
|
+
return if code.nil?
|
107
|
+
validate_field(:code) { load_perform_code }
|
108
|
+
end
|
109
|
+
|
110
|
+
def validate_before_code
|
111
|
+
return if before_code.nil?
|
112
|
+
validate_field(:before_code) { instance_eval("def __before_code\n#{before_code}\nend") }
|
113
|
+
end
|
114
|
+
|
115
|
+
def validate_after_code
|
116
|
+
return if after_code.nil?
|
117
|
+
validate_field(:after_code) { instance_eval("def __after_code\n#{after_code}\nend") }
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_field(field)
|
121
|
+
yield
|
122
|
+
rescue Exception => exc
|
123
|
+
errors.add(field, "Failed to load :#{field}, #{exc.inspect}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RocketJob
|
2
|
+
module Jobs
|
3
|
+
class PerformanceJob < RocketJob::Job
|
4
|
+
include RocketJob::Batch
|
5
|
+
|
6
|
+
# Define the job's default attributes
|
7
|
+
self.description = 'Performance Test'
|
8
|
+
self.priority = 5
|
9
|
+
self.slice_size = 100
|
10
|
+
self.destroy_on_complete = false
|
11
|
+
|
12
|
+
# No operation, just return the supplied line (record)
|
13
|
+
def perform(line)
|
14
|
+
line
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -60,11 +60,8 @@ module RocketJob
|
|
60
60
|
|
61
61
|
def upload_file(job)
|
62
62
|
if job.respond_to?(:upload)
|
63
|
-
if original_file_name
|
64
|
-
|
65
|
-
job.upload(upload_file_name, streams: streams)
|
66
|
-
# job.upload sets the archived filename, we want it to be the original file name.
|
67
|
-
job.upload_file_name = original_file_name
|
63
|
+
if original_file_name
|
64
|
+
job.upload(upload_file_name, file_name: original_file_name)
|
68
65
|
else
|
69
66
|
job.upload(upload_file_name)
|
70
67
|
end
|