marty 1.2.0 → 1.2.1

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/Gemfile.lock +2 -2
  4. data/app/components/marty/base_rule_view.rb +279 -0
  5. data/app/components/marty/delorean_rule_view.rb +26 -0
  6. data/app/components/marty/extras/layout.rb +22 -7
  7. data/app/components/marty/log_view.rb +1 -1
  8. data/app/components/marty/mcfly_grid_panel.rb +53 -0
  9. data/app/components/marty/mcfly_grid_panel/client/dup_in_form.js +20 -0
  10. data/app/models/marty/base_rule.rb +126 -0
  11. data/app/models/marty/delorean_rule.rb +121 -0
  12. data/lib/marty/data_importer.rb +0 -1
  13. data/lib/marty/rule_script_set.rb +176 -0
  14. data/lib/marty/version.rb +1 -1
  15. data/lib/tasks/marty_tasks.rake +42 -0
  16. data/spec/dummy/app/components/gemini/cm_auth_app.rb +18 -0
  17. data/spec/dummy/app/components/gemini/my_rule_view.rb +63 -0
  18. data/spec/dummy/app/components/gemini/xyz_rule_view.rb +25 -0
  19. data/spec/dummy/app/models/gemini/guard_one.rb +5 -0
  20. data/spec/dummy/app/models/gemini/guard_two.rb +5 -0
  21. data/spec/dummy/app/models/gemini/my_rule.rb +46 -0
  22. data/spec/dummy/app/models/gemini/my_rule_type.rb +5 -0
  23. data/spec/dummy/app/models/gemini/xyz_enum.rb +5 -0
  24. data/spec/dummy/app/models/gemini/xyz_rule.rb +63 -0
  25. data/spec/dummy/app/models/gemini/xyz_rule_type.rb +5 -0
  26. data/spec/dummy/config/locales/en.yml +10 -0
  27. data/spec/dummy/db/migrate/20171220150101_add_rule_type_enums.rb +14 -0
  28. data/spec/dummy/db/migrate/20171221095312_create_gemini_my_rules.rb +22 -0
  29. data/spec/dummy/db/migrate/20171221095359_create_gemini_xyz_rules.rb +21 -0
  30. data/spec/dummy/db/migrate/20171222150100_add_rule_indices.rb +34 -0
  31. data/spec/dummy/db/seeds.rb +1 -1
  32. data/spec/dummy/delorean/base_code.dl +6 -0
  33. data/spec/dummy/lib/gemini/my_rule_script_set.rb +13 -0
  34. data/spec/dummy/lib/gemini/xyz_rule_script_set.rb +22 -0
  35. data/spec/features/rule_spec.rb +265 -0
  36. data/spec/fixtures/csv/rule/DataGrid.csv +6 -0
  37. data/spec/fixtures/csv/rule/MyRule.csv +14 -0
  38. data/spec/fixtures/csv/rule/XyzRule.csv +6 -0
  39. data/spec/models/rule_spec.rb +322 -0
  40. data/spec/support/integration_helpers.rb +1 -0
  41. metadata +29 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5462fab5388f7bad639810faa08b6e3fc2958153
4
- data.tar.gz: ada66e1090a390c88f62e7ec4667bc31be8e49d8
3
+ metadata.gz: f72e47b8af214afae1185098fad62b86cb4c740c
4
+ data.tar.gz: f23fdf9fb57e514b3d67340abd050b59b7c1b8ec
5
5
  SHA512:
6
- metadata.gz: 2b42c74d0222cdaba7b04f02035eeed7a892c4dd7b0eb8c49abf713f90f870c9c5159aa61e405b5a9445e218223b4474a574e19febafda665071a87c3cf11cae
7
- data.tar.gz: 686efe822c601622d5272fc5d6fcd9ebeee9b0887bb469f5e82b7f501786995767998e6ad56c7edae2579333eef7cfe5c8a93c6061874f07d8d83e735ba50561
6
+ metadata.gz: 35b2e57e4e67136307490955a47ecdd8ad4a8b45c0d4752492a4b144a9a3764a51c7f7f48fbf844e9ddeb7b92364b3d38d86c30fe3990e60fc27627556ca1612
7
+ data.tar.gz: bd9300147c167ac3e3934666c8249cf80f2c35e20ec67723d0a70311c6e1cc64e2f20a5bb5ad5931b4ee43876e8cad9d8dcde4f8c1bfbaf7ddc80884c96ec85e
data/Gemfile CHANGED
@@ -27,5 +27,6 @@ group :development, :test do
27
27
  gem 'netzke-testing'
28
28
  gem 'rspec-instafail', require: false
29
29
 
30
- gem 'marty_rspec'
30
+ # gem 'marty_rspec', path: File.expand_path('../../marty_rspec', __FILE__)
31
+ gem 'marty_rspec'
31
32
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- marty (1.2.0)
4
+ marty (1.2.1)
5
5
  axlsx (= 2.1.0pre)
6
6
  coderay
7
7
  delorean_lang (~> 0.3.33)
@@ -83,7 +83,7 @@ GEM
83
83
  delayed_job_active_record (4.1.2)
84
84
  activerecord (>= 3.0, < 5.2)
85
85
  delayed_job (>= 3.0, < 5)
86
- delorean_lang (0.3.34)
86
+ delorean_lang (0.3.33)
87
87
  activerecord (>= 3.2)
88
88
  treetop (~> 1.5)
89
89
  diff-lcs (1.3)
@@ -0,0 +1,279 @@
1
+ class Marty::BaseRuleView < Marty::McflyGridPanel
2
+ include Marty::Extras::Layout
3
+
4
+ def self.klass
5
+ Marty::BaseRule
6
+ end
7
+ def klass
8
+ self.class.klass
9
+ end
10
+
11
+ def self.base_fields
12
+ [:name]
13
+ end
14
+ def self.computed_fields
15
+ [:computed_guards, :grids, :results]
16
+ end
17
+ def configure(c)
18
+ super
19
+ c.model = self.class.klass
20
+ c.title = I18n.t('rule')
21
+ c.attributes = self.class.base_fields +
22
+ klass.guard_info.
23
+ sort_by{|_, h| h[:order] || 0}.
24
+ reject{|_, h| h[:hidden]}.
25
+ map { |name, _| name.to_sym } + self.class.computed_fields
26
+ c.store_config.merge!(sorters: [{property: :name, direction: 'ASC'}])
27
+ c.editing = :in_form
28
+ c.paging = :pagination
29
+ c.multi_select = false
30
+ end
31
+
32
+ def default_bbar
33
+ super + [:dup_in_form]
34
+ end
35
+
36
+ def default_context_menu
37
+ []
38
+ end
39
+
40
+ class DupKeyError < StandardError
41
+ def initialize(key, lineno)
42
+ @key = key
43
+ @lineno = lineno
44
+ end
45
+ def message
46
+ "keyword '#{@key}' specified more than once (line #{@lineno})"
47
+ end
48
+ end
49
+
50
+ def self.simple_to_hashstr(s)
51
+ pairs = []
52
+ keys = Set.new
53
+ s.lines.each.with_index(1) do |line, idx|
54
+ next if /\A\s*\z/.match(line)
55
+ line.chomp!
56
+ begin
57
+ m = /\A\s*([a-z0-9][a-z0-9_]*)\s*=\s*(.*)\s*\z/.match(line)
58
+ k, v = m[1], m[2]
59
+ v = [v].to_json[1..-2]
60
+ raise DupKeyError.new(k, idx) if keys.include?(k)
61
+ raise unless /\A['"].*['"]\z/.match(v)
62
+ keys << k
63
+ rescue DupKeyError => e
64
+ raise
65
+ rescue => e
66
+ raise "syntax error on line #{idx}"
67
+ end
68
+ pairs << [k, v]
69
+ end
70
+
71
+ kvs = pairs.map { |k, v| %Q("#{k}":#{v}) }.join(",")
72
+ "{#{kvs}}"
73
+ end
74
+
75
+ def self.hash_to_simple(h)
76
+ return unless h && h.present?
77
+ fmt = '%-' + h.keys.map(&:length).max.to_s + 's = %s'
78
+ h.map do |k, vstr|
79
+ fmt % [k, vstr]
80
+ end.join("\n") || ''
81
+ end
82
+
83
+ def jsonb_getter(c)
84
+ lambda { |r| md = r.send(c); md.present? && md.to_json || '' }
85
+ end
86
+
87
+ def jsonb_simple_getter(c)
88
+ lambda {|r| Marty::BaseRuleView.hash_to_simple(r.send(c)) }
89
+ end
90
+
91
+ def jsonb_simple_setter(c)
92
+ msg = "#{c}="
93
+ lambda { |r, v|
94
+ return r.send(msg, nil) if v.blank?
95
+
96
+ begin
97
+ v = ActiveSupport::JSON.decode(
98
+ Marty::BaseRuleView.simple_to_hashstr(v))
99
+ rescue => e
100
+ v = { "~~ERROR~~": e.message }
101
+ end
102
+ r.send(msg, v)
103
+ }
104
+ end
105
+
106
+ def self.jsonb_field_getter(j, c)
107
+ lambda { |r| r.send(j)[c]||"" }
108
+ end
109
+
110
+ def self.jsonb_field_setter(j, c)
111
+ lambda do |r, v|
112
+ v.blank? || v == '---' ? r.send(j).delete(c) : r.send(j)[c] = v
113
+ end
114
+ end
115
+
116
+ def json_sort_scope(c)
117
+ lambda { |r, dir| r.order("#{c}::text " + dir.to_s) }
118
+ end
119
+
120
+ component :add_window do |c|
121
+ super(c)
122
+ c.width = 1500
123
+ c.height = 740
124
+ end
125
+
126
+ component :edit_window do |c|
127
+ super(c)
128
+ c.width = 1500
129
+ c.height = 740
130
+ end
131
+
132
+ attribute :name do |c|
133
+ c.width = 150
134
+ end
135
+
136
+ def self.grid_column(c, label=nil)
137
+ editor_config = {
138
+ trigger_action: :all,
139
+ xtype: :combo,
140
+ store: Marty::DataGrid.where(obsoleted_dt: 'infinity').
141
+ pluck(:name) + ['---'],
142
+ forceSelection: true,
143
+ }
144
+ {
145
+ name: label || c,
146
+ width: 200,
147
+ column_config: { editor: editor_config },
148
+ field_config: editor_config,
149
+ type: :string,
150
+ getter: jsonb_field_getter(:grids, c.to_s),
151
+ setter: jsonb_field_setter(:grids, c.to_s),
152
+ # getter: lambda { |r| r.grids[c.to_s] },
153
+ # setter: lambda { |r, v| r.grids[c.to_s] = v },
154
+ }
155
+ end
156
+
157
+ def form_items_attrs
158
+ self.class.base_fields
159
+ end
160
+
161
+ def form_items_guards
162
+ klass.guard_info.reject{|_, h| h[:hidden]}.keys.map{|x|x.to_sym}
163
+ end
164
+
165
+ def form_items_grids
166
+ [jsonb_field(:grids,
167
+ getter: jsonb_simple_getter(:grids),
168
+ setter: jsonb_simple_setter(:grids),
169
+ height: 75)]
170
+ end
171
+
172
+ def form_items_computed_guards
173
+ [jsonb_field(:computed_guards,
174
+ getter: jsonb_simple_getter(:computed_guards),
175
+ setter: jsonb_simple_setter(:computed_guards),
176
+ height: 150)]
177
+ end
178
+
179
+ def form_items_results
180
+ [jsonb_field(:results,
181
+ getter: jsonb_simple_getter(:results),
182
+ setter: jsonb_simple_setter(:results),
183
+ height: 150)]
184
+ end
185
+
186
+ def default_form_items
187
+ [
188
+ hbox(
189
+ vbox(*form_items_attrs +
190
+ form_items_guards,
191
+ border: false,
192
+ width: "40%",
193
+ ),
194
+ vbox(width: '2%', border: false),
195
+ vbox(
196
+ width: '55%', border: false),
197
+ height: '40%',
198
+ border: false,
199
+ ),
200
+ hbox(
201
+ vbox(*form_items_computed_guards +
202
+ form_items_grids +
203
+ form_items_results,
204
+ width: '99%',
205
+ border: false
206
+ ),
207
+ height: '40%',
208
+ border: false
209
+ )
210
+ ]
211
+ end
212
+
213
+ def self.field_maker(namestr, h, meth)
214
+ name = namestr.to_sym
215
+ attribute name do |c|
216
+ c.width = h[:width] || 150
217
+ case h[:type]
218
+ when :datetime; c.format = 'Y-m-d H:i'
219
+ when :date; c.format = 'Y-m-d'
220
+ else c.type = h[:type] || :string
221
+ end
222
+ c.label = h[:label] if h[:label]
223
+ if h[:enum]
224
+ if h[:multi]
225
+ enum_array(c, h[:enum])
226
+ else
227
+ enum_column(c, h[:enum])
228
+ end
229
+ end
230
+ # for some unexplained reason the getter/setter need the full
231
+ # class qualification
232
+ if h[:type] != :range
233
+ c.getter = Marty::DeloreanRuleView.jsonb_field_getter(meth, namestr)
234
+ c.setter = Marty::DeloreanRuleView.jsonb_field_setter(meth, namestr)
235
+ c.filter_with = lambda do |rel, value, op|
236
+ v = ActiveRecord::Base.connection.quote(value)[1..-2]
237
+ rel.where("#{meth}->>'#{namestr}' like '%#{v}%'")
238
+ end
239
+ else
240
+ c.getter = range_getter(namestr, meth)
241
+ c.setter = range_setter(namestr, meth)
242
+ c.filterable = false
243
+ end
244
+ c.sorting_scope = get_json_sorter(meth, namestr)
245
+ end
246
+ end
247
+
248
+ attribute :start_dt do |c|
249
+ c.width = 100
250
+ c.format = 'Y-m-d H:i'
251
+ end
252
+
253
+ attribute :end_dt do |c|
254
+ c.width = 100
255
+ c.format = 'Y-m-d H:i'
256
+ end
257
+
258
+ attribute :rule_type do |c|
259
+ c.width = 200
260
+ end
261
+
262
+ def self.init_fields
263
+ klass.guard_info.each do |namestr, h|
264
+ field_maker(namestr, h, :simple_guards)
265
+ end
266
+ end
267
+
268
+ computed_fields.each do |a|
269
+ column a do |c|
270
+ c.flex = 1
271
+ c.getter = jsonb_getter(a.to_s)
272
+ c.sorting_scope = json_sort_scope(a)
273
+ c.filter_with = lambda do |rel, value, op|
274
+ v = ActiveRecord::Base.connection.quote(value)[1..-2]
275
+ rel.where("#{a}::text like '%#{v}%'")
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,26 @@
1
+ class Marty::DeloreanRuleView < Marty::BaseRuleView
2
+ include Marty::Extras::Layout
3
+
4
+ def self.klass
5
+ Marty::DeloreanRule
6
+ end
7
+
8
+ def self.base_fields
9
+ super + [:rule_type, :start_dt, :end_dt]
10
+ end
11
+
12
+ attribute :start_dt do |c|
13
+ c.width = 150
14
+ c.format = 'Y-m-d H:i'
15
+ end
16
+
17
+ attribute :end_dt do |c|
18
+ c.width = 150
19
+ c.format = 'Y-m-d H:i'
20
+ end
21
+
22
+ attribute :rule_type do |c|
23
+ c.width = 200
24
+ end
25
+
26
+ end
@@ -154,15 +154,30 @@ module Layout
154
154
  ######################################################################
155
155
  # make sure to validate range vals on the model (e.g. see rule.rb)
156
156
 
157
- def range_getter(name)
158
- lambda { |r|
159
- Marty::Util.pg_range_to_human(r.send(name))
160
- }
157
+ def range_getter(name, json_field=nil)
158
+ if json_field
159
+ lambda { |r| Marty::Util.pg_range_to_human(r.send(json_field)[name]) }
160
+ else
161
+ lambda { |r| Marty::Util.pg_range_to_human(r.send(name)) }
162
+ end
161
163
  end
162
164
 
163
- def range_setter(name)
164
- lambda do |r, v|
165
- r.send("#{name}=", v && (Marty::Util.human_to_pg_range(v) rescue v))
165
+ def range_setter(name, json_field=nil)
166
+ if json_field
167
+ lambda do |r, v|
168
+ cookedv = v && v.present? && (Marty::Util.human_to_pg_range(v) rescue v)
169
+ h = r.send(json_field)
170
+ if cookedv
171
+ r.send("#{json_field}=", h + {name=>cookedv})
172
+ else
173
+ h.delete(name)
174
+ r.send("#{json_field}=", h)
175
+ end
176
+ end
177
+ else
178
+ lambda do |r, v|
179
+ r.send("#{name}=", v && (Marty::Util.human_to_pg_range(v) rescue v))
180
+ end
166
181
  end
167
182
  end
168
183
 
@@ -81,7 +81,7 @@ class Marty::LogView < Marty::Grid
81
81
  c.text = I18n.t("log_grid.details")
82
82
  c.width = 900
83
83
  c.read_only = true
84
- c.getter = lambda { |r| r.details.to_s}
84
+ c.getter = lambda { |r| r.details.pretty_inspect}
85
85
  end
86
86
  end
87
87
 
@@ -43,6 +43,59 @@ class Marty::McflyGridPanel < Marty::Grid
43
43
  end
44
44
  end
45
45
 
46
+ client_class do |c|
47
+ c.init_component = l(<<-JS)
48
+ function() {
49
+ this.callParent();
50
+
51
+ // dup is a non standard button, so we have to explicitly manage
52
+ // its clickability
53
+ this.getSelectionModel().on('selectionchange', function(selModel) {
54
+ this.actions.dupInForm &&
55
+ this.actions.dupInForm.setDisabled(!selModel.hasSelection() ||
56
+ !this.permissions.create);
57
+ }, this);
58
+ }
59
+ JS
60
+ end
61
+
62
+ client_class do |c|
63
+ c.include :dup_in_form
64
+ end
65
+
66
+ action :dup_in_form do |a|
67
+ a.hidden = !config[:permissions][:create]
68
+ a.icon = :page_copy
69
+ a.disabled = true
70
+ end
71
+
72
+ # edit-in-form submit with dup support
73
+ endpoint :edit_window__edit_form__submit do |params|
74
+
75
+ if params["dup"]
76
+ # FIXME: copied from basepack grid endpoint
77
+ # :add_window__add_form__netzke_submit
78
+
79
+ params[:data] = ActiveSupport::JSON.
80
+ decode(params[:data]).merge(id: nil).to_json
81
+
82
+ client.merge!(component_instance(:add_window).
83
+ component_instance(:add_form).
84
+ invoke_endpoint(:submit, [params]))
85
+
86
+ on_data_changed if client.netzke_set_form_values.present?
87
+ client.delete(:netzke_set_form_values)
88
+ else
89
+ # FIXME: copied from basepack grid endpoint
90
+ # :edit_window__edit_form__netzke_submit
91
+ client.merge!(component_instance(:edit_window).
92
+ component_instance(:edit_form).
93
+ invoke_endpoint(:submit, [params]))
94
+ on_data_changed if client.netzke_set_form_values.present?
95
+ client.delete(:netzke_set_form_values)
96
+ end
97
+ end
98
+
46
99
  private
47
100
  def self.mcfly_scope(sort_column)
48
101
  lambda { |r|