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 +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
|