mobilize-base 1.36 → 1.293

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.
Files changed (42) hide show
  1. data/README.md +666 -1
  2. data/lib/mobilize-base.rb +1 -12
  3. data/lib/mobilize-base/extensions/array.rb +3 -8
  4. data/lib/mobilize-base/extensions/google_drive/acl.rb +1 -1
  5. data/lib/mobilize-base/extensions/google_drive/client_login_fetcher.rb +1 -2
  6. data/lib/mobilize-base/extensions/google_drive/file.rb +37 -11
  7. data/lib/mobilize-base/extensions/string.rb +6 -11
  8. data/lib/mobilize-base/extensions/yaml.rb +7 -10
  9. data/lib/mobilize-base/handlers/gbook.rb +38 -25
  10. data/lib/mobilize-base/handlers/gdrive.rb +4 -20
  11. data/lib/mobilize-base/handlers/gfile.rb +10 -64
  12. data/lib/mobilize-base/handlers/gridfs.rb +24 -19
  13. data/lib/mobilize-base/handlers/gsheet.rb +29 -45
  14. data/lib/mobilize-base/handlers/resque.rb +10 -17
  15. data/lib/mobilize-base/jobtracker.rb +196 -22
  16. data/lib/mobilize-base/models/job.rb +77 -107
  17. data/lib/mobilize-base/models/runner.rb +122 -36
  18. data/lib/mobilize-base/models/stage.rb +37 -18
  19. data/lib/mobilize-base/tasks.rb +13 -50
  20. data/lib/mobilize-base/version.rb +1 -1
  21. data/lib/samples/gdrive.yml +0 -15
  22. data/lib/samples/gridfs.yml +3 -0
  23. data/lib/samples/gsheet.yml +4 -4
  24. data/lib/samples/jobtracker.yml +6 -0
  25. data/mobilize-base.gemspec +3 -3
  26. data/test/base_job_rows.yml +11 -0
  27. data/test/mobilize-base_test.rb +106 -0
  28. data/test/test_base_1.yml +3 -0
  29. data/test/test_helper.rb +0 -155
  30. metadata +24 -36
  31. data/lib/mobilize-base/extensions/time.rb +0 -20
  32. data/lib/mobilize-base/helpers/job_helper.rb +0 -54
  33. data/lib/mobilize-base/helpers/jobtracker_helper.rb +0 -143
  34. data/lib/mobilize-base/helpers/runner_helper.rb +0 -83
  35. data/lib/mobilize-base/helpers/stage_helper.rb +0 -38
  36. data/lib/samples/gfile.yml +0 -9
  37. data/test/fixtures/base1_stage1.in.yml +0 -10
  38. data/test/fixtures/integration_expected.yml +0 -25
  39. data/test/fixtures/integration_jobs.yml +0 -12
  40. data/test/fixtures/is_due.yml +0 -97
  41. data/test/integration/mobilize-base_test.rb +0 -57
  42. data/test/unit/mobilize-base_test.rb +0 -33
@@ -0,0 +1,3 @@
1
+ - {date: 2013-01-01, snake_date: 2013-02-01, camelDate: 2013-03-01}
2
+ - {date: 2013-01-01, snake_date: 2013-02-02, camelDate: 2013-03-02}
3
+ - {date: 2013-01-01, snake_date: 2013-02-03, camelDate: 2013-03-03}
data/test/test_helper.rb CHANGED
@@ -8,158 +8,3 @@ $dir = File.dirname(File.expand_path(__FILE__))
8
8
  ENV['MOBILIZE_ENV'] = 'test'
9
9
  require 'mobilize-base'
10
10
  $TESTING = true
11
- module TestHelper
12
- def TestHelper.confirm_expected_jobs(expected_fixture_name,time_limit=600)
13
- jobs = {}
14
- jobs['expected'] = TestHelper.load_fixture(expected_fixture_name)
15
- jobs['pending'] = jobs['expected'].select{|j| j['confirmed_ats'].length < j['count']}
16
- start_time = Time.now.utc
17
- total_time = 0
18
- while (jobs['pending'].length>0 or Mobilize::Resque.workers('working').length>0) and total_time < time_limit
19
- #working jobs are running on the queue at this instant
20
- jobs['working'] = Mobilize::Resque.workers('working').map{|w| w.job}.select{|j| j and j['payload'] and j['payload']['args']}
21
- #failed jobs are in the failure queue
22
- jobs['failed'] = Mobilize::Resque.failures.select{|j| j and j['payload'] and j['payload']['args']}
23
-
24
- #unexpected jobs are not supposed to be run in this test, includes leftover failures
25
- jobs['unexpected'] = {}
26
- error_msg = ""
27
- ['working','failed'].each do |state|
28
- jobs['unexpected'][state] = jobs[state].reject{|j|
29
- jobs['expected'].select{|ej|
30
- ej['state']==state and j['payload']['args'].first == ej['path']}.first}
31
- if jobs['unexpected'][state].length>0
32
- error_msg += state + ": " + jobs['unexpected'][state].map{|j| j['payload']['args'].first}.join(";") + "\n"
33
- end
34
- end
35
- #clear out unexpected paths or there will be failure
36
- if error_msg.length>0
37
- raise "Found unexpected results:\n" + error_msg
38
- end
39
-
40
- #now make sure pending jobs get done
41
- jobs['expected'].each do |j|
42
- start_confirmed_ats = j['confirmed_ats']
43
- resque_timestamps = jobs[j['state']].select{|sj| sj['payload']['args'].first == j['path']}.map{|sj| sj['run_at'] || sj['failed_at']}
44
- new_timestamps = (resque_timestamps - start_confirmed_ats).uniq
45
- if new_timestamps.length>0 and j['confirmed_ats'].length < j['count']
46
- j['confirmed_ats'] += new_timestamps
47
- puts "#{Time.now.utc.to_s}: #{new_timestamps.length.to_s} #{j['state']} added to #{j['path']}; total #{j['confirmed_ats'].length.to_s} of #{j['count']}"
48
- end
49
- end
50
-
51
- #figure out who's still pending
52
- jobs['pending'] = jobs['expected'].select{|j| j['confirmed_ats'].length < j['count']}
53
- sleep 1
54
- total_time = Time.now.utc - start_time
55
- puts "#{total_time.to_s} seconds elapsed" if total_time.to_s.ends_with?("0")
56
- end
57
- end
58
-
59
- #test methods
60
- def TestHelper.restart_test_redis
61
- TestHelper.stop_test_redis
62
- if !system("which redis-server")
63
- raise "** can't find `redis-server` in your path, you need redis to run Resque and Mobilize"
64
- end
65
- "redis-server #{Mobilize::Base.root}/test/redis-test.conf".bash
66
- end
67
-
68
- def TestHelper.stop_test_redis
69
- processes = `ps -A -o pid,command | grep [r]edis-test`.split($/)
70
- pids = processes.map { |process| process.split(" ")[0] }
71
- puts "Killing test redis server..."
72
- pids.each { |pid| Process.kill("TERM", pid.to_i) }
73
- puts "removing redis db dump file"
74
- sleep 5
75
- `rm -f #{Mobilize::Base.root}/test/dump.rdb #{Mobilize::Base.root}/test/dump-cluster.rdb`
76
- end
77
-
78
- def TestHelper.set_test_env
79
- ENV['MOBILIZE_ENV']='test'
80
- ::Resque.redis="localhost:9736"
81
- mongoid_config_path = "#{Mobilize::Base.root}/config/mobilize/mongoid.yml"
82
- Mongoid.load!(mongoid_config_path, Mobilize::Base.env)
83
- end
84
-
85
- def TestHelper.drop_test_db
86
- TestHelper.set_test_env
87
- Mongoid.session(:default).collections.each do |collection|
88
- unless collection.name =~ /^system\./
89
- collection.drop
90
- end
91
- end
92
- end
93
-
94
- def TestHelper.build_test_runner(user_name)
95
- TestHelper.set_test_env
96
- u = Mobilize::User.where(:name=>user_name).first
97
- Mobilize::Jobtracker.update_status("delete old books and datasets")
98
- # delete any old runner from previous test runs
99
- gdrive_slot = Mobilize::Gdrive.owner_email
100
- u.runner.gsheet(gdrive_slot).spreadsheet.delete
101
- Mobilize::Dataset.find_by_handler_and_path('gbook',u.runner.title).delete
102
- Mobilize::Jobtracker.update_status("enqueue jobtracker, wait 45s")
103
- Mobilize::Jobtracker.start
104
- sleep 45
105
- end
106
-
107
- def TestHelper.owner_user
108
- gdrive_slot = Mobilize::Gdrive.owner_email
109
- user_name = gdrive_slot.split("@").first
110
- return Mobilize::User.find_or_create_by_name(user_name)
111
- end
112
-
113
- def TestHelper.load_fixture(name)
114
- #assume yml, check
115
- yml_file_path = "#{Mobilize::Base.root}/test/fixtures/#{name}.yml"
116
- standard_file_path = "#{Mobilize::Base.root}/test/fixtures/#{name}"
117
- if File.exists?(yml_file_path)
118
- YAML.load_file(yml_file_path)
119
- elsif File.exists?(standard_file_path)
120
- File.read(standard_file_path)
121
- else
122
- raise "Could not find #{standard_file_path}"
123
- end
124
- end
125
-
126
- def TestHelper.write_fixture(fixture_name, target_url, options={})
127
- u = TestHelper.owner_user
128
- fixture_raw = TestHelper.load_fixture(fixture_name)
129
- if options['replace']
130
- fixture_data = if fixture_raw.class == Array
131
- fixture_raw.hash_array_to_tsv
132
- elsif fixture_raw.class == String
133
- fixture_raw
134
- end
135
- Mobilize::Dataset.write_by_url(target_url,fixture_data,u.name,u.email)
136
- elsif options['update']
137
- handler, sheet_path = target_url.split("://")
138
- raise "update only works for gsheet, not #{handler}" unless handler=='gsheet'
139
- sheet = Mobilize::Gsheet.find_or_create_by_path(sheet_path,u.email)
140
- sheet.add_or_update_rows(fixture_raw)
141
- else
142
- raise "unknown options #{options.to_s}"
143
- end
144
- return true
145
- end
146
-
147
- #checks output sheet for matching string or minimum length
148
- def TestHelper.check_output(target_url, options={})
149
- u = TestHelper.owner_user
150
- handler, sheet_path = target_url.split("://")
151
- handler = nil
152
- sheet = Mobilize::Gsheet.find_by_path(sheet_path,u.email)
153
- raise "no output found" if sheet.nil?
154
- output = sheet.to_tsv
155
- if options['match']
156
- return true if output == options['match']
157
- elsif options['min_length']
158
- return true if output.length >= options['min_length']
159
- else
160
- raise "unknown check options #{options.to_s}"
161
- end
162
- return true
163
- end
164
-
165
- end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobilize-base
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.36'
4
+ version: '1.293'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-21 00:00:00.000000000 Z
12
+ date: 2013-03-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - '='
36
36
  - !ruby/object:Gem::Version
37
- version: 1.8.4
37
+ version: 1.6.1
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - '='
44
44
  - !ruby/object:Gem::Version
45
- version: 1.8.4
45
+ version: 1.6.1
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: bson_ext
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - '='
52
52
  - !ruby/object:Gem::Version
53
- version: 1.8.4
53
+ version: 1.6.1
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,39 +58,39 @@ dependencies:
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 1.8.4
61
+ version: 1.6.1
62
62
  - !ruby/object:Gem::Dependency
63
- name: mongoid
63
+ name: mongo
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
- - - ~>
67
+ - - '='
68
68
  - !ruby/object:Gem::Version
69
- version: 3.0.0
69
+ version: 1.6.1
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
- - - ~>
75
+ - - '='
76
76
  - !ruby/object:Gem::Version
77
- version: 3.0.0
77
+ version: 1.6.1
78
78
  - !ruby/object:Gem::Dependency
79
- name: mongoid-grid_fs
79
+ name: mongoid
80
80
  requirement: !ruby/object:Gem::Requirement
81
81
  none: false
82
82
  requirements:
83
- - - ! '>='
83
+ - - ~>
84
84
  - !ruby/object:Gem::Version
85
- version: '0'
85
+ version: 3.0.0
86
86
  type: :runtime
87
87
  prerelease: false
88
88
  version_requirements: !ruby/object:Gem::Requirement
89
89
  none: false
90
90
  requirements:
91
- - - ! '>='
91
+ - - ~>
92
92
  - !ruby/object:Gem::Version
93
- version: '0'
93
+ version: 3.0.0
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: resque
96
96
  requirement: !ruby/object:Gem::Requirement
@@ -182,7 +182,6 @@ files:
182
182
  - lib/mobilize-base/extensions/resque-server/views/queues.erb
183
183
  - lib/mobilize-base/extensions/resque-server/views/working.erb
184
184
  - lib/mobilize-base/extensions/string.rb
185
- - lib/mobilize-base/extensions/time.rb
186
185
  - lib/mobilize-base/extensions/yaml.rb
187
186
  - lib/mobilize-base/handlers/email.rb
188
187
  - lib/mobilize-base/handlers/gbook.rb
@@ -191,10 +190,6 @@ files:
191
190
  - lib/mobilize-base/handlers/gridfs.rb
192
191
  - lib/mobilize-base/handlers/gsheet.rb
193
192
  - lib/mobilize-base/handlers/resque.rb
194
- - lib/mobilize-base/helpers/job_helper.rb
195
- - lib/mobilize-base/helpers/jobtracker_helper.rb
196
- - lib/mobilize-base/helpers/runner_helper.rb
197
- - lib/mobilize-base/helpers/stage_helper.rb
198
193
  - lib/mobilize-base/jobtracker.rb
199
194
  - lib/mobilize-base/models/dataset.rb
200
195
  - lib/mobilize-base/models/job.rb
@@ -204,7 +199,6 @@ files:
204
199
  - lib/mobilize-base/tasks.rb
205
200
  - lib/mobilize-base/version.rb
206
201
  - lib/samples/gdrive.yml
207
- - lib/samples/gfile.yml
208
202
  - lib/samples/gridfs.yml
209
203
  - lib/samples/gsheet.yml
210
204
  - lib/samples/jobtracker.yml
@@ -212,14 +206,11 @@ files:
212
206
  - lib/samples/resque.yml
213
207
  - lib/samples/resque_web.rb
214
208
  - mobilize-base.gemspec
215
- - test/fixtures/base1_stage1.in.yml
216
- - test/fixtures/integration_expected.yml
217
- - test/fixtures/integration_jobs.yml
218
- - test/fixtures/is_due.yml
219
- - test/integration/mobilize-base_test.rb
209
+ - test/base_job_rows.yml
210
+ - test/mobilize-base_test.rb
220
211
  - test/redis-test.conf
212
+ - test/test_base_1.yml
221
213
  - test/test_helper.rb
222
- - test/unit/mobilize-base_test.rb
223
214
  homepage: http://github.com/ngmoco/mobilize-base
224
215
  licenses: []
225
216
  post_install_message:
@@ -234,7 +225,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
234
225
  version: '0'
235
226
  segments:
236
227
  - 0
237
- hash: -579148248180029294
228
+ hash: 3027972477721057312
238
229
  required_rubygems_version: !ruby/object:Gem::Requirement
239
230
  none: false
240
231
  requirements:
@@ -243,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
243
234
  version: '0'
244
235
  segments:
245
236
  - 0
246
- hash: -579148248180029294
237
+ hash: 3027972477721057312
247
238
  requirements: []
248
239
  rubyforge_project: mobilize-base
249
240
  rubygems_version: 1.8.25
@@ -252,11 +243,8 @@ specification_version: 3
252
243
  summary: Moves datasets and schedules data transfers using MongoDB, Resque and Google
253
244
  Docs
254
245
  test_files:
255
- - test/fixtures/base1_stage1.in.yml
256
- - test/fixtures/integration_expected.yml
257
- - test/fixtures/integration_jobs.yml
258
- - test/fixtures/is_due.yml
259
- - test/integration/mobilize-base_test.rb
246
+ - test/base_job_rows.yml
247
+ - test/mobilize-base_test.rb
260
248
  - test/redis-test.conf
249
+ - test/test_base_1.yml
261
250
  - test/test_helper.rb
262
- - test/unit/mobilize-base_test.rb
@@ -1,20 +0,0 @@
1
- class Time
2
- def Time.at_marks_ago(number=1, unit='day', mark='0000')
3
- curr_time = Time.now.utc
4
- #strip out non-numerical characters from mark, add colon
5
- mark = mark.gsub(/[^0-9]/i,"").rjust(4,'0').ie{|m| [m[0..1],":",m[-2..-1]].join}
6
- #if user passes in 0 for the number, make it 1
7
- number = (number.to_i <= 0 ? 1 : number.to_i)
8
- if unit == 'day'
9
- curr_mark_time = Time.parse(curr_time.strftime("%Y-%m-%d #{mark} UTC"))
10
- elsif unit == 'hour'
11
- if curr_time.strftime("%H%M").to_i > mark.to_i
12
- curr_mark_time = Time.parse(curr_time.strftime("%Y-%m-%d %H:#{mark[-2..-1]} UTC"))
13
- end
14
- end
15
- #last mark time is
16
- mark_ago_increment = (curr_time > curr_mark_time ? (number-1).send(unit) : number.send(unit))
17
- last_mark_time = curr_mark_time - mark_ago_increment
18
- return last_mark_time
19
- end
20
- end
@@ -1,54 +0,0 @@
1
- #this module adds convenience methods to the Job model
2
- module Mobilize
3
- module JobHelper
4
- def name
5
- j = self
6
- j.path.split("/").last
7
- end
8
-
9
- def stages
10
- j = self
11
- #starts with the job path, followed by a slash
12
- Stage.where(:path=>/^#{j.path.escape_regex}\//).to_a.sort_by{|s| s.path}
13
- end
14
-
15
- def status
16
- #last stage status
17
- j = self
18
- j.active_stage.status if j.active_stage
19
- end
20
-
21
- def active_stage
22
- j = self
23
- #latest started at or first
24
- j.stages.select{|s| s.started_at}.sort_by{|s| s.started_at}.last || j.stages.first
25
- end
26
-
27
- def completed_at
28
- j = self
29
- j.stages.last.completed_at if j.stages.last
30
- end
31
-
32
- def failed_at
33
- j = self
34
- j.active_stage.failed_at if j.active_stage
35
- end
36
-
37
- def status_at
38
- j = self
39
- j.active_stage.status_at if j.active_stage
40
- end
41
-
42
- #convenience methods
43
- def runner
44
- j = self
45
- runner_path = j.path.split("/")[0..-2].join("/")
46
- return Runner.where(:path=>runner_path).first
47
- end
48
-
49
- def is_working?
50
- j = self
51
- j.stages.select{|s| s.is_working?}.compact.length>0
52
- end
53
- end
54
- end
@@ -1,143 +0,0 @@
1
- module Mobilize
2
- module Jobtracker
3
- def Jobtracker.config
4
- Base.config('jobtracker')
5
- end
6
-
7
- #modify this to increase the frequency of request cycles
8
- def Jobtracker.cycle_freq
9
- Jobtracker.config['cycle_freq']
10
- end
11
-
12
- #frequency of notifications
13
- def Jobtracker.notification_freq
14
- Jobtracker.config['notification_freq']
15
- end
16
-
17
- def Jobtracker.runner_read_freq
18
- Jobtracker.config['runner_read_freq']
19
- end
20
-
21
- #long running tolerance
22
- def Jobtracker.max_run_time
23
- Jobtracker.config['max_run_time']
24
- end
25
-
26
- def Jobtracker.admins
27
- Jobtracker.config['admins']
28
- end
29
-
30
- def Jobtracker.worker
31
- Resque.find_worker_by_path("jobtracker")
32
- end
33
-
34
- def Jobtracker.workers(state="all")
35
- Resque.workers(state)
36
- end
37
-
38
- def Jobtracker.status
39
- args = Jobtracker.get_args
40
- return args['status'] if args
41
- job = Resque.jobs.select{|j| j['args'].first=='jobtracker'}.first
42
- return 'queued' if job
43
- return 'stopped'
44
- end
45
-
46
- def Jobtracker.update_status(msg)
47
- #this is to keep jobtracker from resisting stop commands
48
- return false if Jobtracker.status=="stopping"
49
- #Jobtracker has no persistent database state
50
- Resque.set_worker_args_by_path("jobtracker",{'status'=>msg})
51
- return true
52
- end
53
-
54
- def Jobtracker.restart
55
- Jobtracker.stop!
56
- Jobtracker.start
57
- end
58
-
59
- def Jobtracker.set_args(args)
60
- Resque.set_worker_args(Jobtracker.worker,args)
61
- return true
62
- end
63
-
64
- def Jobtracker.get_args
65
- Resque.get_worker_args(Jobtracker.worker)
66
- end
67
-
68
- def Jobtracker.kill_workers
69
- Resque.kill_workers
70
- end
71
-
72
- def Jobtracker.kill_idle_workers
73
- Resque.kill_idle_workers
74
- end
75
-
76
- def Jobtracker.kill_idle_and_stale_workers
77
- Resque.kill_idle_and_stale_workers
78
- end
79
-
80
- def Jobtracker.prep_workers
81
- Resque.prep_workers
82
- end
83
-
84
- def Jobtracker.failures
85
- Resque.failures
86
- end
87
-
88
- def Jobtracker.start
89
- if Jobtracker.status!='stopped'
90
- Jobtracker.update_status("Jobtracker still #{Jobtracker.status}")
91
- else
92
- #make sure that workers are running and at the right number
93
- #Resque.prep_workers
94
- #queue up the jobtracker (starts the perform method)
95
- Jobtracker.enqueue!
96
- end
97
- return true
98
- end
99
-
100
- def Jobtracker.enqueue!
101
- ::Resque::Job.create(Resque.queue_name, Jobtracker, 'jobtracker',{})
102
- end
103
-
104
- def Jobtracker.restart!
105
- Jobtracker.stop!
106
- Jobtracker.start
107
- return true
108
- end
109
-
110
- def Jobtracker.restart_workers!
111
- Jobtracker.kill_workers
112
- sleep 10
113
- Jobtracker.prep_workers
114
- Jobtracker.update_status("put workers back on the queue")
115
- end
116
-
117
- def Jobtracker.stop!
118
- #send signal for Jobtracker to check for
119
- Jobtracker.update_status('stopping')
120
- sleep 5
121
- i=0
122
- while Jobtracker.status=='stopping'
123
- puts "#{Jobtracker.to_s} still on queue, waiting"
124
- sleep 5
125
- i+=1
126
- end
127
- return true
128
- end
129
-
130
- def Jobtracker.last_notification
131
- return Jobtracker.get_args["last_notification"] if Jobtracker.get_args
132
- end
133
-
134
- def Jobtracker.last_notification=(time)
135
- Jobtracker.set_args({"last_notification"=>time})
136
- end
137
-
138
- def Jobtracker.notif_due?
139
- last_duetime = Time.now.utc - Jobtracker.notification_freq
140
- return (Jobtracker.last_notification.to_s.length==0 || Jobtracker.last_notification.to_datetime < last_duetime)
141
- end
142
- end
143
- end