marty 2.6.4 → 2.6.5
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 +4 -4
- data/.gitlab-ci.yml +2 -1
- data/.rubocop_todo.yml +1 -0
- data/Gemfile.lock +4 -2
- data/app/models/marty/enum_promise_type.rb +5 -0
- data/app/models/marty/promise.rb +47 -31
- data/app/services/marty/promises/delorean.rb +6 -0
- data/app/services/marty/promises/delorean/create.rb +75 -0
- data/app/services/marty/promises/ruby.rb +6 -0
- data/app/services/marty/promises/ruby/create.rb +44 -0
- data/db/migrate/502_add_promise_type_enum.rb +15 -0
- data/db/migrate/503_add_promise_type_to_promises.rb +13 -0
- data/lib/marty/monkey.rb +64 -0
- data/lib/marty/permissions.rb +29 -0
- data/lib/marty/promise_job.rb +7 -99
- data/lib/marty/promise_ruby_job.rb +47 -0
- data/lib/marty/version.rb +1 -1
- data/spec/dummy/app/components/gemini/loan_program_view.rb +22 -1
- data/spec/dummy/app/models/gemini/bud_category.rb +16 -0
- data/spec/dummy/lib/gemini/promise_hook/test_hook.rb +9 -0
- data/spec/features/endpoint_access.rb +37 -0
- data/spec/features/jobs_dashboard_spec.rb +10 -4
- data/spec/lib/mcfly_model_spec.rb +3 -2
- data/spec/models/promise_spec.rb +146 -0
- data/spec/support/components/netzke_grid.rb +1 -1
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cabd5f09c9dce0fee4129f0bf6fe90b9c92443db0e6ce2ba6703a56c5b1e067
|
4
|
+
data.tar.gz: 336101e0514a022e49c96709664a881121d42b6d54497f9eede865c89d4470b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b5cae6730109621e2a8334ec31a1b4a045cf11d551bfa6e13aa9784454a4c67e4573818a2d6a1ed92c00bedd63d51f896fe3be017868c3a4f0baf84f96a97d8
|
7
|
+
data.tar.gz: 59a70afb17b02d6b944c0c492f7d1db9105c17e6f69aed36fda844bf9e2400cf3a76722fbec5e202cb3fed579fd3c1a241e67faa58750996e2c9b7a0cf0be173
|
data/.gitlab-ci.yml
CHANGED
@@ -12,7 +12,7 @@ before_script:
|
|
12
12
|
stage: test
|
13
13
|
# Use only the following CI runners
|
14
14
|
tags:
|
15
|
-
-
|
15
|
+
- gitlabci-runner-eks-shared-dev
|
16
16
|
|
17
17
|
rubocop:
|
18
18
|
extends: .base-test
|
@@ -28,6 +28,7 @@ rspec-features:
|
|
28
28
|
extends: .base-test
|
29
29
|
script:
|
30
30
|
- ln -s /opt/support/extjs /cm_tech/marty/spec/dummy/public/extjs
|
31
|
+
- chromedriver-update 73.0.3683.68
|
31
32
|
# - bundle exec rspec --pattern "spec/features/**/*_spec.rb"
|
32
33
|
# FIXME: rule_spec is excluded, because chrome doesn't work with big window size in docker
|
33
34
|
# And test fails with 1400/1400 resolution
|
data/.rubocop_todo.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
marty (2.6.
|
4
|
+
marty (2.6.5)
|
5
5
|
aws-sigv4 (~> 1.0, >= 1.0.2)
|
6
6
|
axlsx (= 3.0.0pre)
|
7
7
|
coderay
|
@@ -62,7 +62,9 @@ GEM
|
|
62
62
|
io-like (~> 0.3.0)
|
63
63
|
arel (8.0.0)
|
64
64
|
ast (2.4.0)
|
65
|
-
aws-
|
65
|
+
aws-eventstream (1.0.3)
|
66
|
+
aws-sigv4 (1.1.0)
|
67
|
+
aws-eventstream (~> 1.0, >= 1.0.2)
|
66
68
|
axlsx (3.0.0.pre)
|
67
69
|
htmlentities (~> 4.3, >= 4.3.4)
|
68
70
|
mimemagic (~> 0.3)
|
data/app/models/marty/promise.rb
CHANGED
@@ -10,44 +10,16 @@ class Marty::Promise < Marty::Base
|
|
10
10
|
Marty::Promise.load_result(res, force)
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.load_result(obj, force = false)
|
14
|
-
if force && obj.respond_to?(:__force__)
|
15
|
-
obj = obj.__force__
|
16
|
-
end
|
17
|
-
|
18
|
-
case obj
|
19
|
-
when Array
|
20
|
-
obj.map { |x| load_result(x, force) }
|
21
|
-
when Hash
|
22
|
-
p = obj['__promise__']
|
23
|
-
|
24
|
-
if p && obj.length == 1
|
25
|
-
load_result(Marty::PromiseProxy.new(*p), force)
|
26
|
-
else
|
27
|
-
obj.each_with_object({}) { |(k, v), h| h[k] = load_result(v, force) }
|
28
|
-
end
|
29
|
-
else
|
30
|
-
obj
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
13
|
has_many :children,
|
35
14
|
foreign_key: 'parent_id',
|
36
15
|
class_name: 'Marty::Promise',
|
37
16
|
dependent: :destroy
|
38
17
|
|
39
|
-
validates_presence_of :title
|
18
|
+
validates_presence_of :title, :promise_type
|
40
19
|
|
41
20
|
belongs_to :parent, class_name: 'Marty::Promise'
|
42
21
|
belongs_to :user, class_name: 'Marty::User'
|
43
22
|
|
44
|
-
def self.cleanup(all = false)
|
45
|
-
where('start_dt < ? AND parent_id IS NULL',
|
46
|
-
DateTime.now - (all ? 0.hours : 4.hours)).destroy_all
|
47
|
-
rescue StandardError => exc
|
48
|
-
Marty::Util.logger.error("promise GC error: #{exc}")
|
49
|
-
end
|
50
|
-
|
51
23
|
def raw_conn
|
52
24
|
self.class.connection.raw_connection
|
53
25
|
end
|
@@ -162,8 +134,8 @@ class Marty::Promise < Marty::Base
|
|
162
134
|
work_off_job(job)
|
163
135
|
rescue StandardError => exc
|
164
136
|
# log "OFFERR #{exc}"
|
165
|
-
|
166
|
-
last.set_result(
|
137
|
+
error = exception_to_result(exc)
|
138
|
+
last.set_result(error)
|
167
139
|
end
|
168
140
|
# log "OFF1 #{Process.pid} #{last}"
|
169
141
|
end
|
@@ -211,4 +183,48 @@ class Marty::Promise < Marty::Base
|
|
211
183
|
result: promise.result
|
212
184
|
}
|
213
185
|
end
|
186
|
+
|
187
|
+
def delorean?
|
188
|
+
promise_type == 'delorean'
|
189
|
+
end
|
190
|
+
|
191
|
+
class << self
|
192
|
+
def load_result(obj, force = false)
|
193
|
+
if force && obj.respond_to?(:__force__)
|
194
|
+
obj = obj.__force__
|
195
|
+
end
|
196
|
+
|
197
|
+
case obj
|
198
|
+
when Array
|
199
|
+
obj.map { |x| load_result(x, force) }
|
200
|
+
when Hash
|
201
|
+
p = obj['__promise__']
|
202
|
+
|
203
|
+
if p && obj.length == 1
|
204
|
+
load_result(Marty::PromiseProxy.new(*p), force)
|
205
|
+
else
|
206
|
+
obj.each_with_object({}) { |(k, v), h| h[k] = load_result(v, force) }
|
207
|
+
end
|
208
|
+
else
|
209
|
+
obj
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def cleanup(all = false)
|
214
|
+
where(
|
215
|
+
'start_dt < ? AND parent_id IS NULL',
|
216
|
+
DateTime.now - (all ? 0.hours : 4.hours)
|
217
|
+
).destroy_all
|
218
|
+
rescue StandardError => exc
|
219
|
+
Marty::Util.logger.error("promise GC error: #{exc}")
|
220
|
+
end
|
221
|
+
|
222
|
+
def exception_to_result(promise:, exception:)
|
223
|
+
if promise.delorean?
|
224
|
+
return Delorean::Engine.grok_runtime_exception(exception)
|
225
|
+
end
|
226
|
+
|
227
|
+
{ 'error' => exception.message, 'backtrace' => exception.backtrace }
|
228
|
+
end
|
229
|
+
end
|
214
230
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Marty
|
2
|
+
module Promises
|
3
|
+
module Delorean
|
4
|
+
module Create
|
5
|
+
def self.call(params:, script:, node_name:, attr:, args:, tag:)
|
6
|
+
default_timeout = Marty::Promise::DEFAULT_PROMISE_TIMEOUT
|
7
|
+
|
8
|
+
title = params['p_title'] || "#{script}::#{node_name.demodulize}"
|
9
|
+
timeout = params['p_timeout'] || default_timeout
|
10
|
+
hook = params['p_hook']
|
11
|
+
|
12
|
+
promise = Marty::Promise.create(
|
13
|
+
title: title,
|
14
|
+
user_id: params[:_user_id],
|
15
|
+
parent_id: params[:_parent_id],
|
16
|
+
promise_type: 'delorean'
|
17
|
+
)
|
18
|
+
|
19
|
+
params[:_promise_id] = promise.id
|
20
|
+
|
21
|
+
begin
|
22
|
+
promise_job = Marty::PromiseJob.new(
|
23
|
+
promise,
|
24
|
+
title,
|
25
|
+
script,
|
26
|
+
tag,
|
27
|
+
node_name,
|
28
|
+
params,
|
29
|
+
args,
|
30
|
+
hook
|
31
|
+
)
|
32
|
+
|
33
|
+
job = Delayed::Job.enqueue(promise_job)
|
34
|
+
rescue StandardError => exc
|
35
|
+
# log "CALLERR #{exc}"
|
36
|
+
res = ::Delorean::Engine.grok_runtime_exception(exc)
|
37
|
+
promise.set_start
|
38
|
+
promise.set_result(res)
|
39
|
+
# log "CALLERRSET #{res}"
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
|
43
|
+
# keep a reference to the job. This is needed in case we want to
|
44
|
+
# work off a promise job that we're waiting for and which hasn't
|
45
|
+
# been reserved yet.
|
46
|
+
promise.job_id = job.id
|
47
|
+
promise.save!
|
48
|
+
|
49
|
+
evh = params['p_event']
|
50
|
+
if evh
|
51
|
+
event, klass, subject_id, operation = evh.values_at(
|
52
|
+
'event',
|
53
|
+
'klass',
|
54
|
+
'id',
|
55
|
+
'operation'
|
56
|
+
)
|
57
|
+
|
58
|
+
if event
|
59
|
+
event.promise_id = promise.id
|
60
|
+
event.save!
|
61
|
+
else
|
62
|
+
Marty::Event.create!(
|
63
|
+
promise_id: promise.id,
|
64
|
+
klass: klass,
|
65
|
+
subject_id: subject_id,
|
66
|
+
enum_event_operation: operation
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
Marty::PromiseProxy.new(promise.id, timeout, attr)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Marty
|
2
|
+
module Promises
|
3
|
+
module Ruby
|
4
|
+
module Create
|
5
|
+
def self.call(module_name:, method_name:, method_args:, params: {})
|
6
|
+
default_timeout = Marty::Promise::DEFAULT_PROMISE_TIMEOUT
|
7
|
+
|
8
|
+
promise_params = params.with_indifferent_access
|
9
|
+
|
10
|
+
title = promise_params['p_title'] || "#{module_name}.#{method_name}"
|
11
|
+
timeout = promise_params['p_timeout'] || default_timeout
|
12
|
+
hook = promise_params['p_hook']
|
13
|
+
|
14
|
+
promise = Marty::Promise.create(
|
15
|
+
title: title,
|
16
|
+
user_id: promise_params[:_user_id],
|
17
|
+
parent_id: promise_params[:_parent_id],
|
18
|
+
promise_type: 'ruby'
|
19
|
+
)
|
20
|
+
|
21
|
+
begin
|
22
|
+
promise_job = Marty::PromiseRubyJob.new(
|
23
|
+
promise,
|
24
|
+
title,
|
25
|
+
module_name,
|
26
|
+
method_name,
|
27
|
+
method_args,
|
28
|
+
hook
|
29
|
+
)
|
30
|
+
|
31
|
+
job = Delayed::Job.enqueue(promise_job)
|
32
|
+
rescue StandardError => exc
|
33
|
+
res = { 'error' => exc.message }
|
34
|
+
promise.set_start
|
35
|
+
promise.set_result(res)
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
|
39
|
+
Marty::PromiseProxy.new(promise.id, timeout, 'result')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class AddPromiseTypeEnum < ActiveRecord::Migration[4.2]
|
2
|
+
def up
|
3
|
+
values = Marty::EnumPromiseType::VALUES
|
4
|
+
str_values = values.map {|v| ActiveRecord::Base.connection.quote v}.join ','
|
5
|
+
execute <<-SQL
|
6
|
+
CREATE TYPE marty_promise_types AS ENUM (#{str_values})
|
7
|
+
SQL
|
8
|
+
end
|
9
|
+
|
10
|
+
def down
|
11
|
+
execute <<-SQL
|
12
|
+
DROP TYPE marty_promise_types
|
13
|
+
SQL
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class AddPromiseTypeToPromises < ActiveRecord::Migration[4.2]
|
2
|
+
def up
|
3
|
+
add_column :marty_promises, :promise_type, :marty_promise_types
|
4
|
+
|
5
|
+
Marty::Promise.update_all(promise_type: 'delorean')
|
6
|
+
|
7
|
+
change_column :marty_promises, :promise_type, :marty_promise_types, null: false
|
8
|
+
end
|
9
|
+
|
10
|
+
def down
|
11
|
+
remove_column :marty_promises, :promise_type
|
12
|
+
end
|
13
|
+
end
|
data/lib/marty/monkey.rb
CHANGED
@@ -22,6 +22,70 @@ end
|
|
22
22
|
|
23
23
|
######################################################################
|
24
24
|
|
25
|
+
# Patch Delorean to work with promises
|
26
|
+
|
27
|
+
class Delorean::BaseModule::NodeCall
|
28
|
+
def initialize(_e, engine, node, params)
|
29
|
+
super
|
30
|
+
|
31
|
+
# If call has a promise_id (i.e. is from a promise) then that's
|
32
|
+
# our parent. Otherwise, we use its parent as our parent.
|
33
|
+
params[:_parent_id] = _e[:_promise_id] || _e[:_parent_id]
|
34
|
+
params[:_user_id] = _e[:_user_id] || Mcfly.whodunnit.try(:id)
|
35
|
+
end
|
36
|
+
|
37
|
+
# def log(msg)
|
38
|
+
# open('/tmp/dj.out', 'a') { |f| f.puts msg }
|
39
|
+
# end
|
40
|
+
|
41
|
+
# Monkey-patch '|' method for Delorean NodeCall to create promise
|
42
|
+
# jobs and return promise proxy objects.
|
43
|
+
def |(args)
|
44
|
+
if args.is_a?(String)
|
45
|
+
attr = args
|
46
|
+
args = [attr]
|
47
|
+
else
|
48
|
+
raise 'bad arg to %' unless args.is_a?(Array)
|
49
|
+
|
50
|
+
attr = nil
|
51
|
+
end
|
52
|
+
script, tag = engine.module_name, engine.sset.tag
|
53
|
+
nn = node.is_a?(Class) ? node.name : node.to_s
|
54
|
+
begin
|
55
|
+
# make sure params is serialzable before starting a Job
|
56
|
+
JSON.dump(params)
|
57
|
+
rescue StandardError => exc
|
58
|
+
raise "non-serializable parameters: #{params} #{exc}"
|
59
|
+
end
|
60
|
+
|
61
|
+
Marty::Promises::Delorean::Create.call(
|
62
|
+
params: params,
|
63
|
+
script: script,
|
64
|
+
node_name: nn,
|
65
|
+
attr: attr,
|
66
|
+
args: args,
|
67
|
+
tag: tag
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Delorean::Engine
|
73
|
+
def background_eval(node, params, attrs, event = {})
|
74
|
+
raise 'background_eval bad params' unless params.is_a?(Hash)
|
75
|
+
|
76
|
+
unless event.empty?
|
77
|
+
params['p_event'] = event.each_with_object({}) do |(k, v), h|
|
78
|
+
h[k.to_s] = v
|
79
|
+
end
|
80
|
+
end
|
81
|
+
nc = Delorean::BaseModule::NodeCall.new({}, self, node, params)
|
82
|
+
# start the background promise
|
83
|
+
nc | attrs
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
######################################################################
|
88
|
+
|
25
89
|
class Hash
|
26
90
|
# define addition on hashes -- useful in Delorean code.
|
27
91
|
def +(x)
|
data/lib/marty/permissions.rb
CHANGED
@@ -59,4 +59,33 @@ module Marty::Permissions
|
|
59
59
|
def has_any_perm?
|
60
60
|
!(current_user_roles & ALL_ROLES).empty?
|
61
61
|
end
|
62
|
+
|
63
|
+
# FIXME: for backwards compatibility returns true
|
64
|
+
# if permission is not specified in has_marty_permissions
|
65
|
+
|
66
|
+
NETZKE_ENDPOINTS = [:create, :read, :update, :delete].freeze
|
67
|
+
|
68
|
+
def can_call_endpoint?(endpoint)
|
69
|
+
# Netzke endpoints access is controlled by Netzke permissions
|
70
|
+
return true if NETZKE_ENDPOINTS.include?(endpoint.to_sym)
|
71
|
+
|
72
|
+
return true unless respond_to?(:marty_permissions)
|
73
|
+
return true unless marty_permissions.key?(endpoint.to_sym)
|
74
|
+
|
75
|
+
can_perform_action?(endpoint)
|
76
|
+
end
|
77
|
+
|
78
|
+
# FIXME: hack to override Netzke invoke endpoint
|
79
|
+
# for classes with Marty::Permissions
|
80
|
+
def self.extended(mod)
|
81
|
+
mod.class_exec do
|
82
|
+
def invoke_endpoint(endpoint, params, configs = [])
|
83
|
+
return super(endpoint, params, configs) if self.class.can_call_endpoint?(endpoint)
|
84
|
+
|
85
|
+
self.client = Netzke::Core::EndpointResponse.new
|
86
|
+
client.netzke_notify 'Permission Denied'
|
87
|
+
client
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
62
91
|
end
|
data/lib/marty/promise_job.rb
CHANGED
@@ -1,100 +1,5 @@
|
|
1
1
|
require 'delorean_lang'
|
2
2
|
|
3
|
-
class Delorean::BaseModule::NodeCall
|
4
|
-
def initialize(_e, engine, node, params)
|
5
|
-
super
|
6
|
-
|
7
|
-
# If call has a promise_id (i.e. is from a promise) then that's
|
8
|
-
# our parent. Otherwise, we use its parent as our parent.
|
9
|
-
params[:_parent_id] = _e[:_promise_id] || _e[:_parent_id]
|
10
|
-
params[:_user_id] = _e[:_user_id] || Mcfly.whodunnit.try(:id)
|
11
|
-
end
|
12
|
-
|
13
|
-
# def log(msg)
|
14
|
-
# open('/tmp/dj.out', 'a') { |f| f.puts msg }
|
15
|
-
# end
|
16
|
-
|
17
|
-
# Monkey-patch '|' method for Delorean NodeCall to create promise
|
18
|
-
# jobs and return promise proxy objects.
|
19
|
-
def |(args)
|
20
|
-
if args.is_a?(String)
|
21
|
-
attr = args
|
22
|
-
args = [attr]
|
23
|
-
else
|
24
|
-
raise 'bad arg to %' unless args.is_a?(Array)
|
25
|
-
|
26
|
-
attr = nil
|
27
|
-
end
|
28
|
-
script, tag = engine.module_name, engine.sset.tag
|
29
|
-
nn = node.is_a?(Class) ? node.name : node.to_s
|
30
|
-
begin
|
31
|
-
# make sure params is serialzable before starting a Job
|
32
|
-
JSON.dump(params)
|
33
|
-
rescue StandardError => exc
|
34
|
-
raise "non-serializable parameters: #{params} #{exc}"
|
35
|
-
end
|
36
|
-
|
37
|
-
# log "||||| #{args.inspect} #{params.inspect}"
|
38
|
-
|
39
|
-
title = params['p_title'] || "#{script}::#{nn.demodulize}"
|
40
|
-
timeout = params['p_timeout'] || Marty::Promise::DEFAULT_PROMISE_TIMEOUT
|
41
|
-
hook = params['p_hook']
|
42
|
-
promise = Marty::Promise.
|
43
|
-
create(title: title,
|
44
|
-
user_id: params[:_user_id],
|
45
|
-
parent_id: params[:_parent_id],
|
46
|
-
)
|
47
|
-
params[:_promise_id] = promise.id
|
48
|
-
begin
|
49
|
-
job = Delayed::Job.enqueue Marty::PromiseJob.
|
50
|
-
new(promise, title, script, tag, nn, params, args, hook)
|
51
|
-
rescue StandardError => exc
|
52
|
-
# log "CALLERR #{exc}"
|
53
|
-
res = Delorean::Engine.grok_runtime_exception(exc)
|
54
|
-
promise.set_start
|
55
|
-
promise.set_result(res)
|
56
|
-
# log "CALLERRSET #{res}"
|
57
|
-
raise
|
58
|
-
end
|
59
|
-
|
60
|
-
# keep a reference to the job. This is needed in case we want to
|
61
|
-
# work off a promise job that we're waiting for and which hasn't
|
62
|
-
# been reserved yet.
|
63
|
-
promise.job_id = job.id
|
64
|
-
promise.save!
|
65
|
-
|
66
|
-
evh = params['p_event']
|
67
|
-
if evh
|
68
|
-
event, klass, subject_id, operation = evh.values_at('event', 'klass',
|
69
|
-
'id', 'operation')
|
70
|
-
if event
|
71
|
-
event.promise_id = promise.id
|
72
|
-
event.save!
|
73
|
-
else
|
74
|
-
event = Marty::Event.
|
75
|
-
create!(promise_id: promise.id,
|
76
|
-
klass: klass,
|
77
|
-
subject_id: subject_id,
|
78
|
-
enum_event_operation: operation)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
Marty::PromiseProxy.new(promise.id, timeout, attr)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class Delorean::Engine
|
86
|
-
def background_eval(node, params, attrs, event = {})
|
87
|
-
raise 'background_eval bad params' unless params.is_a?(Hash)
|
88
|
-
|
89
|
-
params['p_event'] = event.each_with_object({}) do |(k, v), h|
|
90
|
-
h[k.to_s] = v
|
91
|
-
end unless event.empty?
|
92
|
-
nc = Delorean::BaseModule::NodeCall.new({}, self, node, params)
|
93
|
-
# start the background promise
|
94
|
-
nc | attrs
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
3
|
class Marty::PromiseJob < Struct.new(:promise,
|
99
4
|
:title,
|
100
5
|
:sname,
|
@@ -141,12 +46,15 @@ class Marty::PromiseJob < Struct.new(:promise,
|
|
141
46
|
# log "ERR- #{Process.pid} #{promise.id} #{Time.now.to_f} #{exc}"
|
142
47
|
end
|
143
48
|
promise.set_result(res)
|
49
|
+
process_hook(res)
|
50
|
+
end
|
144
51
|
|
145
|
-
|
146
|
-
|
147
|
-
|
52
|
+
def process_hook(res)
|
53
|
+
return unless hook
|
54
|
+
|
55
|
+
hook.run(params: params, result: res)
|
56
|
+
rescue StandardError => exc
|
148
57
|
Marty::Util.logger.error "promise hook failed: #{exc}"
|
149
|
-
end
|
150
58
|
end
|
151
59
|
|
152
60
|
def max_attempts
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Marty::PromiseRubyJob < Struct.new(:promise,
|
2
|
+
:title,
|
3
|
+
:module_name,
|
4
|
+
:method_name,
|
5
|
+
:method_args,
|
6
|
+
:hook,
|
7
|
+
)
|
8
|
+
|
9
|
+
def enqueue(job)
|
10
|
+
config = Rails.configuration.marty
|
11
|
+
hooks = config.promise_job_enqueue_hooks
|
12
|
+
|
13
|
+
return if hooks.blank?
|
14
|
+
|
15
|
+
hooks.each do |hook|
|
16
|
+
hook.call(job)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform
|
21
|
+
promise.set_start
|
22
|
+
|
23
|
+
begin
|
24
|
+
Mcfly.whodunnit = promise.user
|
25
|
+
|
26
|
+
mod = module_name.constantize
|
27
|
+
res = { 'result' => mod.send(method_name, *method_args) }
|
28
|
+
rescue StandardError => exc
|
29
|
+
res = ::Marty::Promise.exception_to_result(promise: promise, exception: exc)
|
30
|
+
end
|
31
|
+
|
32
|
+
promise.set_result(res)
|
33
|
+
process_hook(res)
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_hook(res)
|
37
|
+
return unless hook
|
38
|
+
|
39
|
+
hook.run(params: method_args, result: res)
|
40
|
+
rescue StandardError => exc
|
41
|
+
Marty::Util.logger.error "promise hook failed: #{exc}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def max_attempts
|
45
|
+
1
|
46
|
+
end
|
47
|
+
end
|
data/lib/marty/version.rb
CHANGED
@@ -4,7 +4,8 @@ class Gemini::LoanProgramView < Marty::GridAppendOnly
|
|
4
4
|
has_marty_permissions create: :dev,
|
5
5
|
read: :any,
|
6
6
|
update: :dev,
|
7
|
-
delete: :dev
|
7
|
+
delete: :dev,
|
8
|
+
test_access: :admin
|
8
9
|
|
9
10
|
def configure(c)
|
10
11
|
super
|
@@ -22,7 +23,27 @@ class Gemini::LoanProgramView < Marty::GridAppendOnly
|
|
22
23
|
c.store_config.merge!({sorters: [{property: :name, direction: 'ASC'}]})
|
23
24
|
end
|
24
25
|
|
26
|
+
client_class do |c|
|
27
|
+
c.netzke_on_test_access = l(<<-JS)
|
28
|
+
function() {
|
29
|
+
this.server.testAccess({})
|
30
|
+
}
|
31
|
+
JS
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_bbar
|
35
|
+
super + [:test_access]
|
36
|
+
end
|
37
|
+
|
38
|
+
action :test_access do |a|
|
39
|
+
a.text = a.tooltip = 'Test Access'
|
40
|
+
end
|
41
|
+
|
25
42
|
attribute :enum_state do |c|
|
26
43
|
enum_column(c, Gemini::EnumState)
|
27
44
|
end
|
45
|
+
|
46
|
+
endpoint :test_access do |c|
|
47
|
+
client.netzke_notify 'You have admin access'
|
48
|
+
end
|
28
49
|
end
|
@@ -3,5 +3,21 @@ module Gemini
|
|
3
3
|
self.table_name = 'gemini_bud_categories'
|
4
4
|
has_mcfly append_only: true
|
5
5
|
mcfly_validates_uniqueness_of :name
|
6
|
+
|
7
|
+
def self.create_from_promise_keyword_attrs(name:, group_id:)
|
8
|
+
create!(name: name, group_id: group_id).id
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.create_from_promise_regular_attrs(name, group_id)
|
12
|
+
create!(name: name, group_id: group_id).id
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create_from_promise_mixed_attrs(name, group_id:)
|
16
|
+
create!(name: name, group_id: group_id).id
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create_from_promise_error
|
20
|
+
raise 'Something went wrong'
|
21
|
+
end
|
6
22
|
end
|
7
23
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'Endpoint access', js: true do
|
4
|
+
before do
|
5
|
+
populate_test_users
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'as admin' do
|
9
|
+
before do
|
10
|
+
log_in_as('admin1')
|
11
|
+
wait_for_ajax
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can access test_access endpoint' do
|
15
|
+
press('Pricing Config.')
|
16
|
+
press('Loan Programs')
|
17
|
+
press('Test Access')
|
18
|
+
wait_for_ajax
|
19
|
+
expect(page).to have_content 'You have admin access'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'as dev' do
|
24
|
+
before do
|
25
|
+
log_in_as('dev1')
|
26
|
+
wait_for_ajax
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'can not access test_access endpoint' do
|
30
|
+
press('Pricing Config.')
|
31
|
+
press('Loan Programs')
|
32
|
+
press('Test Access')
|
33
|
+
wait_for_ajax
|
34
|
+
expect(page).to have_content 'Permission Denied'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -6,14 +6,20 @@ describe 'Jobs Dashboard', type: :feature, js: true, capybara: true do
|
|
6
6
|
firstname: 'other',
|
7
7
|
lastname: 'other',
|
8
8
|
active: true)
|
9
|
-
Marty::Promise.create
|
9
|
+
Marty::Promise.create!(
|
10
|
+
title: 'Test Job 1',
|
10
11
|
user: Marty::User.find_by(login: 'marty'),
|
11
12
|
cformat: 'csv',
|
12
|
-
start_dt: Time.now
|
13
|
-
|
13
|
+
start_dt: Time.now,
|
14
|
+
promise_type: 'delorean'
|
15
|
+
)
|
16
|
+
Marty::Promise.create!(
|
17
|
+
title: 'Test Job 2',
|
14
18
|
user: other_user,
|
15
19
|
cformat: 'csv',
|
16
|
-
start_dt: Time.now
|
20
|
+
start_dt: Time.now,
|
21
|
+
promise_type: 'delorean'
|
22
|
+
)
|
17
23
|
|
18
24
|
visit '/'
|
19
25
|
all 'span', text: 'Sign in', minimum: 1
|
@@ -194,8 +194,9 @@ describe 'McflyModel' do
|
|
194
194
|
1, 2)
|
195
195
|
end
|
196
196
|
end
|
197
|
-
# x time should be
|
198
|
-
|
197
|
+
# x time should be 25x or more than y time
|
198
|
+
# Used to be 30x, but 30x sometimes fails on CI
|
199
|
+
expect(x.real / y.real).to be > 25
|
199
200
|
end
|
200
201
|
end
|
201
202
|
end
|
data/spec/models/promise_spec.rb
CHANGED
@@ -28,6 +28,7 @@ describe Marty::Promise, slow: true do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
after(:each) do
|
31
|
+
Marty::Log.delete_all
|
31
32
|
Marty::Promise.where('parent_id IS NULL').destroy_all
|
32
33
|
Timecop.return
|
33
34
|
end
|
@@ -60,4 +61,149 @@ describe Marty::Promise, slow: true do
|
|
60
61
|
expect(Marty::VwPromise.live_search('marty').size).to eq(10)
|
61
62
|
expect(Marty::VwPromise.live_search('Admin').size).to eq(10)
|
62
63
|
end
|
64
|
+
|
65
|
+
describe 'delorean' do
|
66
|
+
it 'processes result' do
|
67
|
+
expect(Marty::Promise.where(title: 'PromiseB').exists?).to be false
|
68
|
+
|
69
|
+
engine = Marty::ScriptSet.new.get_engine(NAME_B)
|
70
|
+
engine.background_eval(
|
71
|
+
'Y',
|
72
|
+
{
|
73
|
+
'p_title' => NAME_B,
|
74
|
+
'p_hook' => Gemini::PromiseHook::TestHook
|
75
|
+
},
|
76
|
+
['result']
|
77
|
+
)
|
78
|
+
|
79
|
+
promise = Marty::Promise.find_by(title: 'PromiseB')
|
80
|
+
promise.wait_for_result(Marty::Promise::DEFAULT_PROMISE_TIMEOUT)
|
81
|
+
promise.reload
|
82
|
+
|
83
|
+
expected = [{ 'a' => 1, 'b' => 1 }, { 'a' => 2, 'b' => 4 }, { 'a' => 3, 'b' => 9 }]
|
84
|
+
expect(promise.status).to be true
|
85
|
+
expect(promise.promise_type).to eq 'delorean'
|
86
|
+
expect(promise.result['error']).to be nil
|
87
|
+
expect(promise.result['result']).to eq expected
|
88
|
+
|
89
|
+
sleep 0.1 # Wait while hooks are executed after Promise was updated
|
90
|
+
log = Marty::Log.find_by(message_type: 'TestHook')
|
91
|
+
expect(log.message).to eq 'was called'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'fails on exception' do
|
95
|
+
expect(Marty::Promise.where(title: 'PromiseJ').exists?).to be false
|
96
|
+
|
97
|
+
engine = Marty::ScriptSet.new.get_engine(NAME_J)
|
98
|
+
engine.background_eval('FAILER', { 'p_title' => NAME_J }, ['a'])
|
99
|
+
|
100
|
+
promise = Marty::Promise.find_by(title: 'PromiseJ')
|
101
|
+
promise.wait_for_result(Marty::Promise::DEFAULT_PROMISE_TIMEOUT)
|
102
|
+
promise.reload
|
103
|
+
|
104
|
+
expect(promise.status).to be false
|
105
|
+
expect(promise.promise_type).to eq 'delorean'
|
106
|
+
expect(promise.result['error']).to eq 'I had an error'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'ruby' do
|
111
|
+
let(:user) { Marty::User.find_by(login: 'marty') }
|
112
|
+
|
113
|
+
it 'processes result with regular attrs' do
|
114
|
+
Marty::Promises::Ruby::Create.call(
|
115
|
+
module_name: 'Gemini::BudCategory',
|
116
|
+
method_name: 'create_from_promise_regular_attrs',
|
117
|
+
method_args: ['test name', 1],
|
118
|
+
params: {
|
119
|
+
p_title: 'test_title',
|
120
|
+
_user_id: user.id,
|
121
|
+
p_hook: Gemini::PromiseHook::TestHook
|
122
|
+
}
|
123
|
+
)
|
124
|
+
|
125
|
+
promise = Marty::Promise.where(promise_type: 'ruby').last
|
126
|
+
promise.wait_for_result(Marty::Promise::DEFAULT_PROMISE_TIMEOUT)
|
127
|
+
|
128
|
+
promise.reload
|
129
|
+
|
130
|
+
bud_category = Gemini::BudCategory.order(:id).last
|
131
|
+
expect(bud_category.name).to eq 'test name'
|
132
|
+
|
133
|
+
sleep 0.1 # Wait while hooks are executed after Promise was updated
|
134
|
+
log = Marty::Log.find_by(message_type: 'TestHook')
|
135
|
+
|
136
|
+
expect(promise.status).to be true
|
137
|
+
expect(promise.promise_type).to eq 'ruby'
|
138
|
+
expect(promise.result['result']).to eq bud_category.id
|
139
|
+
expect(log.message).to eq 'was called'
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'processes result with keyword attrs' do
|
143
|
+
Marty::Promises::Ruby::Create.call(
|
144
|
+
module_name: 'Gemini::BudCategory',
|
145
|
+
method_name: 'create_from_promise_keyword_attrs',
|
146
|
+
method_args: [group_id: 1, name: 'test name 2'],
|
147
|
+
params: {
|
148
|
+
_user_id: user.id,
|
149
|
+
}
|
150
|
+
)
|
151
|
+
|
152
|
+
promise = Marty::Promise.where(promise_type: 'ruby').last
|
153
|
+
promise.wait_for_result(Marty::Promise::DEFAULT_PROMISE_TIMEOUT)
|
154
|
+
|
155
|
+
promise.reload
|
156
|
+
|
157
|
+
bud_category = Gemini::BudCategory.order(:id).last
|
158
|
+
expect(bud_category.name).to eq 'test name 2'
|
159
|
+
|
160
|
+
expect(promise.status).to be true
|
161
|
+
expect(promise.promise_type).to eq 'ruby'
|
162
|
+
expect(promise.result['result']).to eq bud_category.id
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'processes result with mixed attrs' do
|
166
|
+
Marty::Promises::Ruby::Create.call(
|
167
|
+
module_name: 'Gemini::BudCategory',
|
168
|
+
method_name: 'create_from_promise_mixed_attrs',
|
169
|
+
method_args: ['test name 3', { group_id: 1 }],
|
170
|
+
params: {
|
171
|
+
_user_id: user.id,
|
172
|
+
}
|
173
|
+
)
|
174
|
+
|
175
|
+
promise = Marty::Promise.where(promise_type: 'ruby').last
|
176
|
+
promise.wait_for_result(Marty::Promise::DEFAULT_PROMISE_TIMEOUT)
|
177
|
+
|
178
|
+
promise.reload
|
179
|
+
|
180
|
+
bud_category = Gemini::BudCategory.order(:id).last
|
181
|
+
expect(bud_category.name).to eq 'test name 3'
|
182
|
+
|
183
|
+
expect(promise.status).to be true
|
184
|
+
expect(promise.promise_type).to eq 'ruby'
|
185
|
+
expect(promise.result['result']).to eq bud_category.id
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'fails on exception' do
|
189
|
+
Marty::Promises::Ruby::Create.call(
|
190
|
+
module_name: 'Gemini::BudCategory',
|
191
|
+
method_name: 'create_from_promise_error',
|
192
|
+
method_args: [],
|
193
|
+
params: {
|
194
|
+
_user_id: user.id,
|
195
|
+
}
|
196
|
+
)
|
197
|
+
|
198
|
+
promise = Marty::Promise.where(promise_type: 'ruby').last
|
199
|
+
promise.wait_for_result(Marty::Promise::DEFAULT_PROMISE_TIMEOUT)
|
200
|
+
|
201
|
+
promise.reload
|
202
|
+
|
203
|
+
expect(promise.status).to be false
|
204
|
+
expect(promise.promise_type).to eq 'ruby'
|
205
|
+
expect(promise.result['error']).to eq 'Something went wrong'
|
206
|
+
expect(promise.result['backtrace']).to_not be_empty
|
207
|
+
end
|
208
|
+
end
|
63
209
|
end
|
@@ -150,7 +150,7 @@ module Marty; module RSpec; module Components
|
|
150
150
|
def select_row_range(st, en)
|
151
151
|
resid = run_js(<<-JS, 10.0)
|
152
152
|
#{ext_var(grid, 'grid')}
|
153
|
-
grid.getSelectionModel().selectRange(#{st-1}, #{en-1});
|
153
|
+
grid.getSelectionModel().selectRange(#{st - 1}, #{en - 1});
|
154
154
|
JS
|
155
155
|
wait_for_ajax
|
156
156
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: marty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.6.
|
4
|
+
version: 2.6.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arman Bostani
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date: 2019-
|
17
|
+
date: 2019-05-02 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: pg
|
@@ -311,6 +311,7 @@ files:
|
|
311
311
|
- app/models/marty/delorean_rule.rb
|
312
312
|
- app/models/marty/enum.rb
|
313
313
|
- app/models/marty/enum_event_operation.rb
|
314
|
+
- app/models/marty/enum_promise_type.rb
|
314
315
|
- app/models/marty/event.rb
|
315
316
|
- app/models/marty/grid_index_boolean.rb
|
316
317
|
- app/models/marty/grid_index_int4range.rb
|
@@ -332,6 +333,10 @@ files:
|
|
332
333
|
- app/models/marty/user.rb
|
333
334
|
- app/models/marty/user_role.rb
|
334
335
|
- app/models/marty/vw_promise.rb
|
336
|
+
- app/services/marty/promises/delorean.rb
|
337
|
+
- app/services/marty/promises/delorean/create.rb
|
338
|
+
- app/services/marty/promises/ruby.rb
|
339
|
+
- app/services/marty/promises/ruby/create.rb
|
335
340
|
- app/views/layouts/marty/application.html.erb
|
336
341
|
- app/views/marty/diagnostic/diag.html.erb
|
337
342
|
- app/views/marty/diagnostic/op.html.erb
|
@@ -381,6 +386,8 @@ files:
|
|
381
386
|
- db/migrate/411_create_vw_promises.rb
|
382
387
|
- db/migrate/500_add_api_class_to_marty_api_config.rb
|
383
388
|
- db/migrate/501_create_dg_plpgsql_v1_fns.rb
|
389
|
+
- db/migrate/502_add_promise_type_enum.rb
|
390
|
+
- db/migrate/503_add_promise_type_to_promises.rb
|
384
391
|
- db/seeds.rb
|
385
392
|
- db/sql/lookup_grid_distinct_v1.sql
|
386
393
|
- db/sql/query_grid_dir_v1.sql
|
@@ -411,6 +418,7 @@ files:
|
|
411
418
|
- lib/marty/permissions.rb
|
412
419
|
- lib/marty/promise_job.rb
|
413
420
|
- lib/marty/promise_proxy.rb
|
421
|
+
- lib/marty/promise_ruby_job.rb
|
414
422
|
- lib/marty/railtie.rb
|
415
423
|
- lib/marty/relation.rb
|
416
424
|
- lib/marty/rpc_call.rb
|
@@ -537,6 +545,7 @@ files:
|
|
537
545
|
- spec/dummy/lib/assets/.gitkeep
|
538
546
|
- spec/dummy/lib/class_list.rb
|
539
547
|
- spec/dummy/lib/gemini/my_rule_script_set.rb
|
548
|
+
- spec/dummy/lib/gemini/promise_hook/test_hook.rb
|
540
549
|
- spec/dummy/lib/gemini/xyz_rule_script_set.rb
|
541
550
|
- spec/dummy/log/.gitkeep
|
542
551
|
- spec/dummy/public/400.html
|
@@ -1551,6 +1560,7 @@ files:
|
|
1551
1560
|
- spec/dummy/tmp/.gitkeep
|
1552
1561
|
- spec/features/auth_app_spec.rb
|
1553
1562
|
- spec/features/data_import_spec.rb
|
1563
|
+
- spec/features/endpoint_access.rb
|
1554
1564
|
- spec/features/enum_spec.rb
|
1555
1565
|
- spec/features/javascripts/job_dashboard_live_search.js.coffee
|
1556
1566
|
- spec/features/javascripts/login.js.coffee
|