marty 4.0.0.rc2 → 5.1.0
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 +5 -5
- data/.gitignore +7 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +3 -16
- data/.ssh-docker/.keep +0 -0
- data/Dockerfile.dummy +3 -0
- data/Gemfile +19 -15
- data/app/components/marty/base_rule_view.rb +104 -10
- data/app/components/marty/base_rule_view/client/base_rule_view.js +24 -0
- data/app/components/marty/data_grid_user_view.rb +39 -0
- data/app/components/marty/data_grid_view.rb +68 -18
- data/app/components/marty/extras/layout.rb +1 -1
- data/app/components/marty/grid.rb +1 -1
- data/app/components/marty/grid/client/grid.js +29 -13
- data/app/components/marty/import_view.rb +3 -3
- data/app/components/marty/main_auth_app.rb +11 -1
- data/app/components/marty/report_form.rb +6 -6
- data/app/components/marty/script_form.rb +5 -5
- data/app/components/marty/script_tester.rb +2 -2
- data/app/components/marty/user_view.rb +3 -9
- data/app/models/marty/base_rule.rb +92 -32
- data/app/models/marty/data_grid.rb +92 -22
- data/app/models/marty/event.rb +2 -2
- data/app/models/marty/promise.rb +4 -4
- data/app/models/marty/role_type.rb +14 -1
- data/app/services/marty/data_grid_view/save_grid.rb +2 -2
- data/app/services/marty/promises/delorean/create.rb +2 -2
- data/app/services/marty/promises/ruby/create.rb +2 -2
- data/config/locales/en.yml +11 -2
- data/db/migrate/108_add_data_grid_perms.rb +16 -0
- data/db/migrate/508_add_not_to_data_grids_tables.rb +18 -0
- data/db/migrate/509_update_dg_plpgsql_v1_fns.rb +13 -0
- data/db/sql/query_grid_dir_v1.sql +16 -2
- data/docker-compose.dummy.yml +1 -0
- data/lib/marty/content_handler.rb +2 -2
- data/lib/marty/data_change.rb +1 -1
- data/lib/marty/data_conversion.rb +3 -4
- data/lib/marty/data_importer.rb +4 -4
- data/lib/marty/mcfly_model.rb +7 -10
- data/lib/marty/migrations.rb +1 -1
- data/lib/marty/monkey.rb +2 -2
- data/lib/marty/promise_job.rb +5 -5
- data/lib/marty/promise_proxy.rb +2 -2
- data/lib/marty/promise_ruby_job.rb +4 -4
- data/lib/marty/version.rb +1 -1
- data/make-app.mk +1 -1
- data/marty.gemspec +13 -18
- data/other/marty/diagnostic/aws/ec2_instance.rb +17 -2
- data/other/marty/diagnostic/aws/error.rb +8 -0
- data/other/marty/diagnostic/database.rb +2 -2
- data/other/marty/diagnostic/delayed_job_version.rb +0 -1
- data/spec/dummy/app/components/gemini/my_rule_view.rb +32 -6
- data/spec/dummy/app/models/gemini/fannie_bup.rb +13 -20
- data/spec/dummy/app/models/gemini/my_rule.rb +4 -0
- data/spec/dummy/app/models/gemini/xyz_rule.rb +3 -1
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/config/initializers/secret_token.rb +1 -1
- data/spec/dummy/db/migrate/20190702115241_add_simple_guards_options_to_rules.rb +37 -0
- data/spec/features/data_grid_spec.rb +109 -47
- data/spec/features/reporting_spec.rb +4 -4
- data/spec/features/rule_spec.rb +62 -31
- data/spec/features/scripting_spec.rb +3 -3
- data/spec/features/user_view_spec.rb +17 -8
- data/spec/fixtures/csv/rule/MyRule.csv +4 -1
- data/spec/lib/data_importer_spec.rb +8 -8
- data/spec/lib/mcfly_model_spec.rb +6 -6
- data/spec/models/data_grid_spec.rb +139 -7
- data/spec/models/rule_spec.rb +116 -9
- data/spec/spec_helper.rb +2 -2
- data/spec/support/netzke.rb +4 -3
- metadata +55 -54
- data/Gemfile.lock +0 -289
@@ -115,7 +115,7 @@ module Layout
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def get_sorter(col)
|
118
|
-
lambda { |rel, dir| rel.order("#{col}::text #{dir
|
118
|
+
lambda { |rel, dir| rel.order(Arel.sql("#{col}::text #{dir}")) }
|
119
119
|
end
|
120
120
|
|
121
121
|
######################################################################
|
@@ -1,13 +1,13 @@
|
|
1
1
|
{
|
2
|
-
getComponent: function
|
2
|
+
getComponent: function(name) {
|
3
3
|
return Ext.getCmp(name);
|
4
4
|
},
|
5
5
|
|
6
|
-
findComponent: function
|
6
|
+
findComponent: function(name) {
|
7
7
|
return Ext.ComponentQuery.query(`[name=${name}]`)[0];
|
8
8
|
},
|
9
9
|
|
10
|
-
setDisableComponentActions: function
|
10
|
+
setDisableComponentActions: function(prefix, flag) {
|
11
11
|
for (var key in this.actions) {
|
12
12
|
if (key.substring(0, prefix.length) == prefix) {
|
13
13
|
this.actions[key].setDisabled(flag);
|
@@ -15,7 +15,7 @@
|
|
15
15
|
}
|
16
16
|
},
|
17
17
|
|
18
|
-
initComponent: function
|
18
|
+
initComponent: function() {
|
19
19
|
this.dockedItems = this.dockedItems || [];
|
20
20
|
if (this.paging == 'pagination') {
|
21
21
|
this.dockedItems.push({
|
@@ -57,7 +57,7 @@
|
|
57
57
|
|
58
58
|
var children = me.serverConfig.child_components || [];
|
59
59
|
me.onSelectionChange(
|
60
|
-
function
|
60
|
+
function(m) {
|
61
61
|
var has_sel = m.hasSelection();
|
62
62
|
|
63
63
|
var rid = null;
|
@@ -92,7 +92,7 @@
|
|
92
92
|
var store = me.getStore();
|
93
93
|
var linked = me.serverConfig.linked_components || [];
|
94
94
|
for (var event of ['update', 'netzkerefresh']) {
|
95
|
-
store.on(event, function
|
95
|
+
store.on(event, function() {
|
96
96
|
for (var link of linked) {
|
97
97
|
var comp = me.findComponent(link);
|
98
98
|
if (comp && comp.reload) {
|
@@ -103,19 +103,30 @@
|
|
103
103
|
}
|
104
104
|
},
|
105
105
|
|
106
|
-
onSelectionChange: function
|
106
|
+
onSelectionChange: function(f) {
|
107
107
|
var me = this;
|
108
108
|
me.getSelectionModel().on('selectionchange', f);
|
109
109
|
},
|
110
110
|
|
111
|
-
|
111
|
+
// override netzkeReloadStore to allow option passthrough
|
112
|
+
// reference: http://api.netzke.org/client/files/doc_client_netzke-basepack_javascripts_grid_event_handlers.js.html
|
113
|
+
netzkeReloadStore: function(opts = {}) {
|
114
|
+
var store = this.getStore();
|
115
|
+
|
116
|
+
// HACK to work around buffered store's buggy reload()
|
117
|
+
if (!store.lastRequestStart) {
|
118
|
+
store.load(opts);
|
119
|
+
} else store.reload(opts);
|
120
|
+
},
|
121
|
+
|
122
|
+
doViewInForm: function(record) {
|
112
123
|
this.netzkeLoadComponent("view_window", {
|
113
124
|
serverConfig: {
|
114
125
|
record_id: record.id
|
115
126
|
},
|
116
|
-
callback: function
|
127
|
+
callback: function(w) {
|
117
128
|
w.show();
|
118
|
-
w.on('close', function
|
129
|
+
w.on('close', function() {
|
119
130
|
if (w.closeRes === "ok") {
|
120
131
|
this.netzkeReloadStore();
|
121
132
|
}
|
@@ -124,11 +135,16 @@
|
|
124
135
|
});
|
125
136
|
},
|
126
137
|
|
127
|
-
|
138
|
+
// always reset store to first page on reload
|
139
|
+
// to avoid load bug when moving from a higher page count
|
140
|
+
// to a grid with a lower page count
|
141
|
+
reload: function(opts = {
|
142
|
+
start: 0
|
143
|
+
}) {
|
128
144
|
this.netzkeReloadStore(opts);
|
129
145
|
},
|
130
146
|
|
131
|
-
reloadAll: function
|
147
|
+
reloadAll: function() {
|
132
148
|
var me = this;
|
133
149
|
var children = me.serverConfig.child_components || [];
|
134
150
|
this.store.reload();
|
@@ -140,7 +156,7 @@
|
|
140
156
|
}
|
141
157
|
},
|
142
158
|
|
143
|
-
clearFilters: function
|
159
|
+
clearFilters: function() {
|
144
160
|
this.filters.clearFilters();
|
145
161
|
},
|
146
162
|
}
|
@@ -84,10 +84,10 @@ class Marty::ImportView < Marty::Form
|
|
84
84
|
result << messages if messages
|
85
85
|
|
86
86
|
client.set_result result.join('<br/>')
|
87
|
-
rescue Marty::DataImporter::Error =>
|
87
|
+
rescue Marty::DataImporter::Error => e
|
88
88
|
result = [
|
89
|
-
"Import failed on line(s): #{
|
90
|
-
"Error: #{
|
89
|
+
"Import failed on line(s): #{e.lines.join(', ')}",
|
90
|
+
"Error: #{e.to_s}",
|
91
91
|
]
|
92
92
|
|
93
93
|
client.set_result '<font color="red">' + result.join('<br/>') + '</font>'
|
@@ -4,6 +4,7 @@ require 'marty/api_log_view'
|
|
4
4
|
require 'marty/config_view'
|
5
5
|
require 'marty/data_grid_view'
|
6
6
|
require 'marty/schedule_jobs_dashboard'
|
7
|
+
require 'marty/data_grid_user_view'
|
7
8
|
require 'marty/event_view'
|
8
9
|
require 'marty/import_type_view'
|
9
10
|
require 'marty/new_posting_window'
|
@@ -96,6 +97,7 @@ class Marty::MainAuthApp < Marty::AuthApp
|
|
96
97
|
icon_cls: 'fa fa-window-restore glyph',
|
97
98
|
menu: [
|
98
99
|
:data_grid_view,
|
100
|
+
:data_grid_user_view,
|
99
101
|
:reporting,
|
100
102
|
:scripting,
|
101
103
|
:promise_view,
|
@@ -237,7 +239,14 @@ class Marty::MainAuthApp < Marty::AuthApp
|
|
237
239
|
end
|
238
240
|
|
239
241
|
action :data_grid_view do |a|
|
240
|
-
a.text = I18n.t('data_grid_view'
|
242
|
+
a.text = I18n.t('data_grid_view')
|
243
|
+
a.handler = :netzke_load_component_by_action
|
244
|
+
a.icon_cls = 'fa fa-table glyph'
|
245
|
+
a.disabled = !self.class.has_any_perm?
|
246
|
+
end
|
247
|
+
|
248
|
+
action :data_grid_user_view do |a|
|
249
|
+
a.text = I18n.t('data_grid_user_view')
|
241
250
|
a.handler = :netzke_load_component_by_action
|
242
251
|
a.icon_cls = 'fa fa-table glyph'
|
243
252
|
a.disabled = !self.class.has_any_perm?
|
@@ -388,6 +397,7 @@ class Marty::MainAuthApp < Marty::AuthApp
|
|
388
397
|
component :config_view
|
389
398
|
|
390
399
|
component :data_grid_view
|
400
|
+
component :data_grid_user_view
|
391
401
|
|
392
402
|
component :event_view
|
393
403
|
|
@@ -61,10 +61,10 @@ class Marty::ReportForm < Marty::Form
|
|
61
61
|
|
62
62
|
begin
|
63
63
|
engine.evaluate(node, 'result', d_params)
|
64
|
-
rescue StandardError =>
|
65
|
-
Marty::Util.logger.error "run_eval failed: #{
|
64
|
+
rescue StandardError => e
|
65
|
+
Marty::Util.logger.error "run_eval failed: #{e.backtrace}"
|
66
66
|
|
67
|
-
res = Delorean::Engine.grok_runtime_exception(
|
67
|
+
res = Delorean::Engine.grok_runtime_exception(e)
|
68
68
|
res['backtrace'] =
|
69
69
|
res['backtrace'].map { |m, line, fn| "#{m}:#{line} #{fn}" }.join('\n')
|
70
70
|
res
|
@@ -156,15 +156,15 @@ class Marty::ReportForm < Marty::Form
|
|
156
156
|
raise 'bad form items' unless items.is_a?(Array)
|
157
157
|
raise 'bad format' unless
|
158
158
|
Marty::ContentHandler::GEN_FORMATS.member?(format)
|
159
|
-
rescue StandardError =>
|
159
|
+
rescue StandardError => e
|
160
160
|
c.title = 'ERROR'
|
161
161
|
c.items =
|
162
162
|
[
|
163
163
|
{
|
164
|
-
field_label: '
|
164
|
+
field_label: 'exception',
|
165
165
|
xtype: :displayfield,
|
166
166
|
name: 'displayfield1',
|
167
|
-
value: "<span style=\"color:red;\">#{
|
167
|
+
value: "<span style=\"color:red;\">#{e}</span>"
|
168
168
|
},
|
169
169
|
]
|
170
170
|
return
|
@@ -83,10 +83,10 @@ class Marty::ScriptForm < Marty::Form
|
|
83
83
|
begin
|
84
84
|
dev = Marty::Tag.find_by_name('DEV')
|
85
85
|
Marty::ScriptSet.new(dev).parse_check(script.name, data['body'])
|
86
|
-
rescue Delorean::ParseError =>
|
87
|
-
client.netzke_notify
|
86
|
+
rescue Delorean::ParseError => e
|
87
|
+
client.netzke_notify e.message
|
88
88
|
client.netzke_apply_form_errors({})
|
89
|
-
client.set_line_error(
|
89
|
+
client.set_line_error(e.line)
|
90
90
|
return
|
91
91
|
end
|
92
92
|
|
@@ -120,8 +120,8 @@ class Marty::ScriptForm < Marty::Form
|
|
120
120
|
'PrettyScript',
|
121
121
|
rep_params)
|
122
122
|
client.get_report(path)
|
123
|
-
rescue StandardError =>
|
124
|
-
return client.netzke_notify "ERROR: #{
|
123
|
+
rescue StandardError => e
|
124
|
+
return client.netzke_notify "ERROR: #{e}"
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
@@ -75,8 +75,8 @@ class Marty::ScriptTester < Marty::Form
|
|
75
75
|
client.set_result result.join('<br/>')
|
76
76
|
rescue SystemStackError
|
77
77
|
return client.netzke_notify 'System Stack Error'
|
78
|
-
rescue StandardError =>
|
79
|
-
res = Delorean::Engine.grok_runtime_exception(
|
78
|
+
rescue StandardError => e
|
79
|
+
res = Delorean::Engine.grok_runtime_exception(e)
|
80
80
|
|
81
81
|
result = ["Error: #{res['error']}", 'Backtrace:'] +
|
82
82
|
res['backtrace'].map { |m, line, fn| "#{m}:#{line} #{fn}" }
|
@@ -33,9 +33,7 @@ module Marty; class UserView < Marty::Grid
|
|
33
33
|
def self.set_roles(roles, user)
|
34
34
|
roles = [] unless roles.present?
|
35
35
|
|
36
|
-
roles = Marty::RoleType.
|
37
|
-
roles.include?(I18n.t("roles.#{role}", default: role))
|
38
|
-
end
|
36
|
+
roles = ::Marty::RoleType.from_nice_names(roles)
|
39
37
|
|
40
38
|
roles_in_user = user.user_roles.map(&:role)
|
41
39
|
roles_to_delete = roles_in_user - roles
|
@@ -152,14 +150,10 @@ module Marty; class UserView < Marty::Grid
|
|
152
150
|
c.type = :string
|
153
151
|
|
154
152
|
c.getter = lambda do |r|
|
155
|
-
r.user_roles.map
|
156
|
-
I18n.t("roles.#{ur.role}", default: ur.role)
|
157
|
-
end
|
153
|
+
Marty::RoleType.to_nice_names(r.user_roles.map(&:role))
|
158
154
|
end
|
159
155
|
|
160
|
-
store = ::Marty::RoleType.
|
161
|
-
I18n.t("roles.#{role}", default: role)
|
162
|
-
end
|
156
|
+
store = ::Marty::RoleType.to_nice_names(::Marty::RoleType::VALUES.sort)
|
163
157
|
|
164
158
|
c.editor_config = {
|
165
159
|
multi_select: true,
|
@@ -53,13 +53,16 @@ class Marty::BaseRule < Marty::Base
|
|
53
53
|
|
54
54
|
def validate
|
55
55
|
self.class.guard_info.each { |name, h| check(name, h) }
|
56
|
+
|
56
57
|
grids.each do |vn, gn|
|
57
58
|
return errors[:grids] << "- Bad grid name '#{gn}' for '#{vn}'" unless
|
58
59
|
gn.blank? || Marty::DataGrid.lookup('infinity', gn)
|
59
60
|
end
|
61
|
+
|
60
62
|
cg_err = computed_guards.delete('~~ERROR~~')
|
61
63
|
errors[:computed] <<
|
62
64
|
"- Error in rule '#{name}' field 'computed_guards': #{cg_err.capitalize}" if cg_err
|
65
|
+
|
63
66
|
res_err = results.delete('~~ERROR~~')
|
64
67
|
errors[:computed] <<
|
65
68
|
"- Error in rule '#{name}' field 'results': #{res_err.capitalize}" if res_err
|
@@ -67,12 +70,14 @@ class Marty::BaseRule < Marty::Base
|
|
67
70
|
|
68
71
|
validates_presence_of :name
|
69
72
|
validate :validate
|
73
|
+
validate :validate_simple_guard_options
|
70
74
|
|
71
75
|
before_validation(on: [:create, :update]) do
|
72
|
-
self.simple_guards
|
73
|
-
self.
|
74
|
-
self.
|
75
|
-
self.
|
76
|
+
self.simple_guards ||= {}
|
77
|
+
self.simple_guards_options ||= {}
|
78
|
+
self.computed_guards ||= {}
|
79
|
+
self.grids ||= {}
|
80
|
+
self.results ||= {}
|
76
81
|
end
|
77
82
|
|
78
83
|
before_create do
|
@@ -83,38 +88,93 @@ class Marty::BaseRule < Marty::Base
|
|
83
88
|
end
|
84
89
|
end
|
85
90
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
private
|
92
|
+
|
93
|
+
def validate_simple_guard_options
|
94
|
+
simple_guards_options.each do |guard_name, value|
|
95
|
+
field_path = "'simple_guard_options' -> '#{guard_name}' -> 'not'"
|
96
|
+
|
97
|
+
guard_info = self.class.guard_info[guard_name.to_s]
|
98
|
+
|
99
|
+
if guard_info.blank?
|
100
|
+
errors[:simple_guards_options] <<
|
101
|
+
"- Error in rule '#{name}' #{field_path}."\
|
102
|
+
"Guard '#{guard_name}' doesn't exist."
|
103
|
+
|
104
|
+
next
|
105
|
+
end
|
106
|
+
|
107
|
+
not_is_allowed = guard_info.fetch(:allow_not, true)
|
108
|
+
|
109
|
+
not_field = value['not'] || value[:not]
|
110
|
+
next if not_field.nil?
|
111
|
+
|
112
|
+
if not_field.is_a?(TrueClass)
|
113
|
+
next if not_is_allowed
|
114
|
+
|
115
|
+
errors[:simple_guards_options] <<
|
116
|
+
"- Error in rule '#{name}' #{field_path}. True value is not allowed"
|
117
|
+
next
|
118
|
+
end
|
119
|
+
|
120
|
+
next if not_field.is_a?(FalseClass)
|
121
|
+
|
122
|
+
errors[:simple_guards_options] <<
|
123
|
+
"- Error in rule '#{name}' #{field_path} field must be a boolean"
|
124
|
+
end
|
97
125
|
end
|
98
126
|
|
99
|
-
|
100
|
-
|
127
|
+
class << self
|
128
|
+
def get_subq(field, subfield, multi, type, vraw)
|
129
|
+
arrow = multi || ![:range, :string, :date, :datetime].include?(type) ?
|
130
|
+
'->' : '->>'
|
131
|
+
op = multi || type == :range ? '@>' : '='
|
132
|
+
value0 = [:string, :date, :datetime].include?(type) ?
|
133
|
+
ActiveRecord::Base.connection.quote(vraw) :
|
134
|
+
type == :range ? vraw.to_f :
|
135
|
+
"'#{vraw}'::jsonb"
|
136
|
+
value = multi ? %Q('["%s"]') % value0[1..-2] : value0
|
137
|
+
fieldcast = type == :range ? '::numrange' : ''
|
138
|
+
"(#{field}#{arrow}'#{subfield}')#{fieldcast} #{op} #{value}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_matches_(_pt, attrs, params)
|
142
|
+
q = select('DISTINCT ON (name) *').where(attrs)
|
101
143
|
|
102
|
-
params.each do |k, vraw|
|
103
144
|
h = guard_info
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
145
|
+
|
146
|
+
params.each do |k, vraw|
|
147
|
+
use_k = (h[k] && k) ||
|
148
|
+
(h[k + '_array'] && k + '_array') ||
|
149
|
+
(h[k + '_range'] && k + '_range')
|
150
|
+
|
151
|
+
next unless use_k
|
152
|
+
|
153
|
+
param_guard_info = h[use_k]
|
154
|
+
|
155
|
+
multi = param_guard_info[:multi]
|
156
|
+
type = param_guard_info[:type]
|
157
|
+
with_not = param_guard_info.fetch(:allow_not, true)
|
158
|
+
|
159
|
+
filts = [vraw].flatten.map do |v|
|
160
|
+
qstr = get_subq('simple_guards', use_k, multi, type, v)
|
161
|
+
end.join(' OR ')
|
162
|
+
|
163
|
+
condition = if with_not
|
164
|
+
"CASE
|
165
|
+
WHEN (simple_guards_options->'#{use_k}'->>'not')::boolean
|
166
|
+
THEN simple_guards->'#{use_k}' IS NULL OR NOT #{filts}
|
167
|
+
ELSE simple_guards->'#{use_k}' IS NULL OR #{filts}
|
168
|
+
END
|
169
|
+
"
|
170
|
+
else
|
171
|
+
"simple_guards->'#{use_k}' IS NULL OR #{filts}"
|
172
|
+
end
|
173
|
+
|
174
|
+
q = q.where(condition)
|
175
|
+
end
|
176
|
+
# print q.to_sql
|
177
|
+
q.order(:name)
|
116
178
|
end
|
117
|
-
# print q.to_sql
|
118
|
-
q.order(:name)
|
119
179
|
end
|
120
180
|
end
|
@@ -8,9 +8,11 @@ class Marty::DataGrid < Marty::Base
|
|
8
8
|
'integer' => Marty::GridIndexInteger,
|
9
9
|
'string' => Marty::GridIndexString,
|
10
10
|
'boolean' => Marty::GridIndexBoolean,
|
11
|
-
}
|
11
|
+
}.freeze
|
12
12
|
|
13
|
-
ARRSEP = '|'
|
13
|
+
ARRSEP = '|'.freeze
|
14
|
+
NOT_STRING_START = 'NOT ('.freeze
|
15
|
+
NOT_STRING_END = ')'.freeze
|
14
16
|
|
15
17
|
class DataGridValidator < ActiveModel::Validator
|
16
18
|
def validate(dg)
|
@@ -253,8 +255,7 @@ class Marty::DataGrid < Marty::Base
|
|
253
255
|
|
254
256
|
# private method used to cache lookup_grid_distinct_entry_h result
|
255
257
|
delorean_fn :lookup_grid_h_priv,
|
256
|
-
|
257
|
-
|
258
|
+
private: true, cache: true, sig: 4 do |pt, dgh, h, distinct|
|
258
259
|
lookup_grid_distinct_entry_h(
|
259
260
|
pt, h, dgh, nil, true, false, distinct)['result']
|
260
261
|
end
|
@@ -271,9 +272,9 @@ class Marty::DataGrid < Marty::Base
|
|
271
272
|
end
|
272
273
|
|
273
274
|
def self.lookup_grid_distinct_entry_h(
|
274
|
-
|
275
|
-
|
276
|
-
|
275
|
+
pt, h, dgh, visited = nil, follow = true,
|
276
|
+
return_grid_data = false, distinct = true
|
277
|
+
)
|
277
278
|
|
278
279
|
# Perform grid lookup, if result is another data_grid, and follow is true,
|
279
280
|
# then perform lookup on the resulting grid. Allows grids to be nested
|
@@ -327,9 +328,10 @@ class Marty::DataGrid < Marty::Base
|
|
327
328
|
# should unify this with Marty::DataConversion.convert
|
328
329
|
|
329
330
|
type = inf['type']
|
331
|
+
nots = inf.fetch('nots', [])
|
330
332
|
klass = type.constantize unless INDEX_MAP[type]
|
331
333
|
|
332
|
-
inf['keys'].map do |v|
|
334
|
+
keys = inf['keys'].map do |v|
|
333
335
|
case type
|
334
336
|
when 'numrange', 'int4range'
|
335
337
|
Marty::Util.pg_range_to_human(v)
|
@@ -351,6 +353,12 @@ class Marty::DataGrid < Marty::Base
|
|
351
353
|
v.join(ARRSEP) if v
|
352
354
|
end
|
353
355
|
end
|
356
|
+
|
357
|
+
keys.each_with_index.map do |v, index|
|
358
|
+
next v unless nots[index]
|
359
|
+
|
360
|
+
add_not(v)
|
361
|
+
end
|
354
362
|
end
|
355
363
|
|
356
364
|
# FIXME: this is only here to appease Netzke add_in_form
|
@@ -360,13 +368,19 @@ class Marty::DataGrid < Marty::Base
|
|
360
368
|
def export_array
|
361
369
|
# add data type metadata row if not default
|
362
370
|
lenstr = 'lenient' if lenient
|
363
|
-
typestr = data_type unless [nil, DEFAULT_DATA_TYPE].member?(data_type) &&
|
364
|
-
!constraint.present?
|
365
371
|
|
366
|
-
|
372
|
+
typestr = data_type unless [nil, DEFAULT_DATA_TYPE].member?(data_type)
|
373
|
+
len_type = [lenstr, typestr].compact.join(' ')
|
367
374
|
|
368
|
-
meta_rows =
|
369
|
-
[[
|
375
|
+
meta_rows = if (lenient || typestr) && constraint
|
376
|
+
[[len_type, constraint]]
|
377
|
+
elsif lenient || typestr
|
378
|
+
[[len_type]]
|
379
|
+
elsif constraint
|
380
|
+
[['', constraint]]
|
381
|
+
else
|
382
|
+
[]
|
383
|
+
end
|
370
384
|
|
371
385
|
meta_rows += metadata.map do |inf|
|
372
386
|
[inf['attr'], inf['type'], inf['dir'], inf['rs_keep'] || '']
|
@@ -409,6 +423,8 @@ class Marty::DataGrid < Marty::Base
|
|
409
423
|
def self.parse_fvalue(pt, v, type, klass)
|
410
424
|
return unless v
|
411
425
|
|
426
|
+
v = remove_not(v)
|
427
|
+
|
412
428
|
case type
|
413
429
|
when 'numrange', 'int4range'
|
414
430
|
Marty::Util.human_to_pg_range(v)
|
@@ -461,11 +477,20 @@ class Marty::DataGrid < Marty::Base
|
|
461
477
|
|
462
478
|
def self.parse_keys(pt, keys, type)
|
463
479
|
klass = maybe_get_klass(type)
|
480
|
+
|
464
481
|
keys.map do |v|
|
465
482
|
parse_fvalue(pt, v, type, klass)
|
466
483
|
end
|
467
484
|
end
|
468
485
|
|
486
|
+
def self.parse_nots(_pt, keys)
|
487
|
+
keys.map do |v|
|
488
|
+
next false unless v
|
489
|
+
|
490
|
+
v.starts_with?(NOT_STRING_START) && v.ends_with?(NOT_STRING_END)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
469
494
|
# parse grid external representation into metadata/data
|
470
495
|
def self.parse(pt, grid_text, options)
|
471
496
|
options[:headers] ||= false
|
@@ -506,11 +531,15 @@ class Marty::DataGrid < Marty::Base
|
|
506
531
|
raise "unknown metadata type #{type}" unless
|
507
532
|
Marty::DataGrid.type_to_index(type)
|
508
533
|
|
534
|
+
keys = key && parse_keys(pt, [key], type)
|
535
|
+
nots = key && parse_nots(pt, [key])
|
536
|
+
|
509
537
|
res = {
|
510
538
|
'attr' => attr,
|
511
539
|
'type' => type,
|
512
540
|
'dir' => dir,
|
513
|
-
'keys' =>
|
541
|
+
'keys' => keys,
|
542
|
+
'nots' => nots,
|
514
543
|
}
|
515
544
|
res['rs_keep'] = rs_keep if rs_keep
|
516
545
|
res
|
@@ -530,6 +559,7 @@ class Marty::DataGrid < Marty::Base
|
|
530
559
|
row[0, v_infos.count].any?
|
531
560
|
|
532
561
|
inf['keys'] = parse_keys(pt, row[v_infos.count, row.count], inf['type'])
|
562
|
+
inf['nots'] = parse_nots(pt, row[v_infos.count, row.count])
|
533
563
|
end
|
534
564
|
|
535
565
|
raise 'horiz. info keys length mismatch!' unless
|
@@ -542,6 +572,7 @@ class Marty::DataGrid < Marty::Base
|
|
542
572
|
|
543
573
|
v_infos.each_with_index do |inf, i|
|
544
574
|
inf['keys'] = parse_keys(pt, v_key_cols[i], inf['type'])
|
575
|
+
inf['nots'] = parse_nots(pt, v_key_cols[i])
|
545
576
|
end
|
546
577
|
|
547
578
|
raise 'vert. info keys length mismatch!' unless
|
@@ -553,18 +584,23 @@ class Marty::DataGrid < Marty::Base
|
|
553
584
|
|
554
585
|
# based on data type, decide to check using convert or instance
|
555
586
|
# lookup. FIXME: DRY.
|
587
|
+
|
556
588
|
if String === c_data_type
|
557
589
|
tsym = c_data_type.to_sym
|
558
590
|
|
559
591
|
data = data_rows.map do |r|
|
560
592
|
r[v_infos.count, r.count].map do |v|
|
561
|
-
|
593
|
+
next v unless v
|
594
|
+
|
595
|
+
Marty::DataConversion.convert(v, tsym)
|
562
596
|
end
|
563
597
|
end
|
564
598
|
else
|
565
599
|
data = data_rows.map do |r|
|
566
600
|
r[v_infos.count, r.count].map do |v|
|
567
|
-
next v
|
601
|
+
next v unless v
|
602
|
+
|
603
|
+
next v if Marty::DataGrid.
|
568
604
|
find_class_instance(pt, c_data_type, v)
|
569
605
|
|
570
606
|
raise "can't find key '#{v}' for class #{data_type}"
|
@@ -572,12 +608,24 @@ class Marty::DataGrid < Marty::Base
|
|
572
608
|
end
|
573
609
|
end
|
574
610
|
|
575
|
-
|
611
|
+
{
|
612
|
+
metadata: metadata,
|
613
|
+
data: data,
|
614
|
+
data_type: data_type,
|
615
|
+
lenient: lenient,
|
616
|
+
constraint: constraint,
|
617
|
+
}
|
576
618
|
end
|
577
619
|
|
578
620
|
def self.create_from_import(name, import_text, created_dt = nil)
|
579
|
-
|
580
|
-
|
621
|
+
parsed_result = parse(created_dt, import_text, {})
|
622
|
+
|
623
|
+
metadata = parsed_result[:metadata]
|
624
|
+
data = parsed_result[:data]
|
625
|
+
data_type = parsed_result[:data_type]
|
626
|
+
lenient = parsed_result[:lenient]
|
627
|
+
constraint = parsed_result[:constraint]
|
628
|
+
|
581
629
|
dg = new
|
582
630
|
dg.name = name
|
583
631
|
dg.data = data
|
@@ -591,8 +639,13 @@ class Marty::DataGrid < Marty::Base
|
|
591
639
|
end
|
592
640
|
|
593
641
|
def update_from_import(name, import_text, created_dt = nil)
|
594
|
-
|
595
|
-
|
642
|
+
parsed_result = self.class.parse(created_dt, import_text, {})
|
643
|
+
|
644
|
+
new_metadata = parsed_result[:metadata]
|
645
|
+
data = parsed_result[:data]
|
646
|
+
data_type = parsed_result[:data_type]
|
647
|
+
lenient = parsed_result[:lenient]
|
648
|
+
constraint = parsed_result[:constraint]
|
596
649
|
|
597
650
|
self.name = name
|
598
651
|
self.data = data
|
@@ -609,7 +662,10 @@ class Marty::DataGrid < Marty::Base
|
|
609
662
|
def build_index
|
610
663
|
# create indices for the metadata
|
611
664
|
metadata.each do |inf|
|
612
|
-
attr
|
665
|
+
attr = inf['attr']
|
666
|
+
type = inf['type']
|
667
|
+
keys = inf['keys']
|
668
|
+
nots = inf.fetch('nots', [])
|
613
669
|
|
614
670
|
# find index class
|
615
671
|
idx_class = Marty::DataGrid.type_to_index(type)
|
@@ -621,6 +677,7 @@ class Marty::DataGrid < Marty::Base
|
|
621
677
|
gi.created_dt = created_dt
|
622
678
|
gi.data_grid_id = group_id
|
623
679
|
gi.index = index
|
680
|
+
gi.not = nots[index] || false
|
624
681
|
gi.save!
|
625
682
|
end
|
626
683
|
end
|
@@ -796,4 +853,17 @@ class Marty::DataGrid < Marty::Base
|
|
796
853
|
# we convert to the opposite (what to prune)
|
797
854
|
[opposite_sign(opstr.to_sym), ident]
|
798
855
|
end
|
856
|
+
|
857
|
+
def self.remove_not(string)
|
858
|
+
return string unless string.starts_with?(NOT_STRING_START)
|
859
|
+
return string unless string.ends_with?(NOT_STRING_END)
|
860
|
+
|
861
|
+
remove_from_left = NOT_STRING_START.size
|
862
|
+
remove_from_right = NOT_STRING_END.size
|
863
|
+
string.slice(remove_from_left...-remove_from_right)
|
864
|
+
end
|
865
|
+
|
866
|
+
def self.add_not(string)
|
867
|
+
"#{NOT_STRING_START}#{string}#{NOT_STRING_END}"
|
868
|
+
end
|
799
869
|
end
|