marty 6.1.0 → 8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +17 -3
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +3 -2
- data/Gemfile +2 -1
- data/app/assets/javascripts/marty/extjs/extensions/marty.js +23 -1
- data/app/assets/stylesheets/marty/application.css +4 -1
- data/app/assets/stylesheets/marty/dark_mode.css +17 -0
- data/app/components/marty/auth_app.rb +10 -1
- data/app/components/marty/auth_app/client/auth_app.js +4 -0
- data/app/components/marty/extras/layout.rb +2 -2
- data/app/components/marty/extras/misc.rb +1 -1
- data/app/components/marty/log_view.rb +0 -1
- data/app/components/marty/main_auth_app.rb +9 -12
- data/app/components/marty/promise_view.rb +5 -0
- data/app/components/marty/promise_view/client/promise_view.js +11 -0
- data/app/components/marty/schedule_jobs_dashboard.rb +30 -96
- data/app/components/marty/schedule_jobs_grid.rb +118 -0
- data/app/controllers/marty/application_controller.rb +6 -2
- data/app/models/marty/base.rb +48 -48
- data/app/models/marty/config.rb +14 -2
- data/app/models/marty/user.rb +12 -0
- data/app/services/marty/background_job/fetch_missing_in_schedule_cron_jobs.rb +19 -0
- data/app/services/marty/enums/report.rb +18 -0
- data/app/services/marty/promises/delorean/create.rb +16 -27
- data/app/services/marty/promises/ruby/create.rb +10 -1
- data/app/views/layouts/marty/application.html.erb +4 -1
- data/app/views/marty/diagnostic/op.html.erb +3 -1
- data/config/locales/en.yml +2 -0
- data/db/migrate/512_add_promise_priority.rb +9 -0
- data/db/migrate/513_add_priority_to_promise_view.rb +44 -0
- data/db/migrate/514_remove_marty_events.rb +13 -0
- data/delorean/enum_report.dl +11 -0
- data/delorean/table_report.dl +7 -0
- data/docker-compose.dummy.yml +7 -4
- data/lib/marty.rb +5 -2
- data/lib/marty/api/base.rb +15 -9
- data/lib/marty/cache_adapters.rb +2 -0
- data/lib/marty/cache_adapters/mcfly_ruby_cache.rb +1 -5
- data/lib/marty/cache_adapters/memory_and_redis.rb +93 -0
- data/lib/marty/cache_adapters/redis.rb +63 -0
- data/lib/marty/delayed_job/scheduled_job_plugin.rb +33 -0
- data/lib/marty/diagnostic/database.rb +1 -2
- data/lib/marty/logger.rb +50 -17
- data/lib/marty/monkey.rb +26 -6
- data/lib/marty/promise_ruby_job.rb +2 -0
- data/lib/marty/rails_app.rb +29 -0
- data/lib/marty/railtie.rb +1 -0
- data/lib/marty/version.rb +1 -1
- data/marty.gemspec +1 -0
- data/spec/controllers/job_controller_spec.rb +2 -2
- data/spec/dummy/app/components/gemini/cm_auth_app.rb +12 -0
- data/spec/dummy/app/components/gemini/simple_view.rb +17 -0
- data/spec/dummy/app/jobs/test_failing_job.rb +14 -0
- data/spec/dummy/app/models/gemini/helper.rb +90 -1
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/db/migrate/20191101132729_add_activity_flag_to_simple.rb +10 -0
- data/spec/dummy/delorean/blame_report.dl +1 -0
- data/spec/dummy/delorean/enum_report.dl +1 -0
- data/spec/dummy/delorean/marty_fields.dl +1 -0
- data/spec/dummy/delorean/table_report.dl +1 -0
- data/spec/features/data_blame_report_spec.rb +66 -0
- data/spec/features/data_grid_spec.rb +1 -1
- data/spec/features/enum_values_report_spec.rb +76 -0
- data/spec/features/inline_editing_spec.rb +33 -0
- data/spec/features/rule_spec.rb +1 -1
- data/spec/features/schedule_jobs_dashboard_spec.rb +1 -1
- data/spec/features/scripting_spec.rb +1 -1
- data/spec/features/user_list_report_spec.rb +74 -0
- data/spec/fixtures/misc/struct_compare_tests.txt +15 -5
- data/spec/job_helper.rb +39 -0
- data/spec/jobs/cron_job_spec.rb +91 -0
- data/spec/lib/mcfly_model_spec.rb +9 -0
- data/spec/models/promise_spec.rb +168 -1
- data/spec/other/diagnostic/delayed_job_workers_spec.rb +1 -1
- data/spec/performance/caching_spec.rb +99 -0
- data/spec/services/background_job/fetch_missing_in_schedule_cron_jobs_spec.rb +34 -0
- data/spec/support/delayed_job_helpers.rb +3 -3
- data/spec/support/shared_connection.rb +9 -1
- data/spec/support/structure_compare.rb +19 -3
- metadata +39 -6
- data/app/components/marty/event_view.rb +0 -129
- data/app/models/marty/event.rb +0 -317
- data/spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb +0 -8
- data/spec/dummy/delorean/blame_report.dl +0 -268
- data/spec/dummy/delorean/marty_fields.dl +0 -63
- data/spec/dummy/delorean/table_report.dl +0 -34
- data/spec/models/event_spec.rb +0 -272
data/app/models/marty/event.rb
DELETED
@@ -1,317 +0,0 @@
|
|
1
|
-
class Marty::Event < Marty::Base
|
2
|
-
class EventValidator < ActiveModel::Validator
|
3
|
-
def validate(event)
|
4
|
-
event.errors[:base] << 'Must have promise_id or start_dt' unless
|
5
|
-
event.promise_id || event.start_dt
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
validates_presence_of :klass, :subject_id, :enum_event_operation
|
10
|
-
|
11
|
-
belongs_to :promise
|
12
|
-
|
13
|
-
validates_with EventValidator
|
14
|
-
|
15
|
-
after_validation(on: [:create, :update]) do
|
16
|
-
self.comment = comment.truncate(255) if comment
|
17
|
-
end
|
18
|
-
|
19
|
-
UPDATE_SQL = <<SQL
|
20
|
-
UPDATE marty_events as me
|
21
|
-
SET start_dt = p.start_dt,
|
22
|
-
end_dt = p.end_dt
|
23
|
-
FROM marty_promises p
|
24
|
-
WHERE me.promise_id = p.id
|
25
|
-
AND ( ( p.start_dt IS NOT NULL
|
26
|
-
AND me.start_dt IS NULL
|
27
|
-
)
|
28
|
-
OR ( p.end_dt IS NOT NULL
|
29
|
-
AND me.end_dt IS NULL
|
30
|
-
)
|
31
|
-
)
|
32
|
-
SQL
|
33
|
-
BASE_QUERY = <<SQL
|
34
|
-
SELECT id,
|
35
|
-
klass,
|
36
|
-
subject_id,
|
37
|
-
enum_event_operation,
|
38
|
-
comment,
|
39
|
-
start_dt,
|
40
|
-
end_dt,
|
41
|
-
expire_secs,
|
42
|
-
error
|
43
|
-
FROM marty_events
|
44
|
-
SQL
|
45
|
-
def self.running_query(time_now_s)
|
46
|
-
"#{BASE_QUERY}
|
47
|
-
WHERE start_dt >= '#{time_now_s}'::timestamp - interval '24 hours'
|
48
|
-
AND (end_dt IS NULL or end_dt > '#{time_now_s}'::timestamp)
|
49
|
-
AND (expire_secs IS NULL
|
50
|
-
OR expire_secs > EXTRACT (EPOCH FROM '#{time_now_s}'::timestamp - start_dt))
|
51
|
-
ORDER BY start_dt"
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.op_is_running?(klass, subject_id, operation)
|
55
|
-
all_running.detect do |pm|
|
56
|
-
pm['klass'] == klass && pm['subject_id'].to_i == subject_id.to_i &&
|
57
|
-
pm['enum_event_operation'] == operation
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.create_event(klass,
|
62
|
-
subject_id,
|
63
|
-
operation,
|
64
|
-
start_dt,
|
65
|
-
expire_secs,
|
66
|
-
comment = nil)
|
67
|
-
|
68
|
-
# use lookup_event instead of all_running which is throttled
|
69
|
-
evs = lookup_event(klass, subject_id, operation)
|
70
|
-
running = evs.detect do |ev|
|
71
|
-
next if ev['end_dt']
|
72
|
-
next true unless ev['expire_secs']
|
73
|
-
|
74
|
-
(Time.zone.now - ev['start_dt']).truncate < ev['expire_secs']
|
75
|
-
end
|
76
|
-
|
77
|
-
raise "#{operation} is already running for #{klass}/#{subject_id}" if
|
78
|
-
running
|
79
|
-
|
80
|
-
create!(klass: klass,
|
81
|
-
subject_id: subject_id,
|
82
|
-
enum_event_operation: operation,
|
83
|
-
start_dt: start_dt,
|
84
|
-
expire_secs: expire_secs,
|
85
|
-
comment: comment,
|
86
|
-
)
|
87
|
-
end
|
88
|
-
|
89
|
-
def self.lookup_event(klass, subject_id, operation)
|
90
|
-
update_start_and_end
|
91
|
-
get_data("#{BASE_QUERY}
|
92
|
-
WHERE klass = '#{klass}'
|
93
|
-
AND subject_id = #{subject_id}
|
94
|
-
AND enum_event_operation = '#{operation}'")
|
95
|
-
|
96
|
-
# For now we return a bare hash
|
97
|
-
# Marty::Event.find_by_id(hash["id"])
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.finish_event(klass, subject_id, operation, error = false, comment = nil)
|
101
|
-
raise 'error must be true or false' unless [true, false].include?(error)
|
102
|
-
|
103
|
-
time_now_s = Time.zone.now.strftime('%Y-%m-%d %H:%M:%S.%6N')
|
104
|
-
|
105
|
-
event = get_data(running_query(time_now_s)).detect do |ev|
|
106
|
-
ev['klass'] == klass && ev['subject_id'] == subject_id.to_i &&
|
107
|
-
ev['enum_event_operation'] == operation
|
108
|
-
end
|
109
|
-
raise "event #{klass}/#{subject_id}/#{operation} not found" unless
|
110
|
-
event
|
111
|
-
|
112
|
-
ev = Marty::Event.find_by_id(event['id'])
|
113
|
-
raise "can't explicitly finish a promise event" if ev.promise_id
|
114
|
-
|
115
|
-
ev.end_dt = Time.zone.now
|
116
|
-
ev.error = error
|
117
|
-
ev.comment = comment if comment
|
118
|
-
ev.save!
|
119
|
-
end
|
120
|
-
|
121
|
-
def self.last_event(klass, subject_id, operation = nil)
|
122
|
-
hash = all_running.reverse.find do |pm|
|
123
|
-
pm['klass'] == klass && pm['subject_id'] == subject_id.to_i &&
|
124
|
-
(operation.nil? || pm['enum_event_operation'] == operation)
|
125
|
-
end
|
126
|
-
|
127
|
-
return hash if hash
|
128
|
-
|
129
|
-
op_sql = "AND enum_event_operation = '#{operation}'" if operation
|
130
|
-
|
131
|
-
get_data("#{BASE_QUERY}
|
132
|
-
WHERE klass = '#{klass}'
|
133
|
-
AND subject_id = #{subject_id} #{op_sql}
|
134
|
-
AND end_dt IS NOT NULL
|
135
|
-
ORDER BY end_dt desc").first
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.last_event_multi(klass, subject_ids_arg, operation = nil)
|
139
|
-
subject_ids = subject_ids_arg.map(&:to_i)
|
140
|
-
events = all_running.select do |pm|
|
141
|
-
pm['klass'] == klass && subject_ids.include?(pm['subject_id']) &&
|
142
|
-
(operation.nil? || pm['enum_event_operation'] == operation)
|
143
|
-
end.group_by { |ev| ev['subject_id'] }.each_with_object({}) do |(id, evs), h|
|
144
|
-
h[id] = evs.sort { |a, b| a['start_dt'] <=> b['start_dt'] }.first
|
145
|
-
end
|
146
|
-
|
147
|
-
running_ids = events.keys
|
148
|
-
check_fin = subject_ids - running_ids
|
149
|
-
|
150
|
-
if check_fin.present?
|
151
|
-
op_filt = "AND enum_event_operation = '#{operation}'" if operation
|
152
|
-
op_col = ', enum_event_operation' if operation
|
153
|
-
|
154
|
-
fins = get_data("SELECT klass,
|
155
|
-
subject_id,
|
156
|
-
enum_event_operation,
|
157
|
-
comment,
|
158
|
-
start_dt,
|
159
|
-
end_dt,
|
160
|
-
expire_secs,
|
161
|
-
error
|
162
|
-
FROM (SELECT klass,
|
163
|
-
subject_id,
|
164
|
-
enum_event_operation,
|
165
|
-
comment,
|
166
|
-
start_dt,
|
167
|
-
end_dt,
|
168
|
-
expire_secs,
|
169
|
-
error,
|
170
|
-
ROW_NUMBER() OVER (PARTITION BY klass,
|
171
|
-
subject_id
|
172
|
-
#{op_col}
|
173
|
-
ORDER BY end_dt DESC) rnum
|
174
|
-
FROM marty_events
|
175
|
-
WHERE klass = '#{klass}'
|
176
|
-
AND subject_id IN (#{check_fin.join(',')})
|
177
|
-
#{op_filt}
|
178
|
-
AND end_dt IS NOT NULL) sub
|
179
|
-
WHERE rnum = 1")
|
180
|
-
|
181
|
-
fins.each do |fin|
|
182
|
-
events[fin['subject_id']] = fin
|
183
|
-
end
|
184
|
-
end
|
185
|
-
events
|
186
|
-
end
|
187
|
-
|
188
|
-
def self.currently_running(klass, subject_id)
|
189
|
-
all_running.select do |pm|
|
190
|
-
pm['klass'] == klass && pm['subject_id'] == subject_id.to_i
|
191
|
-
end.map { |e| e['enum_event_operation'] }
|
192
|
-
end
|
193
|
-
|
194
|
-
def self.currently_running_multi(klass, subject_id_raw)
|
195
|
-
subject_ids = [subject_id_raw].flatten.map(&:to_i)
|
196
|
-
all_running.select do |pm|
|
197
|
-
pm['klass'] == klass && subject_ids.include?(pm['subject_id'])
|
198
|
-
end.each_with_object({}) do |e, h|
|
199
|
-
(h[e['subject_id']] ||= []) << e['enum_event_operation']
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
def self.update_comment(hash, comment)
|
204
|
-
hid = hash.is_a?(Hash) ? hash['id'] : hash
|
205
|
-
e = Marty::Event.find_by_id(hid)
|
206
|
-
e.comment = comment
|
207
|
-
e.save!
|
208
|
-
end
|
209
|
-
|
210
|
-
def self.pretty_op(hash)
|
211
|
-
d = hash['enum_event_operation'].downcase.capitalize
|
212
|
-
|
213
|
-
# && !(hash['comment'] =~ /^ERROR/)
|
214
|
-
hash['end_dt'] ? d.sub(/ing/, 'ed') : d
|
215
|
-
end
|
216
|
-
|
217
|
-
def self.compact_end_dt(hash)
|
218
|
-
hash['end_dt'] ? hash['end_dt'].strftime('%H:%M') : '---'
|
219
|
-
end
|
220
|
-
|
221
|
-
def self.update_start_and_end
|
222
|
-
ActiveRecord::Base.connection.execute(UPDATE_SQL).cmd_tuples
|
223
|
-
end
|
224
|
-
|
225
|
-
def self.get_data(sql)
|
226
|
-
ActiveRecord::Base.connection.execute(sql).to_a.map do |h|
|
227
|
-
h['id'] = h['id'].to_i
|
228
|
-
h['subject_id'] = h['subject_id'].to_i
|
229
|
-
h['start_dt'] = Time.zone.parse(h['start_dt']) if h['start_dt']
|
230
|
-
h['end_dt'] = Time.zone.parse(h['end_dt']) if h['end_dt']
|
231
|
-
h['expire_secs'] = h['expire_secs'].to_i if h['expire_secs']
|
232
|
-
h['comment'] = h['comment']
|
233
|
-
h['error'] = h['error']
|
234
|
-
h
|
235
|
-
end
|
236
|
-
end
|
237
|
-
private_class_method :get_data
|
238
|
-
|
239
|
-
def self.clear_cache
|
240
|
-
@poll_secs = @all_running = @all_finished = nil
|
241
|
-
end
|
242
|
-
|
243
|
-
def self.all_running
|
244
|
-
@all_running ||= { timestamp: 0, data: [] }
|
245
|
-
@poll_secs ||= Marty::Config['MARTY_EVENT_POLL_SECS'] || 0
|
246
|
-
time_now = Time.zone.now
|
247
|
-
time_now_i = time_now.to_i
|
248
|
-
time_now_s = time_now.strftime('%Y-%m-%d %H:%M:%S.%6N')
|
249
|
-
upd_count = update_start_and_end
|
250
|
-
if upd_count > 0 ||
|
251
|
-
time_now_i - @all_running[:timestamp] > @poll_secs
|
252
|
-
@all_running[:data] = get_data(running_query(time_now_s))
|
253
|
-
@all_running[:timestamp] = time_now_i
|
254
|
-
end
|
255
|
-
@all_running[:data]
|
256
|
-
end
|
257
|
-
private_class_method :all_running
|
258
|
-
|
259
|
-
def self.all_finished
|
260
|
-
@all_finished ||= {
|
261
|
-
data: {},
|
262
|
-
timestamp: Time.zone.parse('00:00:00').to_i,
|
263
|
-
}
|
264
|
-
@poll_secs ||= Marty::Config['MARTY_EVENT_POLL_SECS'] || 0
|
265
|
-
time_now_i = Time.zone.now.to_i
|
266
|
-
cutoff = Time.zone.at(@all_finished[:timestamp]).
|
267
|
-
strftime('%Y-%m-%d %H:%M:%S.%6N')
|
268
|
-
|
269
|
-
upd_count = update_start_and_end
|
270
|
-
if upd_count > 0 ||
|
271
|
-
time_now_i - @all_finished[:timestamp] > @poll_secs
|
272
|
-
raw = get_data("
|
273
|
-
SELECT * FROM (SELECT id
|
274
|
-
, klass
|
275
|
-
, subject_id
|
276
|
-
, enum_event_operation
|
277
|
-
, start_dt
|
278
|
-
, end_dt
|
279
|
-
, expire_secs
|
280
|
-
, comment
|
281
|
-
, error
|
282
|
-
, ROW_NUMBER() OVER (PARTITION BY
|
283
|
-
klass
|
284
|
-
, subject_id
|
285
|
-
, enum_event_operation
|
286
|
-
ORDER BY end_dt DESC) row_num
|
287
|
-
FROM marty_events
|
288
|
-
WHERE end_dt > '#{cutoff}') sub
|
289
|
-
WHERE row_num = 1")
|
290
|
-
@all_finished[:timestamp] = time_now_i
|
291
|
-
raw.each_with_object(@all_finished[:data]) do |ev, hash|
|
292
|
-
if ev['end_dt'] && ev['error'].nil?
|
293
|
-
real_ev = Marty::Event.where(id: ev['id']).first
|
294
|
-
promise = Marty::Promise.where(id: real_ev['promise_id']).first
|
295
|
-
maybe_error = promise.result['error']
|
296
|
-
ev['error'] = real_ev.error = !!maybe_error
|
297
|
-
real_ev.comment = maybe_error
|
298
|
-
real_ev.save!
|
299
|
-
end
|
300
|
-
subhash = hash[[ev['klass'], ev['subject_id']]] ||= {}
|
301
|
-
subhash[ev['enum_event_operation']] =
|
302
|
-
ev['end_dt'].strftime('%Y-%m-%d %H:%M:%S')
|
303
|
-
end
|
304
|
-
end
|
305
|
-
@all_finished[:data]
|
306
|
-
end
|
307
|
-
|
308
|
-
def self.get_finished(klass, id)
|
309
|
-
all_finished[[klass, id]]
|
310
|
-
end
|
311
|
-
|
312
|
-
def self.cleanup
|
313
|
-
where('start_dt < ?', Time.zone.now - 48.hours).delete_all
|
314
|
-
rescue StandardError => e
|
315
|
-
Marty::Util.logger.error("event GC error: #{e}")
|
316
|
-
end
|
317
|
-
end
|
@@ -1,8 +0,0 @@
|
|
1
|
-
class AddBulkPricingEventOps < ActiveRecord::Migration[4.2]
|
2
|
-
disable_ddl_transaction!
|
3
|
-
def change
|
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
|
-
end
|
8
|
-
end
|
@@ -1,268 +0,0 @@
|
|
1
|
-
import MartyFields
|
2
|
-
import Styles
|
3
|
-
|
4
|
-
PostingField2: MartyFields::PostingField2
|
5
|
-
store = [ ["NOW", "NOW"] ] + [ [ lp.name, lp.name + ' (' + lp.comment + ')']
|
6
|
-
for lp in Marty::Posting.get_latest_by_type(
|
7
|
-
20, ['BASE', 'INTRA', 'CLOSE'])]
|
8
|
-
|
9
|
-
StyleRow:
|
10
|
-
profile =?
|
11
|
-
obj = profile["obj"]
|
12
|
-
status = profile["status"]
|
13
|
-
attrs = profile["attrs"]
|
14
|
-
user_name = Marty::DataChange.user_name(obj.user_id)
|
15
|
-
delete = profile["deleted"]
|
16
|
-
create = status == "new"
|
17
|
-
|
18
|
-
o_user_name = if delete then Marty::DataChange.user_name(obj.o_user_id)
|
19
|
-
else ""
|
20
|
-
o_dt = if delete then obj.obsoleted_dt.to_s else ""
|
21
|
-
del_style = if delete then Styles::Style.bg_redish else {}
|
22
|
-
mod_style = Styles::Style.bg_tan
|
23
|
-
new_style = if create then Styles::Style.bg_lightgreen else {}
|
24
|
-
|
25
|
-
b_style = Styles::Style.calibri + new_style
|
26
|
-
b_c_style = b_style + mod_style
|
27
|
-
m_style = b_style + Styles::Style.bg_lightgray + del_style
|
28
|
-
m_d_style = m_style + Styles::Style.datetime
|
29
|
-
|
30
|
-
row = [
|
31
|
-
obj.group_id,
|
32
|
-
obj.created_dt.to_s,
|
33
|
-
user_name,
|
34
|
-
o_dt,
|
35
|
-
o_user_name,
|
36
|
-
] + [ a.value for a in attrs]
|
37
|
-
|
38
|
-
row_styles = [
|
39
|
-
m_style,
|
40
|
-
m_d_style,
|
41
|
-
m_style,
|
42
|
-
m_d_style,
|
43
|
-
m_style,
|
44
|
-
] + [
|
45
|
-
(if a.changed then b_c_style else b_style)
|
46
|
-
for a in attrs
|
47
|
-
]
|
48
|
-
|
49
|
-
s_row = ["row", row, {"style": row_styles}]
|
50
|
-
|
51
|
-
StyleGroup:
|
52
|
-
group =?
|
53
|
-
rows = [
|
54
|
-
StyleRow(profile = profile).s_row
|
55
|
-
for profile in group
|
56
|
-
]
|
57
|
-
|
58
|
-
ModelRows:
|
59
|
-
klass =?
|
60
|
-
t1 =?
|
61
|
-
t2 =?
|
62
|
-
ids =?
|
63
|
-
|
64
|
-
groups = Marty::DataChange.changes(t1, t2, klass, ids)
|
65
|
-
|
66
|
-
headers = ["Group ID", "Created", "By", "Deleted", "By"] +
|
67
|
-
Marty::DataChange.class_headers(klass)
|
68
|
-
|
69
|
-
width = headers.length
|
70
|
-
border = [
|
71
|
-
"border",
|
72
|
-
[0, {"off": 1}, width, {"off": 1}],
|
73
|
-
Styles::Style.border_thin,
|
74
|
-
]
|
75
|
-
|
76
|
-
row_groups = [
|
77
|
-
StyleGroup(group = x[1]).rows + [border]
|
78
|
-
for x in groups
|
79
|
-
]
|
80
|
-
|
81
|
-
s_style = {"style": [Styles::Style.s_hdr] * width}
|
82
|
-
s_headers = ["row", headers, s_style]
|
83
|
-
rows = [s_headers] + row_groups.flatten(1)
|
84
|
-
count = row_groups.length
|
85
|
-
ws = if count > 0 then [klass, rows] else nil
|
86
|
-
|
87
|
-
OptionalIDsField: MartyFields::TextField
|
88
|
-
field_label = "Group IDs (optional)"
|
89
|
-
name = "restrict_to_ids"
|
90
|
-
|
91
|
-
DataBlameReport:
|
92
|
-
title = "Data Blame Report"
|
93
|
-
|
94
|
-
pt_name1 =?
|
95
|
-
posting1 =? Marty::Posting.lookup(pt_name1)
|
96
|
-
t1 = posting1.created_dt
|
97
|
-
|
98
|
-
pt_name2 =?
|
99
|
-
posting2 =? Marty::Posting.lookup(pt_name2)
|
100
|
-
t2 = posting2.created_dt
|
101
|
-
|
102
|
-
class_list =? false
|
103
|
-
restrict_to_ids =? nil
|
104
|
-
|
105
|
-
form = [
|
106
|
-
MartyFields::PostingField1,
|
107
|
-
PostingField2,
|
108
|
-
MartyFields::ClassListField,
|
109
|
-
OptionalIDsField
|
110
|
-
]
|
111
|
-
|
112
|
-
ts = if Marty::Helper.infinity_dt(t1)
|
113
|
-
then [t2, t1]
|
114
|
-
else if Marty::Helper.infinity_dt(t2)
|
115
|
-
then [t1, t2]
|
116
|
-
else [t1, t2].sort
|
117
|
-
|
118
|
-
sanitized = if class_list
|
119
|
-
then Marty::DataChange.sanitize_classes(class_list)
|
120
|
-
else Marty::DataChange.class_list
|
121
|
-
|
122
|
-
ids = if restrict_to_ids
|
123
|
-
then [ idstr.to_i for idstr in restrict_to_ids.split(',') ]
|
124
|
-
else nil
|
125
|
-
|
126
|
-
ids_check = if ids && ids.length > 0 && sanitized.length > 1
|
127
|
-
then ERR("Can't specify Group IDs if more than one class selected")
|
128
|
-
else true
|
129
|
-
|
130
|
-
result = ids_check && [
|
131
|
-
ModelRows(t1 = ts[0], t2 = ts[1], klass = klass,
|
132
|
-
ids = ids).ws
|
133
|
-
for klass in sanitized
|
134
|
-
].compact
|
135
|
-
|
136
|
-
format = "xlsx"
|
137
|
-
|
138
|
-
ModelSummaryRow:
|
139
|
-
klass =?
|
140
|
-
t1 =?
|
141
|
-
t2 =?
|
142
|
-
|
143
|
-
r = Marty::DataChange.change_summary(t1, t2, klass)
|
144
|
-
cr = r['created']
|
145
|
-
up = r['updated']
|
146
|
-
dl = r['deleted']
|
147
|
-
|
148
|
-
ws = if cr > 0 || up > 0 || dl > 0 then ["row", [klass, cr, up, dl]] else nil
|
149
|
-
|
150
|
-
DataBlameReportSummary:
|
151
|
-
title = "Summary Data Blame Report"
|
152
|
-
|
153
|
-
pt_name1 =?
|
154
|
-
posting1 =? Marty::Posting.lookup(pt_name1)
|
155
|
-
t1 = posting1.created_dt
|
156
|
-
|
157
|
-
pt_name2 =?
|
158
|
-
posting2 =? Marty::Posting.lookup(pt_name2)
|
159
|
-
t2 = posting2.created_dt
|
160
|
-
|
161
|
-
form = [
|
162
|
-
MartyFields::PostingField1,
|
163
|
-
PostingField2,
|
164
|
-
]
|
165
|
-
|
166
|
-
postings = [
|
167
|
-
["Posting 1", pt_name1,],
|
168
|
-
["Posting 2", pt_name2,],
|
169
|
-
]
|
170
|
-
|
171
|
-
hrow = [
|
172
|
-
"row", ["Data Table", "Created", "Updated", "Deleted",],
|
173
|
-
Styles::Style.m_hdr_style0,
|
174
|
-
]
|
175
|
-
|
176
|
-
ts = if Marty::Helper.infinity_dt(t1)
|
177
|
-
then [t2, t1]
|
178
|
-
else if Marty::Helper.infinity_dt(t2)
|
179
|
-
then [t1, t2]
|
180
|
-
else [t1, t2].sort
|
181
|
-
|
182
|
-
rows = [
|
183
|
-
ModelSummaryRow(t1 = ts[0], t2 = ts[1], klass = klass).ws
|
184
|
-
for klass in Marty::DataChange.class_list
|
185
|
-
].compact
|
186
|
-
|
187
|
-
header = [["row", r, {"style" : [Styles::Style.s_hdr]}] for r in postings]
|
188
|
-
result = [[title,[hrow] + rows, {"widths" : [30]}], ["Parameters", header]]
|
189
|
-
format = "xlsx"
|
190
|
-
|
191
|
-
DeadReferenceReport:
|
192
|
-
title = "Dead Reference Report"
|
193
|
-
|
194
|
-
class_list =? false
|
195
|
-
pt_name =?
|
196
|
-
posting =? Marty::Posting.lookup(pt_name)
|
197
|
-
|
198
|
-
sanitized = if class_list
|
199
|
-
then Marty::DataChange.sanitize_classes(class_list)
|
200
|
-
else Marty::DataChange.class_list
|
201
|
-
|
202
|
-
form = [
|
203
|
-
MartyFields::PostingField,
|
204
|
-
MartyFields::ClassListField,
|
205
|
-
]
|
206
|
-
|
207
|
-
result = [
|
208
|
-
[[[klass, attr, obj].flatten for obj in list]
|
209
|
-
for attr, list in Marty::DataChange.dead_refs(posting.created_dt,
|
210
|
-
klass)].flatten(1)
|
211
|
-
for klass in sanitized
|
212
|
-
].flatten(1)
|
213
|
-
|
214
|
-
format = "csv"
|
215
|
-
|
216
|
-
######################################################################
|
217
|
-
|
218
|
-
DataImportParam:
|
219
|
-
field_label = "Input Rows"
|
220
|
-
name = "data_import_field"
|
221
|
-
xtype = ":textareafield"
|
222
|
-
scrollable = true
|
223
|
-
field_style = {
|
224
|
-
"font_family": 'courier new',
|
225
|
-
"font_size": '12px',
|
226
|
-
}
|
227
|
-
height = 600
|
228
|
-
|
229
|
-
CommaSepField: MartyFields::CheckboxField
|
230
|
-
name = "comma_sep"
|
231
|
-
field_label = "Comma Separated"
|
232
|
-
|
233
|
-
DiffReport:
|
234
|
-
title = "Diff Report"
|
235
|
-
|
236
|
-
class_name =? false
|
237
|
-
data_import_field =?
|
238
|
-
comma_sep =? false
|
239
|
-
|
240
|
-
klass = Marty::Helper.constantize(class_name)
|
241
|
-
|
242
|
-
col_types = Marty::Helper.get_column_types(klass)
|
243
|
-
data = Marty::Helper.parse_csv_to_hash(
|
244
|
-
data_import_field, comma_sep, col_types)
|
245
|
-
|
246
|
-
diff = Marty::DataChange.diff(klass, data)
|
247
|
-
|
248
|
-
result = [{"title" : "different",
|
249
|
-
"format" : "csv",
|
250
|
-
"result" : diff["different"].flatten},
|
251
|
-
{"title" : "same",
|
252
|
-
"format" : "csv",
|
253
|
-
"result" : diff["same"]},
|
254
|
-
{"title" : "only_input",
|
255
|
-
"format" : "csv",
|
256
|
-
"result" : diff["only_input"]},
|
257
|
-
{"title" : "only_source",
|
258
|
-
"format" : "csv",
|
259
|
-
"result" : diff["only_source"]},
|
260
|
-
]
|
261
|
-
|
262
|
-
form = [
|
263
|
-
MartyFields::ClassField,
|
264
|
-
DataImportParam,
|
265
|
-
CommaSepField,
|
266
|
-
]
|
267
|
-
|
268
|
-
format = "zip"
|