marty 1.0.12 → 1.0.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/app/components/marty/main_auth_app.rb +145 -1
- data/app/models/marty/enum_event_operation.rb +5 -0
- data/app/models/marty/event.rb +206 -0
- data/db/migrate/200_create_marty_event_operation_enum.rb +9 -0
- data/db/migrate/201_create_marty_events.rb +30 -0
- data/db/seeds.rb +4 -0
- data/lib/marty/migrations.rb +53 -0
- data/lib/marty/promise_job.rb +10 -5
- data/lib/marty/version.rb +1 -1
- data/spec/dummy/app/models/gemini/enum_event_operation.rb +5 -0
- data/spec/dummy/db/migrate/20140000000000_create_enums.rb +2 -12
- data/spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb +9 -0
- data/spec/job_helper.rb +8 -0
- data/spec/models/event_spec.rb +176 -0
- data/spec/models/promise_spec.rb +0 -2
- data/spec/spec_helper.rb +5 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6954bc349cc4800a6505d17737f5ddcb91ce16b2
|
4
|
+
data.tar.gz: 8bfff59fcd54425f3f076b0fdc61ec98a5d52b23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3d5897c69ee0feb698f467a30095273ba724a030fbeae9032f82129e3341b6905c67ededb6256264079702a68a286076ae216d9384b06eb84edbf3d1b4c7300
|
7
|
+
data.tar.gz: f36961f6208fbdef9d54a9f717cadb04cac2bb5a3d7c956905a650718d7996c155469d93f0ac3dce78d16991fbac1d6947625acb81ca2f0ccaf5458c48821459
|
data/Gemfile
CHANGED
@@ -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,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
data/lib/marty/migrations.rb
CHANGED
@@ -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
|
|
data/lib/marty/promise_job.rb
CHANGED
@@ -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,16 +1,6 @@
|
|
1
1
|
class CreateEnums < ActiveRecord::Migration
|
2
|
+
include Marty::Migrations
|
2
3
|
def change
|
3
|
-
|
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
|
data/spec/models/promise_spec.rb
CHANGED
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.
|
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-
|
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
|