marty 4.0.0.rc2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +7 -0
  3. data/.rubocop.yml +1 -0
  4. data/.rubocop_todo.yml +3 -16
  5. data/.ssh-docker/.keep +0 -0
  6. data/Dockerfile.dummy +3 -0
  7. data/Gemfile +19 -15
  8. data/app/components/marty/base_rule_view.rb +104 -10
  9. data/app/components/marty/base_rule_view/client/base_rule_view.js +24 -0
  10. data/app/components/marty/data_grid_user_view.rb +39 -0
  11. data/app/components/marty/data_grid_view.rb +68 -18
  12. data/app/components/marty/extras/layout.rb +1 -1
  13. data/app/components/marty/grid.rb +1 -1
  14. data/app/components/marty/grid/client/grid.js +29 -13
  15. data/app/components/marty/import_view.rb +3 -3
  16. data/app/components/marty/main_auth_app.rb +11 -1
  17. data/app/components/marty/report_form.rb +6 -6
  18. data/app/components/marty/script_form.rb +5 -5
  19. data/app/components/marty/script_tester.rb +2 -2
  20. data/app/components/marty/user_view.rb +3 -9
  21. data/app/models/marty/base_rule.rb +92 -32
  22. data/app/models/marty/data_grid.rb +92 -22
  23. data/app/models/marty/event.rb +2 -2
  24. data/app/models/marty/promise.rb +4 -4
  25. data/app/models/marty/role_type.rb +14 -1
  26. data/app/services/marty/data_grid_view/save_grid.rb +2 -2
  27. data/app/services/marty/promises/delorean/create.rb +2 -2
  28. data/app/services/marty/promises/ruby/create.rb +2 -2
  29. data/config/locales/en.yml +11 -2
  30. data/db/migrate/108_add_data_grid_perms.rb +16 -0
  31. data/db/migrate/508_add_not_to_data_grids_tables.rb +18 -0
  32. data/db/migrate/509_update_dg_plpgsql_v1_fns.rb +13 -0
  33. data/db/sql/query_grid_dir_v1.sql +16 -2
  34. data/docker-compose.dummy.yml +1 -0
  35. data/lib/marty/content_handler.rb +2 -2
  36. data/lib/marty/data_change.rb +1 -1
  37. data/lib/marty/data_conversion.rb +3 -4
  38. data/lib/marty/data_importer.rb +4 -4
  39. data/lib/marty/mcfly_model.rb +7 -10
  40. data/lib/marty/migrations.rb +1 -1
  41. data/lib/marty/monkey.rb +2 -2
  42. data/lib/marty/promise_job.rb +5 -5
  43. data/lib/marty/promise_proxy.rb +2 -2
  44. data/lib/marty/promise_ruby_job.rb +4 -4
  45. data/lib/marty/version.rb +1 -1
  46. data/make-app.mk +1 -1
  47. data/marty.gemspec +13 -18
  48. data/other/marty/diagnostic/aws/ec2_instance.rb +17 -2
  49. data/other/marty/diagnostic/aws/error.rb +8 -0
  50. data/other/marty/diagnostic/database.rb +2 -2
  51. data/other/marty/diagnostic/delayed_job_version.rb +0 -1
  52. data/spec/dummy/app/components/gemini/my_rule_view.rb +32 -6
  53. data/spec/dummy/app/models/gemini/fannie_bup.rb +13 -20
  54. data/spec/dummy/app/models/gemini/my_rule.rb +4 -0
  55. data/spec/dummy/app/models/gemini/xyz_rule.rb +3 -1
  56. data/spec/dummy/config/application.rb +1 -0
  57. data/spec/dummy/config/initializers/secret_token.rb +1 -1
  58. data/spec/dummy/db/migrate/20190702115241_add_simple_guards_options_to_rules.rb +37 -0
  59. data/spec/features/data_grid_spec.rb +109 -47
  60. data/spec/features/reporting_spec.rb +4 -4
  61. data/spec/features/rule_spec.rb +62 -31
  62. data/spec/features/scripting_spec.rb +3 -3
  63. data/spec/features/user_view_spec.rb +17 -8
  64. data/spec/fixtures/csv/rule/MyRule.csv +4 -1
  65. data/spec/lib/data_importer_spec.rb +8 -8
  66. data/spec/lib/mcfly_model_spec.rb +6 -6
  67. data/spec/models/data_grid_spec.rb +139 -7
  68. data/spec/models/rule_spec.rb +116 -9
  69. data/spec/spec_helper.rb +2 -2
  70. data/spec/support/netzke.rb +4 -3
  71. metadata +55 -54
  72. data/Gemfile.lock +0 -289
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 2e88119aa409a4b6e0b108e44f821c9ae6f3c402bde7a0b5982aaac317f8c0c0
4
- data.tar.gz: f0e0a25f744a13ed98e647aa379c2421a54edc1dd8bf01e84262e4f952ce7ae2
2
+ SHA1:
3
+ metadata.gz: c1dd27f462d7a2a7c78919c8388b35464b2a09db
4
+ data.tar.gz: a82eb24eb59673ae6a7c9f4c48a690b06dd7f984
5
5
  SHA512:
6
- metadata.gz: cbfd7f8b4c3fcc8f7060f7913bd2330635517a52710acae731638d6190f05726174fddfca0447bc0400d06770e10c72dd371e24e0da4f9e2699495473041a3eb
7
- data.tar.gz: e17fa1789a98f3f67fa324b2b67e7bcc1a98c48101d62088a1c1196e9d148f9275b8c3a1134a03ed7b6022d7757cfc737b2598bfc071cfd22df215ce62876293
6
+ metadata.gz: 36f9986869878ae91ed80ebb0d4698609ed8cc19507103d4437c3469db2f3fa32aef09b21333ee62fe92cfa2e3eafa041088e2d248c75561d6f8d1aeea5c0493
7
+ data.tar.gz: a9d59ed17f912a53da36a6292c2c729ce6b4daad127e621170a845b2df93faaad4c509c0e0a4e1a587f6786446bde7356127b0f7fdf1130f7f3027f9836d1c38
data/.gitignore CHANGED
@@ -31,3 +31,10 @@ spec/dummy/.sass-cache
31
31
  # Files with command history for docker
32
32
  .bash_history.docker
33
33
  .pry_history.docker
34
+
35
+ # Keep empty for for ssh keys in docker
36
+ /.ssh-docker/*
37
+ !/.ssh-docker/.keep
38
+
39
+ # Ignore Gemfile.lock
40
+ Gemfile.lock
@@ -1,4 +1,5 @@
1
1
  inherit_from: .rubocop_todo.yml
2
+ require: rubocop-performance
2
3
 
3
4
  AllCops:
4
5
  TargetRubyVersion: 2.3.3
@@ -48,7 +48,7 @@ Layout/ExtraSpacing:
48
48
  # Cop supports --auto-correct.
49
49
  # Configuration parameters: EnforcedStyle, IndentationWidth.
50
50
  # SupportedStyles: special_inside_parentheses, consistent, align_braces
51
- Layout/IndentHash:
51
+ Layout/IndentFirstHashElement:
52
52
  Exclude:
53
53
  - 'app/components/marty/extras/layout.rb'
54
54
  - 'lib/marty/data_change.rb'
@@ -316,7 +316,7 @@ Metrics/BlockLength:
316
316
  # Offense count: 21
317
317
  # Configuration parameters: CountComments.
318
318
  Metrics/ClassLength:
319
- Max: 539
319
+ Max: 600
320
320
 
321
321
  # Offense count: 68
322
322
  Metrics/CyclomaticComplexity:
@@ -508,7 +508,7 @@ Performance/TimesMap:
508
508
 
509
509
  # Offense count: 1
510
510
  # Cop supports --auto-correct.
511
- Performance/UnneededSort:
511
+ Style/UnneededSort:
512
512
  Exclude:
513
513
  - 'app/models/marty/event.rb'
514
514
 
@@ -934,19 +934,6 @@ Style/Next:
934
934
  Style/NumericLiterals:
935
935
  MinDigits: 15
936
936
 
937
- # Offense count: 10
938
- # Cop supports --auto-correct.
939
- # Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
940
- # SupportedStyles: predicate, comparison
941
- Style/NumericPredicate:
942
- Exclude:
943
- - 'spec/**/*'
944
- - 'app/components/marty/main_auth_app.rb'
945
- - 'lib/marty/content_handler.rb'
946
- - 'lib/marty/data_change.rb'
947
- - 'lib/marty/data_importer.rb'
948
- - 'lib/marty/xl.rb'
949
-
950
937
  # Offense count: 42
951
938
  # Cop supports --auto-correct.
952
939
  Style/ParallelAssignment:
File without changes
@@ -24,6 +24,9 @@ RUN curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrom
24
24
  && sed -i 's|HERE/chrome\"|HERE/chrome\" --disable-setuid-sandbox|g' /opt/google/chrome/google-chrome \
25
25
  && rm google-chrome.deb
26
26
 
27
+ # Install additional tools
28
+ RUN apt-get install -qq -y --no-install-recommends netcat-openbsd vim
29
+
27
30
  RUN gem install bundler
28
31
 
29
32
  ENV BUNDLE_PATH /bundle_box/bundle
data/Gemfile CHANGED
@@ -5,33 +5,37 @@ source 'http://rubygems.org'
5
5
  # development dependencies will be added by default to the :development group.
6
6
  gemspec
7
7
 
8
- gem 'daemons'
9
- gem 'delayed_job_active_record'
10
- gem 'pg'
11
- gem 'rails', '~> 5.1.4'
12
- gem 'sqlite3'
8
+ group :default do
9
+ gem 'daemons'
10
+ gem 'delayed_job_active_record'
11
+ gem 'pg'
12
+ gem 'rails'
13
+ end
14
+
15
+ group :default, :cmit do
16
+ gem 'delorean_lang'
17
+ gem 'mcfly'
18
+ # gem 'delorean_lang', path: File.expand_path('../../delorean', __FILE__)
19
+ # gem 'mcfly', path: File.expand_path('../../mcfly', __FILE__)
20
+ end
13
21
 
14
22
  group :development, :test do
15
- gem 'capybara', '~> 2.18.0'
23
+ gem 'capybara'
16
24
  gem 'connection_pool'
17
25
  gem 'database_cleaner'
26
+ gem 'fuubar', require: false
27
+ gem 'netzke', '6.5.0.0'
18
28
  gem 'pry-byebug'
19
29
  gem 'pry-rails'
30
+ gem 'puma'
20
31
  gem 'rails-controller-testing'
21
32
  gem 'rspec-instafail', require: false
22
33
  gem 'rspec-rails'
23
34
  gem 'rspec-retry'
24
35
  gem 'rubocop', require: false
36
+ gem 'rubocop-performance', require: false
37
+ gem 'rubocop-rails', require: false
25
38
  gem 'selenium-webdriver'
26
39
  gem 'timecop'
27
40
  gem 'webdrivers'
28
-
29
- # gem 'mcfly', path: File.expand_path('../../mcfly', __FILE__)
30
- gem 'mcfly'
31
- gem 'netzke', '6.5.0.0'
32
-
33
- # gem 'delorean_lang', path: File.expand_path('../../delorean', __FILE__)
34
-
35
- # gem 'marty_rspec', path: File.expand_path('../../marty_rspec', __FILE__)
36
- gem 'marty_rspec'
37
41
  end
@@ -22,16 +22,25 @@ class Marty::BaseRuleView < Marty::McflyGridPanel
22
22
  c.model = self.class.klass
23
23
  c.title = I18n.t('rule')
24
24
  c.attributes = self.class.base_fields +
25
- klass.guard_info.
26
- sort_by { |_, h| h[:order] || 0 }.
27
- reject { |_, h| h[:hidden] }.
28
- map { |name, _| name.to_sym } + self.class.computed_fields
25
+ guard_info_attributes + self.class.computed_fields
29
26
  c.store_config.merge!(sorters: [{ property: :name, direction: 'ASC' }])
30
27
  c.editing = :in_form
31
28
  c.paging = :pagination
32
29
  c.multi_select = false
33
30
  end
34
31
 
32
+ def guard_info_attributes
33
+ res = klass.guard_info.
34
+ sort_by { |_, h| h[:order] || 0 }.
35
+ reject { |_, h| h[:hidden] }.
36
+ map do |name, opts|
37
+ next name.to_sym unless opts.fetch(:allow_not, true)
38
+
39
+ [name.to_sym, "#{name}_not".to_sym]
40
+ end
41
+ res.flatten
42
+ end
43
+
35
44
  def default_bbar
36
45
  super + [:dup_in_form]
37
46
  end
@@ -222,7 +231,11 @@ class Marty::BaseRuleView < Marty::McflyGridPanel
222
231
  end
223
232
 
224
233
  def form_items_guards
225
- klass.guard_info.reject { |_, h| h[:hidden] }.keys.map(&:to_sym)
234
+ klass.guard_info.reject { |_, h| h[:hidden] }.map do |field, opts|
235
+ next { field: field.to_sym } unless opts.fetch(:allow_not, true)
236
+
237
+ { field: field.to_sym, not_field: "#{field.to_sym}_not".to_sym }
238
+ end
226
239
  end
227
240
 
228
241
  def form_items_grids
@@ -246,17 +259,56 @@ class Marty::BaseRuleView < Marty::McflyGridPanel
246
259
  height: 225)]
247
260
  end
248
261
 
262
+ def default_form_items_guards
263
+ with_not_fields = form_items_guards.any? { |h| h.key?(:not_field) }
264
+
265
+ guards = form_items_guards.map do |h|
266
+ if with_not_fields
267
+ hbox(
268
+ vbox(h.fetch(:field), width: '78%', border: false),
269
+ vbox(width: '2%', border: false),
270
+ vbox(h.fetch(:not_field, nil), width: '20%', border: false),
271
+ width: '100%',
272
+ border: false
273
+ )
274
+ else
275
+ hbox(
276
+ vbox(h.fetch(:field), width: '100%', border: false),
277
+ width: '100%',
278
+ border: false
279
+ )
280
+ end
281
+ end
282
+ end
283
+
249
284
  def default_form_items
250
285
  [
251
286
  hbox(
252
- vbox(*form_items_attrs +
253
- form_items_guards,
254
- border: false,
255
- width: '40%',
287
+ vbox(
288
+ hbox(
289
+ vbox(
290
+ *form_items_attrs,
291
+ width: '100%',
292
+ border: false
293
+ ),
294
+ width: '100%',
295
+ border: false
296
+ ),
297
+ hbox(
298
+ vbox(
299
+ *default_form_items_guards,
300
+ width: '100%',
301
+ border: false
256
302
  ),
303
+ width: '100%',
304
+ border: false
305
+ ),
306
+ width: '40%',
307
+ border: false
308
+ ),
257
309
  vbox(width: '2%', border: false),
258
310
  vbox(
259
- width: '55%', border: false),
311
+ width: '5%', border: false),
260
312
  height: '40%',
261
313
  border: false,
262
314
  ),
@@ -310,10 +362,42 @@ class Marty::BaseRuleView < Marty::McflyGridPanel
310
362
  else
311
363
  c.getter = range_getter(namestr, meth)
312
364
  c.setter = range_setter(namestr, meth)
365
+
313
366
  c.filterable = false
314
367
  end
368
+
369
+ c.column_config = {
370
+ renderer: 'simpleGuardColumnRenderer'
371
+ }
315
372
  c.sorting_scope = get_json_sorter(meth, namestr)
316
373
  end
374
+
375
+ # Checkbox and hidden column for "NOT" option for simpleguards
376
+ attribute "#{name}_not" do |c|
377
+ c.width = 30
378
+ c.label = 'Not'
379
+ c.type = :boolean
380
+ c.filterable = false
381
+ c.column_config = { hidden: true }
382
+
383
+ c.field_config = {
384
+ label_align: 'right',
385
+ max_width: 100,
386
+ label_pad: 2,
387
+ label_width: 30
388
+ }
389
+ c.getter = lambda do |record|
390
+ record.simple_guards_options.dig(name.to_s, 'not')
391
+ end
392
+
393
+ c.setter = lambda do |record, value|
394
+ options = record.simple_guards_options.fetch(name.to_s, {})
395
+ new_options = options.merge('not' => value)
396
+ record.simple_guards_options = record.simple_guards_options.merge(
397
+ name.to_s => new_options
398
+ )
399
+ end
400
+ end
317
401
  end
318
402
 
319
403
  attribute :start_dt do |c|
@@ -347,4 +431,14 @@ class Marty::BaseRuleView < Marty::McflyGridPanel
347
431
  end
348
432
  end
349
433
  end
434
+
435
+ client_class do |c|
436
+ c.myy_renderer = l(<<-JS)
437
+ function(value){
438
+ console.log(arguments)
439
+
440
+ return value ? "*" + value + "*" : "";
441
+ }
442
+ JS
443
+ end
350
444
  end
@@ -0,0 +1,24 @@
1
+ {
2
+ simpleGuardColumnRenderer: function(value, cell, obj) {
3
+ if (value === undefined || value === null) {
4
+ return value
5
+ }
6
+
7
+ if (!(cell && cell.column && cell.column.config && cell.column.config.name)) {
8
+ return value;
9
+ }
10
+
11
+ if (!(obj && obj.data)) {
12
+ return value;
13
+ }
14
+
15
+ column_name = cell.column.config.name;
16
+ with_not = obj.data[`${column_name}_not`];
17
+
18
+ if (with_not) {
19
+ return `NOT (${value})`
20
+ }
21
+
22
+ return value;
23
+ }
24
+ }
@@ -0,0 +1,39 @@
1
+ module Marty
2
+ class DataGridUserView < DataGridView
3
+ has_marty_permissions read: [:data_grid_editor, :admin, :dev]
4
+
5
+ def configure(c)
6
+ super
7
+
8
+ c.attributes =
9
+ [
10
+ :name,
11
+ :created_dt,
12
+ ]
13
+ c.editing = :inline
14
+ end
15
+
16
+ def default_bbar
17
+ [:edit_grid]
18
+ end
19
+
20
+ def get_records(params)
21
+ cur_perms = Mcfly.whodunnit.roles.map(&:to_sym)
22
+ model.where("permissions->'view' ?| ARRAY[:roles] OR "\
23
+ "permissions->'edit_data' ?| ARRAY[:roles] OR "\
24
+ "permissions->'edit_all' ?| ARRAY[:roles]",
25
+ roles: cur_perms).scoping do
26
+ super
27
+ end
28
+ end
29
+
30
+ def self.get_edit_permission(permissions)
31
+ cur_perms = current_user_roles.map(&:to_s)
32
+ ['edit_all', 'edit_data', 'view'].detect do |p|
33
+ permissions[p] - cur_perms != permissions[p]
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ DataGridUserView = Marty::DataGridUserView
@@ -98,6 +98,9 @@ module Marty; class DataGridView < McflyGridPanel
98
98
  :lenient,
99
99
  :data_type,
100
100
  :constraint,
101
+ :perm_view,
102
+ :perm_edit_data,
103
+ :perm_edit_all,
101
104
  :created_dt,
102
105
  ]
103
106
 
@@ -107,6 +110,19 @@ module Marty; class DataGridView < McflyGridPanel
107
110
  c.multi_select = false
108
111
  end
109
112
 
113
+ def set_perms(dg, data)
114
+ permstrs = %w[perm_view perm_edit_data perm_edit_all]
115
+ view, edit_data, edit_all = data.values_at(*permstrs).map do |plist|
116
+ Marty::RoleType.from_nice_names(plist)
117
+ end
118
+ dg.permissions = {
119
+ view: view.present? ? view : [],
120
+ edit_data: edit_data.present? ? edit_data : [],
121
+ edit_all: edit_all.present? ? edit_all : [],
122
+ }
123
+ dg.save!
124
+ end
125
+
110
126
  endpoint :add_window__add_form__submit do |params|
111
127
  data = ActiveSupport::JSON.decode(params[:data])
112
128
 
@@ -114,11 +130,12 @@ module Marty; class DataGridView < McflyGridPanel
114
130
  !config[:permissions][:create]
115
131
 
116
132
  begin
117
- DataGrid.create_from_import(data['name'], data['export'])
133
+ dg = DataGrid.create_from_import(data['name'], data['export'])
134
+ set_perms(dg, data)
118
135
  client.success = true
119
136
  client.netzke_on_submit_success
120
- rescue StandardError => exc
121
- client.netzke_notify(exc.to_s)
137
+ rescue StandardError => e
138
+ client.netzke_notify(e.to_s)
122
139
  end
123
140
  end
124
141
 
@@ -129,10 +146,11 @@ module Marty; class DataGridView < McflyGridPanel
129
146
 
130
147
  begin
131
148
  dg.update_from_import(data['name'], data['export'])
149
+ set_perms(dg, data)
132
150
  client.success = true
133
151
  client.netzke_on_submit_success
134
- rescue StandardError => exc
135
- client.netzke_notify(exc.to_s)
152
+ rescue StandardError => e
153
+ client.netzke_notify(e.to_s)
136
154
  end
137
155
  end
138
156
 
@@ -168,14 +186,8 @@ module Marty; class DataGridView < McflyGridPanel
168
186
  client.netzke_client_show_grid maxcount, res, 'Data Grid'
169
187
  end
170
188
 
171
- # placeholders for grid editing permission logic.
172
- # for now, this allows the rspec to control the permission
173
- def self.get_edit_edit_permission
174
- Marty::Config['grid_edit_edit_perm'] || 'edit_all'
175
- end
176
-
177
- def self.get_edit_save_permission
178
- Marty::Config['grid_edit_save_perm'] || 'edit_all'
189
+ def self.get_edit_permission(_permissions)
190
+ 'edit_all'
179
191
  end
180
192
 
181
193
  endpoint :edit_grid do |params|
@@ -193,9 +205,21 @@ module Marty; class DataGridView < McflyGridPanel
193
205
  vdim = md.map { |m| m['dir'] == 'v' && m['attr'] }.select { |v| v }
194
206
  hdim_en = hdim.map { |d| I18n.t('attributes.' + d, default: d) }
195
207
  vdim_en = vdim.map { |d| I18n.t('attributes.' + d, default: d) }
196
- name = "Editing Data Grid '#{dg.name}'"
197
- permission = Marty::DataGridView.get_edit_edit_permission
198
- client.edit_grid(record_id, hdim_en, vdim_en, res, name, permission)
208
+ perm = self.class.get_edit_permission(dg.permissions)
209
+ # should never happen
210
+ return client.netzke_notify('No permission to edit/view grid.') unless perm
211
+
212
+ doing = case perm
213
+ when 'view'
214
+ 'Viewing'
215
+ when 'edit_all'
216
+ 'Editing (all)'
217
+ when 'edit_data'
218
+ 'Editing (data only)'
219
+ end
220
+ name = "#{doing} Data Grid '#{dg.name}'"
221
+
222
+ client.edit_grid(record_id, hdim_en, vdim_en, res, name, perm)
199
223
  end
200
224
 
201
225
  endpoint :save_grid do |params|
@@ -213,10 +237,36 @@ module Marty; class DataGridView < McflyGridPanel
213
237
  def default_form_items
214
238
  [
215
239
  :name,
240
+ :perm_view, :perm_edit_data, :perm_edit_all,
216
241
  textarea_field(:export, height: 300, hide_label: true),
217
242
  ]
218
243
  end
219
244
 
245
+ ['view', 'edit_data', 'edit_all'].each do |p|
246
+ s = ('perm_' + p).to_s
247
+ attribute s do |c|
248
+ c.width = 100
249
+ c.flex = 1
250
+ c.label = I18n.t("data_grid_view_perms.#{s}")
251
+ c.type = :string
252
+ c.getter = lambda do |r|
253
+ Marty::RoleType.to_nice_names(r.permissions[p].sort)
254
+ end
255
+ store = Marty::RoleType.to_nice_names(::Marty::RoleType.get_all.sort.map)
256
+
257
+ # edit does not work without this dummy setter
258
+ c.setter = ->(r, v) {}
259
+
260
+ c.editor_config = {
261
+ multi_select: true,
262
+ empty_text: I18n.t('user_grid.select_roles'),
263
+ store: store,
264
+ type: :string,
265
+ xtype: :combo,
266
+ }
267
+ end
268
+ end
269
+
220
270
  component :edit_window do |c|
221
271
  super(c)
222
272
  c.width = 700
@@ -228,11 +278,11 @@ module Marty; class DataGridView < McflyGridPanel
228
278
  end
229
279
 
230
280
  attribute :name do |c|
231
- c.width = 120
281
+ c.width = 400
232
282
  end
233
283
 
234
284
  attribute :constraint do |c|
235
- c.width = 100
285
+ c.width = 150
236
286
  end
237
287
 
238
288
  attribute :hcols do |c|