marty 1.0.12 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb347975c0c4442e6466f8cbcbd72d431c85f45f
4
- data.tar.gz: 4c7de97777049441bbf43ea5d324ed1a4c858315
3
+ metadata.gz: 6954bc349cc4800a6505d17737f5ddcb91ce16b2
4
+ data.tar.gz: 8bfff59fcd54425f3f076b0fdc61ec98a5d52b23
5
5
  SHA512:
6
- metadata.gz: dc0b688584580e74b519a04559d0c3dd6bcbc0d3fc2e00949403a32c54ef25f78380573238f8d80d048ce434d3c936a6c455db94c1a2945065ea4c39525b277d
7
- data.tar.gz: 7318c212db79212f25ad3fcfc3f27d28e666f1948d333b93f7ab5d5689e5eb4f16f54873a02d016eff9678bd607a7edce244d3234b94e2229c05b7313149197b
6
+ metadata.gz: f3d5897c69ee0feb698f467a30095273ba724a030fbeae9032f82129e3341b6905c67ededb6256264079702a68a286076ae216d9384b06eb84edbf3d1b4c7300
7
+ data.tar.gz: f36961f6208fbdef9d54a9f717cadb04cac2bb5a3d7c956905a650718d7996c155469d93f0ac3dce78d16991fbac1d6947625acb81ca2f0ccaf5458c48821459
data/Gemfile CHANGED
@@ -9,6 +9,7 @@ gem 'delayed_job_active_record'
9
9
  gem 'daemons', '~> 1.1.9'
10
10
  gem 'mime-types', '< 3.0', platforms: :ruby_19
11
11
  gem 'rails', '~> 4.2.1'
12
+ gem 'pg', '~> 0.18.4'
12
13
 
13
14
  group :development, :test do
14
15
  gem 'pry-rails'
@@ -57,7 +57,7 @@ class Marty::MainAuthApp < Marty::AuthApp
57
57
  :api_auth_view,
58
58
  :reload_scripts,
59
59
  :load_seed,
60
- ],
60
+ ] + background_jobs_menu
61
61
  }
62
62
  end
63
63
 
@@ -73,6 +73,21 @@ class Marty::MainAuthApp < Marty::AuthApp
73
73
  }
74
74
  end
75
75
 
76
+ def background_jobs_menu
77
+ [
78
+ {
79
+ text: 'Background Jobs',
80
+ icon: icon_hack(:clock),
81
+ disabled: !self.class.has_admin_perm?,
82
+ menu: [
83
+ :bg_status,
84
+ :bg_stop,
85
+ :bg_restart,
86
+ ]
87
+ },
88
+ ]
89
+ end
90
+
76
91
  def warped
77
92
  Marty::Util.warped?
78
93
  end
@@ -181,6 +196,62 @@ class Marty::MainAuthApp < Marty::AuthApp
181
196
  a.disabled = !self.class.has_admin_perm?
182
197
  end
183
198
 
199
+ action :bg_status do |a|
200
+ a.text = 'Show Delayed Jobs Status'
201
+ a.tooltip = 'Run delayed_job status script'
202
+ a.icon = :monitor
203
+ a.disabled = !self.class.has_admin_perm?
204
+ end
205
+
206
+ action :bg_stop do |a|
207
+ a.text = 'Stop Delayed Jobs'
208
+ a.tooltip = 'Run delayed_job stop script'
209
+ a.icon = :stop
210
+ a.disabled = !self.class.has_admin_perm?
211
+ end
212
+
213
+ action :bg_restart do |a|
214
+ a.text = 'Restart Delayed Jobs'
215
+ a.tooltip = 'Run delayed_job restart script using DELAYED_JOB_PARAMS'
216
+ a.icon = :arrow_rotate_clockwise
217
+ a.disabled = !self.class.has_admin_perm?
218
+ end
219
+
220
+ ######################################################################
221
+
222
+ def bg_command(param)
223
+ e, root, p = ENV['RAILS_ENV'], Rails.root, Marty::Config["RUBY_PATH"]
224
+ # preserve backward compatability with Gemini
225
+ # for new Rails apps, use 'bin/delayed_job'
226
+ dj_path = Marty::Config["DELAYED_JOB_PATH"] || 'script/delayed_job'
227
+ cmd = "export RAILS_ENV=#{e};"
228
+ # FIXME: Environment looks to be setup incorrectly - this is a hack
229
+ cmd += "export PATH=#{p}:$PATH;" if p
230
+ # 2>&1 redirects STDERR to STDOUT since backticks only captures STDOUT
231
+ cmd += "#{root}/#{dj_path} #{param} 2>&1"
232
+ cmd
233
+ end
234
+
235
+ endpoint :bg_status do |params|
236
+ cmd = bg_command('status')
237
+ res = `#{cmd}`
238
+ client.show_detail res.html_safe.gsub("\n","<br/>"), 'Delayed Job Status'
239
+ end
240
+
241
+ endpoint :bg_stop do |params|
242
+ cmd = bg_command("stop")
243
+ res = `#{cmd}`
244
+ res = "delayed_job: no instances running. Nothing to stop." if res.length==0
245
+ client.show_detail res.html_safe.gsub("\n","<br/>"), 'Delayed Job Stop'
246
+ end
247
+
248
+ endpoint :bg_restart do |params|
249
+ params = Marty::Config["DELAYED_JOB_PARAMS"] || ""
250
+ cmd = bg_command("restart #{params}")
251
+ res = `#{cmd}`
252
+ client.show_detail res.html_safe.gsub("\n","<br/>"), 'Delayed Job Restart'
253
+ end
254
+
184
255
  ######################################################################
185
256
  # Postings
186
257
 
@@ -192,6 +263,38 @@ class Marty::MainAuthApp < Marty::AuthApp
192
263
  end
193
264
 
194
265
  client_class do |c|
266
+ c.show_detail = l(<<-JS)
267
+ function(details, title) {
268
+ this.hideLoadmask();
269
+ Ext.create('Ext.Window', {
270
+ height: 400,
271
+ minWidth: 400,
272
+ maxWidth: 1200,
273
+ autoWidth: true,
274
+ modal: true,
275
+ autoScroll: true,
276
+ html: details,
277
+ title: title || "Details"
278
+ }).show();
279
+ }
280
+ JS
281
+
282
+ c.show_loadmask = l(<<-JS)
283
+ function(msg) {
284
+ this.maskCmp = new Ext.LoadMask( {
285
+ msg: msg || 'Loading...',
286
+ target: this,
287
+ });
288
+ this.maskCmp.show();
289
+ }
290
+ JS
291
+
292
+ c.hide_loadmask = l(<<-JS)
293
+ function() {
294
+ if (this.maskCmp) { this.maskCmp.hide(); };
295
+ }
296
+ JS
297
+
195
298
  c.netzke_on_new_posting = l(<<-JS)
196
299
  function(params) {
197
300
  this.netzkeLoadComponent("new_posting_window",
@@ -241,6 +344,47 @@ class Marty::MainAuthApp < Marty::AuthApp
241
344
  });
242
345
  }
243
346
  JS
347
+
348
+ c.netzke_on_bg_stop = l(<<-JS)
349
+ function(params) {
350
+ var me = this;
351
+ me.showLoadmask('Stopping delayed job...');
352
+ Ext.Msg.show({
353
+ title: 'Stop Delayed Jobs',
354
+ msg: 'Enter STOP and press OK to force a stop of delayed_job',
355
+ width: 375,
356
+ buttons: Ext.Msg.OKCANCEL,
357
+ prompt: true,
358
+ fn: function (btn, value) {
359
+ btn == "ok" && value == "STOP" && me.server.bgStop({});
360
+ }
361
+ });
362
+ }
363
+ JS
364
+
365
+ c.netzke_on_bg_restart = l(<<-JS)
366
+ function(params) {
367
+ var me = this;
368
+ me.showLoadmask('Restarting delayed job...');
369
+ Ext.Msg.show({
370
+ title: 'Restart Delayed Jobs',
371
+ msg: 'Enter RESTART and press OK to force a restart of delayed_job',
372
+ width: 375,
373
+ buttons: Ext.Msg.OKCANCEL,
374
+ prompt: true,
375
+ fn: function (btn, value) {
376
+ btn == "ok" && value == "RESTART" && me.server.bgRestart({});
377
+ }
378
+ });
379
+ }
380
+ JS
381
+
382
+ c.netzke_on_bg_status = l(<<-JS)
383
+ function() {
384
+ this.showLoadmask('Checking delayed job status...');
385
+ this.server.bgStatus({});
386
+ }
387
+ JS
244
388
  end
245
389
 
246
390
  action :select_posting do |a|
@@ -0,0 +1,5 @@
1
+ class Marty::EnumEventOperation < Marty::Base
2
+ extend Marty::PgEnum
3
+
4
+ VALUES = Set['UNKNOWN']
5
+ end
@@ -0,0 +1,206 @@
1
+ class Marty::Event < Marty::Base
2
+
3
+ class EventValidator < ActiveModel::Validator
4
+ def validate(event)
5
+ event.errors[:base] = "Must have promise_id or start_dt" unless
6
+ event.promise_id || event.start_dt
7
+ end
8
+ end
9
+
10
+ validates_presence_of :klass, :subject_id, :enum_event_operation
11
+
12
+ belongs_to :promise
13
+
14
+ validates_with EventValidator
15
+
16
+ BASE_QUERY = "SELECT ev.id,
17
+ ev.klass,
18
+ ev.subject_id,
19
+ ev.enum_event_operation,
20
+ ev.comment,
21
+ coalesce(pr.start_dt, ev.start_dt) start_dt,
22
+ coalesce(pr.end_dt, ev.end_dt) end_dt,
23
+ expire_secs
24
+ FROM marty_events ev
25
+ LEFT JOIN marty_promises pr ON ev.promise_id = pr.id "
26
+
27
+ def self.running_query(time_now_s)
28
+ "SELECT * FROM
29
+ (#{BASE_QUERY}
30
+ WHERE coalesce(pr.start_dt, ev.start_dt, '1900-1-1') >=
31
+ '#{time_now_s}'::timestamp - interval '24 hours') sub
32
+ WHERE (end_dt IS NULL or end_dt > '#{time_now_s}'::timestamp)
33
+ AND (expire_secs IS NULL
34
+ OR expire_secs > EXTRACT (EPOCH FROM '#{time_now_s}'::timestamp - start_dt))
35
+ ORDER BY start_dt"
36
+ end
37
+
38
+
39
+ def self.op_is_running?(klass, subject_id, operation)
40
+ all_running.detect do |pm|
41
+ pm["klass"] == klass && pm["subject_id"].to_i == subject_id.to_i &&
42
+ pm["enum_event_operation"] == operation
43
+ end
44
+ end
45
+
46
+ def self.create_event(klass,
47
+ subject_id,
48
+ operation,
49
+ start_dt,
50
+ expire_secs,
51
+ comment=nil)
52
+
53
+ # use lookup_event instead of all_running which is throttled
54
+ evs = self.lookup_event(klass, subject_id, operation)
55
+ running = evs.detect do
56
+ |ev|
57
+ next if ev["end_dt"]
58
+ next true unless ev["expire_secs"]
59
+ (Time.zone.now - ev["start_dt"]).truncate < ev["expire_secs"]
60
+ end
61
+
62
+ raise "#{operation} is already running for #{klass}/#{subject_id}" if
63
+ running
64
+
65
+ self.create!(klass: klass,
66
+ subject_id: subject_id,
67
+ enum_event_operation: operation,
68
+ start_dt: start_dt,
69
+ expire_secs: expire_secs,
70
+ comment: comment,
71
+ )
72
+ end
73
+
74
+ def self.lookup_event(klass, subject_id, operation)
75
+ get_data(BASE_QUERY +
76
+ " WHERE klass = '#{klass}'
77
+ AND subject_id = #{subject_id}
78
+ and enum_event_operation = '#{operation}'")
79
+
80
+ #For now we return a bare hash
81
+ #Marty::Event.find_by_id(hash["id"])
82
+ end
83
+
84
+ def self.finish_event(klass, subject_id, operation, comment=nil)
85
+ time_now_s = Time.zone.now.strftime('%Y-%m-%d %H:%M:%S.%6N')
86
+
87
+ event = get_data(running_query(time_now_s)).detect do |ev|
88
+ ev["klass"] == klass && ev["subject_id"] == subject_id.to_i &&
89
+ ev["enum_event_operation"] == operation
90
+ end
91
+ raise "event #{klass}/#{subject_id}/#{operation} not found" unless
92
+ event
93
+
94
+ ev = Marty::Event.find_by_id(event["id"])
95
+ raise "can't explicitly finish a promise event" if ev.promise_id
96
+ ev.end_dt = Time.zone.now
97
+ ev.comment = comment if comment
98
+ ev.save!
99
+ end
100
+
101
+ def self.last_event(klass, subject_id, operation=nil)
102
+ hash = all_running.select do |pm|
103
+ pm["klass"] == klass && pm["subject_id"] == subject_id.to_i &&
104
+ (operation.nil? || pm["enum_event_operation"] == operation)
105
+ end.sort { |a, b| b["start_dt"] <=> a["start_dt"] }.first
106
+
107
+ return hash if hash
108
+
109
+ op_sql = "AND enum_event_operation = '#{operation}'" if operation
110
+
111
+ get_data("SELECT * FROM (#{BASE_QUERY}) sub
112
+ WHERE klass = '#{klass}'
113
+ AND subject_id = #{subject_id} #{op_sql}
114
+ ORDER BY start_dt desc").first
115
+ end
116
+
117
+ def self.currently_running(klass, subject_id)
118
+ all_running.select do |pm|
119
+ pm["klass"] == klass && pm["subject_id"] == subject_id.to_i
120
+ end.map { |e| e["enum_event_operation"] }
121
+ end
122
+
123
+ def self.update_comment(hash, comment)
124
+ hid = hash.is_a?(Hash) ? hash['id'] : hash
125
+ e = Marty::Event.find_by_id(hid)
126
+ e.comment = comment
127
+ e.save!
128
+ end
129
+
130
+ def self.pretty_op(hash)
131
+ d = hash['enum_event_operation'].downcase.capitalize
132
+
133
+ #&& !(hash['comment'] =~ /^ERROR/)
134
+ hash['end_dt'] ? d.sub(/ing/, 'ed') : d
135
+ end
136
+
137
+ def self.compact_end_dt(hash)
138
+ hash['end_dt'] ? hash['end_dt'].strftime("%H:%M") : '---'
139
+ end
140
+
141
+ def self.get_data(sql)
142
+ ActiveRecord::Base.connection.execute(sql).to_a.map do |h|
143
+ h["id"] = h["id"].to_i
144
+ h["subject_id"] = h["subject_id"].to_i
145
+ h["start_dt"] = Time.zone.parse(h["start_dt"]) if h["start_dt"]
146
+ h["end_dt"] = Time.zone.parse(h["end_dt"]) if h["end_dt"]
147
+ h["expire_secs"] = h["expire_secs"].to_i if h["expire_secs"]
148
+ h["comment"] = h["comment"]
149
+ h
150
+ end
151
+ end
152
+ private_class_method :get_data
153
+
154
+ def self.clear_cache
155
+ @poll_secs = @all_running = @all_finished = nil
156
+ end
157
+
158
+ def self.all_running
159
+ @all_running ||= { timestamp: 0, data: [] }
160
+ @poll_secs ||= Marty::Config['MARTY_EVENT_POLL_SECS'] || 0
161
+ time_now = Time.zone.now
162
+ time_now_i = time_now.to_i
163
+ time_now_s = time_now.strftime('%Y-%m-%d %H:%M:%S.%6N')
164
+ if time_now_i - @all_running[:timestamp] > @poll_secs
165
+ @all_running[:data] = get_data(running_query(time_now_s))
166
+ @all_running[:timestamp] = time_now_i
167
+ end
168
+ @all_running[:data]
169
+ end
170
+ private_class_method :all_running
171
+
172
+ def self.all_finished
173
+ @all_finished ||= {
174
+ data: {},
175
+ timestamp: Time.zone.parse('1970-1-1 00:00:00').to_i,
176
+ }
177
+ @poll_secs ||= Marty::Config['MARTY_EVENT_POLL_SECS'] || 0
178
+ time_now_i = Time.zone.now.to_i
179
+ cutoff = Time.zone.at(@all_finished[:timestamp]).
180
+ strftime('%Y-%m-%d %H:%M:%S.%6N')
181
+
182
+ if time_now_i - @all_finished[:timestamp] > @poll_secs
183
+ raw = get_data(
184
+ "SELECT * FROM
185
+ (SELECT ROW_NUMBER() OVER (PARTITION BY klass,
186
+ subject_id,
187
+ enum_event_operation
188
+ ORDER BY end_dt DESC) rownum, *
189
+ FROM (#{BASE_QUERY}) sub2
190
+ WHERE end_dt IS NOT NULL and end_dt > '#{cutoff}') sub1
191
+ WHERE rownum = 1"
192
+ )
193
+ @all_finished[:timestamp] = time_now_i
194
+ raw.each_with_object(@all_finished[:data]) do |ev, hash|
195
+ subhash = hash[[ev["klass"], ev["subject_id"]]] ||= {}
196
+ subhash[ev["enum_event_operation"]] =
197
+ ev["end_dt"].strftime("%Y-%m-%d %H:%M:%S")
198
+ end
199
+ end
200
+ @all_finished[:data]
201
+ end
202
+
203
+ def self.get_finished(klass, id)
204
+ all_finished[[klass, id]]
205
+ end
206
+ end
@@ -0,0 +1,9 @@
1
+ class CreateMartyEventOperationEnum < ActiveRecord::Migration
2
+ def change
3
+ values = Marty::EnumEventOperation::VALUES
4
+ str_values = values.map {|v| ActiveRecord::Base.connection.quote v}.join ','
5
+ execute <<-SQL
6
+ CREATE TYPE enum_event_operations AS ENUM (#{str_values})
7
+ SQL
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ class CreateMartyEvents < ActiveRecord::Migration
2
+ def change(*args)
3
+ # Giant hack to monkey patch connection so that we can create an
4
+ # UNLOGGED table in PostgreSQL.
5
+ class << @connection
6
+ alias_method :old_execute, :execute
7
+ define_method(:execute) { |sql, name=nil|
8
+ old_execute(sql.sub('CREATE', 'CREATE UNLOGGED'), name)
9
+ }
10
+ end
11
+ create_table :marty_events do |t|
12
+ t.integer :promise_id, null: true
13
+ t.string :klass, null: false, limit: 255
14
+ t.integer :subject_id, null: false
15
+ t.pg_enum :enum_event_operation, null: false
16
+ t.datetime :start_dt, null: true
17
+ t.datetime :end_dt, null: true
18
+ t.integer :expire_secs, null: true
19
+ t.string :comment, null: true
20
+ end
21
+ class << @connection
22
+ alias_method :execute, :old_execute
23
+ end
24
+ add_index :marty_events, [:klass, :subject_id,
25
+ :enum_event_operation],
26
+ name: 'idx_klass_id_op'
27
+ add_index :marty_events, [:klass, :subject_id],
28
+ name: 'idx_klass_id'
29
+ end
30
+ end
data/db/seeds.rb CHANGED
@@ -46,3 +46,7 @@ unless Marty::Tag.find_by_name('DEV')
46
46
  tag.created_dt = 'infinity'
47
47
  tag.save!
48
48
  end
49
+
50
+ # one time set up for delayed_job/promises, override as needed
51
+ Marty::Config["DELAYED_JOB_PARAMS"] = "-n 4"
52
+ Marty::Config["DELAYED_JOB_PATH"] = "bin/delayed_job"
@@ -3,6 +3,59 @@ module Marty::Migrations
3
3
  "marty_"
4
4
  end
5
5
 
6
+ def new_enum(klass, prefix_override = nil)
7
+ raise "bad class arg #{klass}" unless
8
+ klass.is_a?(Class) && klass < ActiveRecord::Base
9
+
10
+ raise "model class needs VALUES (as Set)" unless
11
+ klass.const_defined?(:VALUES)
12
+
13
+ values = klass::VALUES
14
+ str_values =
15
+ values.map {|v| ActiveRecord::Base.connection.quote v}.join ','
16
+
17
+ #hacky way to get name
18
+ prefix = prefix_override || tb_prefix
19
+ enum_name = klass.table_name.sub(/^#{prefix}_*/, '')
20
+
21
+ execute <<-SQL
22
+ CREATE TYPE #{enum_name} AS ENUM (#{str_values});
23
+ SQL
24
+ end
25
+
26
+ def update_enum(klass, prefix_override = nil)
27
+ raise "bad class arg #{klass}" unless
28
+ klass.is_a?(Class) && klass < ActiveRecord::Base
29
+
30
+ raise "model class needs VALUES (as Set)" unless
31
+ klass.const_defined?(:VALUES)
32
+
33
+ #hacky way to get name
34
+ prefix = prefix_override || tb_prefix
35
+ enum_name = klass.table_name.sub(/^#{prefix}/, '')
36
+
37
+ #check values against underlying values
38
+ res = execute <<-SQL
39
+ SELECT ENUM_RANGE(null::#{enum_name});
40
+ SQL
41
+
42
+ db_values = res.first['enum_range'].gsub(/[{}]/, '').split(',')
43
+ ex_values = klass::VALUES - db_values
44
+
45
+ puts "no new #{klass}::VALUES to add" if ex_values.empty?
46
+
47
+ #hack to prevent transaction
48
+ execute("COMMIT;")
49
+ ex_values.each do |v|
50
+ prepped_v = ActiveRecord::Base.connection.quote(v)
51
+
52
+ execute <<-SQL
53
+ ALTER TYPE #{enum_name} ADD VALUE #{prepped_v};
54
+ SQL
55
+ end
56
+ execute("BEGIN;")
57
+ end
58
+
6
59
  def add_fk(from_table, to_table, options = {})
7
60
  options[:column] ||= "#{to_table.to_s.singularize}_id"
8
61
 
@@ -20,7 +20,6 @@ class Delorean::BaseModule::NodeCall
20
20
  raise "bad arg to %" unless args.is_a?(Array)
21
21
  attr = nil
22
22
  end
23
-
24
23
  script, tag = engine.module_name, engine.sset.tag
25
24
  nn = node.is_a?(Class) ? node.name : node.to_s
26
25
 
@@ -40,7 +39,6 @@ class Delorean::BaseModule::NodeCall
40
39
  parent_id: params[:_parent_id],
41
40
  )
42
41
  params[:_promise_id] = promise.id
43
-
44
42
  begin
45
43
  job = Delayed::Job.enqueue Marty::PromiseJob.
46
44
  new(promise, title, script, tag, nn, params, args, hook)
@@ -58,15 +56,22 @@ class Delorean::BaseModule::NodeCall
58
56
  # been reserved yet.
59
57
  promise.job_id = job.id
60
58
  promise.save!
61
-
59
+ event = Marty::Event.
60
+ create!(promise_id: promise.id,
61
+ klass: params[:__metadata__][:klass],
62
+ subject_id: params[:__metadata__][:id],
63
+ enum_event_operation:
64
+ params[:__metadata__][:operation]) if
65
+ params[:__metadata__]
62
66
  Marty::PromiseProxy.new(promise.id, timeout, attr)
63
67
  end
64
68
  end
65
69
 
70
+
66
71
  class Delorean::Engine
67
- def background_eval(node, params, attrs)
72
+ def background_eval(node, params, attrs, meta = {})
68
73
  raise "background_eval bad params" unless params.is_a?(Hash)
69
-
74
+ params[:__metadata__] = meta unless meta.empty?
70
75
  nc = Delorean::BaseModule::NodeCall.new({}, self, node, params)
71
76
  # start the background promise
72
77
  nc | attrs
data/lib/marty/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Marty
2
- VERSION = "1.0.12"
2
+ VERSION = "1.0.13"
3
3
  end
@@ -0,0 +1,5 @@
1
+ class Gemini::EnumEventOperation < Gemini::Base
2
+ extend Marty::EnumEventOperation
3
+
4
+ VALUES = set['PRICING', 'CRA', 'AVM']
5
+ end
@@ -1,16 +1,6 @@
1
1
  class CreateEnums < ActiveRecord::Migration
2
+ include Marty::Migrations
2
3
  def change
3
- values = Gemini::EnumState::VALUES
4
- str_values =
5
- values.map {|v| ActiveRecord::Base.connection.quote v}.join ','
6
-
7
- ActiveRecord::Base.schema_migrations_table_name
8
-
9
- # FIXME: very crude
10
- name = 'enum_states'
11
-
12
- execute <<-SQL
13
- CREATE TYPE #{name} AS ENUM (#{str_values});
14
- SQL
4
+ new_enum(Gemini::EnumState, 'gemini')
15
5
  end
16
6
  end
@@ -0,0 +1,9 @@
1
+ class AddBulkPricingEventOps < ActiveRecord::Migration
2
+ def change
3
+ execute("COMMIT;")
4
+ execute "ALTER TYPE enum_event_operations ADD VALUE 'PRICING';"
5
+ execute "ALTER TYPE enum_event_operations ADD VALUE 'CRA';"
6
+ execute "ALTER TYPE enum_event_operations ADD VALUE 'AVM';"
7
+ execute("BEGIN;")
8
+ end
9
+ end
data/spec/job_helper.rb CHANGED
@@ -88,6 +88,13 @@ Y:
88
88
  d = [Y(q=i) | ['a'] for i in [1, 2]]
89
89
  EOS
90
90
 
91
+ NAME_I = "PromiseI"
92
+ SCRIPT_I = <<EOS
93
+ SLEEPER:
94
+ secs =? nil
95
+ a = Gemini::Helper.sleep(secs) && secs
96
+ EOS
97
+
91
98
  def promise_bodies
92
99
  {
93
100
  NAME_A => SCRIPT_A,
@@ -98,5 +105,6 @@ def promise_bodies
98
105
  NAME_F => SCRIPT_F,
99
106
  NAME_G => SCRIPT_G,
100
107
  NAME_H => SCRIPT_H,
108
+ NAME_I => SCRIPT_I,
101
109
  }
102
110
  end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+ require 'job_helper'
3
+
4
+ describe Marty::Event do
5
+ before(:all) do
6
+ @clean_file = "/tmp/clean_#{Process.pid}.psql"
7
+ save_clean_db(@clean_file)
8
+
9
+ # transactional fixtures interfere with queueing jobs
10
+ self.use_transactional_fixtures = false
11
+
12
+ # Needed here because shutting transactional fixtures off
13
+ # means we lose the globally set user
14
+ Mcfly.whodunnit = UserHelpers.system_user
15
+
16
+ Marty::Script.load_script_bodies(promise_bodies, Date.today)
17
+ start_delayed_job
18
+ Marty::Config["MARTY_EVENT_POLL_SECS"] = 1
19
+
20
+ @time = Time.zone.now
21
+ @date_string = @time.strftime('%Y-%m-%d')
22
+ @old_start = '1970-01-01 08:00:00'
23
+ @old_end = '1970-01-01 09:00:00'
24
+ # add events
25
+ [['testcl1', 123, @time, nil, nil, 'AVM', 'a comment'],
26
+ ['testcl1', 123, @time + 2.second, nil,nil, 'CRA', 'b comment'],
27
+ ['testcl1', 123, @time + 4.seconds, nil,10000, 'PRICING', 'c comment'],
28
+ ['testcl2', 123, @time, nil, 2, 'AVM', 'e comment'],
29
+ ['testcl2', 123, @time + 1.second, nil, 4, 'CRA', 'f comment'],
30
+ ['testcl2', 123, Time.zone.parse(@old_start),
31
+ Time.zone.parse(@old_end), nil, 'PRICING', 'old event'],
32
+ ].each do
33
+ |klass, subjid, startdt, enddt, expire, op, comment|
34
+ Marty::Event.create!(klass: klass,
35
+ subject_id: subjid,
36
+ start_dt: startdt,
37
+ end_dt: enddt,
38
+ expire_secs: expire,
39
+ comment: comment,
40
+ enum_event_operation: op)
41
+ end
42
+
43
+
44
+ engine = Marty::ScriptSet.new.get_engine(NAME_I)
45
+ res = engine.background_eval("SLEEPER", {"secs" => 5}, ["a"],
46
+ {klass: "testcl3",
47
+ id: 987,
48
+ operation: 'PRICING'})
49
+ res.force
50
+ sleep 5
51
+ end
52
+
53
+ after(:all) do
54
+ self.use_transactional_fixtures = true
55
+ restore_clean_db(@clean_file)
56
+ stop_delayed_job
57
+ end
58
+
59
+
60
+ it "Event tests" do
61
+ expect(Marty::Event.currently_running('testcl1', 123)).to eq(
62
+ ['AVM', 'CRA', 'PRICING'])
63
+ expect(Marty::Event.currently_running('testcl2', 123)).to eq([])
64
+ expect(Marty::Event.currently_running('testcl3', 987)).to eq([])
65
+
66
+ expect(Marty::Event.last_event('testcl1', 123)).to include(
67
+ {"klass"=>"testcl1",
68
+ "subject_id"=>123,
69
+ "enum_event_operation"=>"PRICING",
70
+ "comment"=>"c comment", "expire_secs"=>10000})
71
+ expect(Marty::Event.last_event('testcl2', 123)).to include(
72
+ {"klass"=>"testcl2",
73
+ "subject_id"=>123,
74
+ "enum_event_operation"=>"CRA",
75
+ "comment"=>"f comment",
76
+ "expire_secs"=>4})
77
+ expect(Marty::Event.last_event('testcl3', 987)).to include(
78
+ {"klass"=>"testcl3",
79
+ "subject_id"=>987,
80
+ "enum_event_operation"=>"PRICING",
81
+ "comment"=>nil,
82
+ "expire_secs"=>nil})
83
+
84
+ Timecop.freeze(@time+1.second)
85
+ Marty::Event.clear_cache
86
+ expect(Marty::Event.currently_running('testcl1', 123)).to eq(
87
+ ['AVM', 'CRA', 'PRICING'])
88
+ expect(Marty::Event.currently_running('testcl2', 123)).to eq(
89
+ ['AVM', 'CRA'])
90
+
91
+ Timecop.freeze(@time+3.seconds)
92
+ Marty::Event.clear_cache
93
+ expect(Marty::Event.currently_running('testcl1', 123)).to eq(
94
+ ['AVM', 'CRA', 'PRICING'])
95
+ expect(Marty::Event.currently_running('testcl2', 123)).to eq(
96
+ ['CRA'])
97
+
98
+ Timecop.freeze(@time+6.seconds)
99
+ expect(Marty::Event.currently_running('testcl1', 123)).to eq(
100
+ ['AVM', 'CRA', 'PRICING'])
101
+ expect(Marty::Event.currently_running('testcl2', 123)).to eq(
102
+ [])
103
+
104
+ Timecop.return
105
+
106
+ af = Marty::Event.all_finished
107
+ expect(af.count).to eq(2)
108
+ expect(af).to include(['testcl3', 987])
109
+ expect(af).to include(['testcl2', 123])
110
+ expect(af[['testcl3', 987]]).to include('PRICING')
111
+ expect(af[['testcl3', 987]]['PRICING']).to start_with(@date_string)
112
+ expect(af[['testcl2', 123]]).to include('PRICING')
113
+ expect(af[['testcl2', 123]]['PRICING']).to eq(@old_end)
114
+
115
+ expect(Marty::Event.currently_running('testcl1', 123)).to eq(
116
+ ['AVM', 'CRA', 'PRICING'])
117
+ expect(Marty::Event.op_is_running?('testcl1', 123, 'AVM')).to be_truthy
118
+ Marty::Event.finish_event('testcl1', 123, 'AVM', 'wassup')
119
+ Marty::Event.clear_cache
120
+ expect(Marty::Event.currently_running('testcl1', 123)).to eq(
121
+ ['CRA', 'PRICING'])
122
+ expect(Marty::Event.op_is_running?('testcl1', 123, 'AVM')).to be_falsey
123
+ expect(Marty::Event.op_is_running?('testcl1', 123, 'CRA')).to be_truthy
124
+
125
+ ev = Marty::Event.lookup_event('testcl1', 123, 'AVM')
126
+ expect(ev.length).to eq(1)
127
+ expect(ev.first).to include({"klass"=>"testcl1",
128
+ "subject_id"=>123,
129
+ "enum_event_operation"=>"AVM",
130
+ "comment"=>"wassup",
131
+ "expire_secs"=>nil})
132
+ Marty::Event.update_comment(ev.first, "updated")
133
+ ev = Marty::Event.lookup_event('testcl1', 123, 'AVM')
134
+ expect(ev.first).to include({"comment"=>"updated"})
135
+ expect(Marty::Event.compact_end_dt(ev.first)).to match(/\d\d:\d\d/)
136
+ expect(Marty::Event.pretty_op(ev.first)).to eq('Avm')
137
+ ev = Marty::Event.lookup_event('testcl1', 123, 'PRICING').first
138
+ expect(Marty::Event.pretty_op(ev)).to eq('Pricing')
139
+
140
+ af = Marty::Event.all_finished
141
+ expect(af.count).to eq(3)
142
+ expect(af[['testcl3', 987]]).to include('PRICING')
143
+ expect(af[['testcl1', 123]]).to include('AVM')
144
+ expect(af[['testcl1', 123]]['AVM']).to start_with(@date_string)
145
+
146
+ expect {Marty::Event.create_event('testcl', 1234, 'AVM', Time.zone.now, 600,
147
+ "the comment") }.not_to raise_error
148
+
149
+ expect {Marty::Event.create_event('testcl', 1234, 'AVM', Time.zone.now, 600,
150
+ "the comment") }.
151
+ to raise_error(%r!AVM is already running for testcl/1234!)
152
+ expect {Marty::Event.create_event('testcl', 2345, 'AVM', Time.zone.now, 600,
153
+ "the comment") }.not_to raise_error
154
+ expect {Marty::Event.finish_event('testcl', 1234, 'AVM', "new comment") }.
155
+ not_to raise_error
156
+ expect {Marty::Event.finish_event('testcl', 1234, 'AVM', "new comment") }.
157
+ to raise_error(%r!event testcl/1234/AVM not found!)
158
+ expect {Marty::Event.finish_event('testcl', 2345, 'AVM', 'foobar') }.
159
+ not_to raise_error
160
+ expect {Marty::Event.finish_event('testcl', 2345, 'AVM', 'foobar') }.
161
+ to raise_error(%r!event testcl/2345/AVM not found!)
162
+ expect {Marty::Event.create_event('testcl', 1234, 'AMV', Time.zone.now, 600,
163
+ "the comment") }.
164
+ to raise_error(%r!PG::.*invalid input value for enum.*"AMV"!)
165
+ Marty::Event.clear_cache
166
+ af = Marty::Event.all_finished
167
+ expect(af.count).to eq(5)
168
+ expect(af).to include(['testcl', 1234])
169
+ expect(af).to include(['testcl', 2345])
170
+ expect(af[['testcl', 1234]]).to include('AVM')
171
+ expect(af[['testcl', 2345]]).to include('AVM')
172
+ expect(af[['testcl', 1234]]['AVM']).to start_with(@date_string)
173
+ expect(af[['testcl', 2345]]['AVM']).to start_with(@date_string)
174
+
175
+ end
176
+ end
@@ -1,6 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'marty'
3
- require 'delorean_lang'
4
2
  require 'job_helper'
5
3
 
6
4
  describe Marty::Promise, slow: true do
data/spec/spec_helper.rb CHANGED
@@ -30,6 +30,11 @@ class ActiveRecord::Base
30
30
  end
31
31
 
32
32
  Capybara.register_driver :selenium do |app|
33
+ # use personal firefox if it exists
34
+ personal_firefox = File.expand_path('~/firefox/firefox')
35
+ Selenium::WebDriver::Firefox::Binary.path = personal_firefox if
36
+ File.exists?(personal_firefox)
37
+
33
38
  client = Selenium::WebDriver::Remote::Http::Default.new
34
39
  profile = Selenium::WebDriver::Firefox::Profile.new
35
40
  profile.load_no_focus_lib = true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marty
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.12
4
+ version: 1.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arman Bostani
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2016-09-21 00:00:00.000000000 Z
17
+ date: 2016-10-28 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: pg
@@ -382,6 +382,8 @@ files:
382
382
  - app/models/marty/config.rb
383
383
  - app/models/marty/data_grid.rb
384
384
  - app/models/marty/enum.rb
385
+ - app/models/marty/enum_event_operation.rb
386
+ - app/models/marty/event.rb
385
387
  - app/models/marty/grid_index_boolean.rb
386
388
  - app/models/marty/grid_index_int4range.rb
387
389
  - app/models/marty/grid_index_integer.rb
@@ -432,6 +434,8 @@ files:
432
434
  - db/migrate/104_create_marty_grid_index_strings.rb
433
435
  - db/migrate/105_create_marty_grid_index_booleans.rb
434
436
  - db/migrate/106_make_grid_indexes_nullable.rb
437
+ - db/migrate/200_create_marty_event_operation_enum.rb
438
+ - db/migrate/201_create_marty_events.rb
435
439
  - db/seeds.rb
436
440
  - gemini_deprecations.md
437
441
  - lib/marty.rb
@@ -480,6 +484,7 @@ files:
480
484
  - spec/dummy/app/models/gemini/amortization_type.rb
481
485
  - spec/dummy/app/models/gemini/bud_category.rb
482
486
  - spec/dummy/app/models/gemini/entity.rb
487
+ - spec/dummy/app/models/gemini/enum_event_operation.rb
483
488
  - spec/dummy/app/models/gemini/enum_state.rb
484
489
  - spec/dummy/app/models/gemini/extras/data_import.rb
485
490
  - spec/dummy/app/models/gemini/extras/settlement_import.rb
@@ -522,6 +527,7 @@ files:
522
527
  - spec/dummy/db/migrate/20150420000003_create_grouping_head_versions.rb
523
528
  - spec/dummy/db/migrate/20151023000001_create_simple.rb
524
529
  - spec/dummy/db/migrate/20160100000038_create_gemini_states.rb
530
+ - spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb
525
531
  - spec/dummy/db/seeds.rb
526
532
  - spec/dummy/delorean/blame_report.dl
527
533
  - spec/dummy/delorean/data_report.dl
@@ -1562,6 +1568,7 @@ files:
1562
1568
  - spec/models/api_auth_spec.rb
1563
1569
  - spec/models/config_spec.rb
1564
1570
  - spec/models/data_grid_spec.rb
1571
+ - spec/models/event_spec.rb
1565
1572
  - spec/models/import_type_spec.rb
1566
1573
  - spec/models/posting_spec.rb
1567
1574
  - spec/models/promise_spec.rb