marty 2.6.4 → 2.6.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|