marty 6.1.0 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.gitlab-ci.yml +17 -3
  4. data/.rubocop.yml +5 -0
  5. data/.rubocop_todo.yml +3 -2
  6. data/Gemfile +2 -1
  7. data/app/assets/javascripts/marty/extjs/extensions/marty.js +23 -1
  8. data/app/assets/stylesheets/marty/application.css +4 -1
  9. data/app/assets/stylesheets/marty/dark_mode.css +17 -0
  10. data/app/components/marty/auth_app.rb +10 -1
  11. data/app/components/marty/auth_app/client/auth_app.js +4 -0
  12. data/app/components/marty/extras/layout.rb +2 -2
  13. data/app/components/marty/extras/misc.rb +1 -1
  14. data/app/components/marty/log_view.rb +0 -1
  15. data/app/components/marty/main_auth_app.rb +9 -12
  16. data/app/components/marty/promise_view.rb +5 -0
  17. data/app/components/marty/promise_view/client/promise_view.js +11 -0
  18. data/app/components/marty/schedule_jobs_dashboard.rb +30 -96
  19. data/app/components/marty/schedule_jobs_grid.rb +118 -0
  20. data/app/controllers/marty/application_controller.rb +6 -2
  21. data/app/models/marty/base.rb +48 -48
  22. data/app/models/marty/config.rb +14 -2
  23. data/app/models/marty/user.rb +12 -0
  24. data/app/services/marty/background_job/fetch_missing_in_schedule_cron_jobs.rb +19 -0
  25. data/app/services/marty/enums/report.rb +18 -0
  26. data/app/services/marty/promises/delorean/create.rb +16 -27
  27. data/app/services/marty/promises/ruby/create.rb +10 -1
  28. data/app/views/layouts/marty/application.html.erb +4 -1
  29. data/app/views/marty/diagnostic/op.html.erb +3 -1
  30. data/config/locales/en.yml +2 -0
  31. data/db/migrate/512_add_promise_priority.rb +9 -0
  32. data/db/migrate/513_add_priority_to_promise_view.rb +44 -0
  33. data/db/migrate/514_remove_marty_events.rb +13 -0
  34. data/delorean/enum_report.dl +11 -0
  35. data/delorean/table_report.dl +7 -0
  36. data/docker-compose.dummy.yml +7 -4
  37. data/lib/marty.rb +5 -2
  38. data/lib/marty/api/base.rb +15 -9
  39. data/lib/marty/cache_adapters.rb +2 -0
  40. data/lib/marty/cache_adapters/mcfly_ruby_cache.rb +1 -5
  41. data/lib/marty/cache_adapters/memory_and_redis.rb +93 -0
  42. data/lib/marty/cache_adapters/redis.rb +63 -0
  43. data/lib/marty/delayed_job/scheduled_job_plugin.rb +33 -0
  44. data/lib/marty/diagnostic/database.rb +1 -2
  45. data/lib/marty/logger.rb +50 -17
  46. data/lib/marty/monkey.rb +26 -6
  47. data/lib/marty/promise_ruby_job.rb +2 -0
  48. data/lib/marty/rails_app.rb +29 -0
  49. data/lib/marty/railtie.rb +1 -0
  50. data/lib/marty/version.rb +1 -1
  51. data/marty.gemspec +1 -0
  52. data/spec/controllers/job_controller_spec.rb +2 -2
  53. data/spec/dummy/app/components/gemini/cm_auth_app.rb +12 -0
  54. data/spec/dummy/app/components/gemini/simple_view.rb +17 -0
  55. data/spec/dummy/app/jobs/test_failing_job.rb +14 -0
  56. data/spec/dummy/app/models/gemini/helper.rb +90 -1
  57. data/spec/dummy/config/application.rb +1 -0
  58. data/spec/dummy/db/migrate/20191101132729_add_activity_flag_to_simple.rb +10 -0
  59. data/spec/dummy/delorean/blame_report.dl +1 -0
  60. data/spec/dummy/delorean/enum_report.dl +1 -0
  61. data/spec/dummy/delorean/marty_fields.dl +1 -0
  62. data/spec/dummy/delorean/table_report.dl +1 -0
  63. data/spec/features/data_blame_report_spec.rb +66 -0
  64. data/spec/features/data_grid_spec.rb +1 -1
  65. data/spec/features/enum_values_report_spec.rb +76 -0
  66. data/spec/features/inline_editing_spec.rb +33 -0
  67. data/spec/features/rule_spec.rb +1 -1
  68. data/spec/features/schedule_jobs_dashboard_spec.rb +1 -1
  69. data/spec/features/scripting_spec.rb +1 -1
  70. data/spec/features/user_list_report_spec.rb +74 -0
  71. data/spec/fixtures/misc/struct_compare_tests.txt +15 -5
  72. data/spec/job_helper.rb +39 -0
  73. data/spec/jobs/cron_job_spec.rb +91 -0
  74. data/spec/lib/mcfly_model_spec.rb +9 -0
  75. data/spec/models/promise_spec.rb +168 -1
  76. data/spec/other/diagnostic/delayed_job_workers_spec.rb +1 -1
  77. data/spec/performance/caching_spec.rb +99 -0
  78. data/spec/services/background_job/fetch_missing_in_schedule_cron_jobs_spec.rb +34 -0
  79. data/spec/support/delayed_job_helpers.rb +3 -3
  80. data/spec/support/shared_connection.rb +9 -1
  81. data/spec/support/structure_compare.rb +19 -3
  82. metadata +39 -6
  83. data/app/components/marty/event_view.rb +0 -129
  84. data/app/models/marty/event.rb +0 -317
  85. data/spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb +0 -8
  86. data/spec/dummy/delorean/blame_report.dl +0 -268
  87. data/spec/dummy/delorean/marty_fields.dl +0 -63
  88. data/spec/dummy/delorean/table_report.dl +0 -34
  89. data/spec/models/event_spec.rb +0 -272
@@ -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"