marty 6.1.0 → 8.0.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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.gitlab-ci.yml +17 -3
  4. data/.rubocop.yml +5 -0
  5. data/.rubocop_todo.yml +3 -2
  6. data/Gemfile +2 -1
  7. data/app/assets/javascripts/marty/extjs/extensions/marty.js +23 -1
  8. data/app/assets/stylesheets/marty/application.css +4 -1
  9. data/app/assets/stylesheets/marty/dark_mode.css +17 -0
  10. data/app/components/marty/auth_app.rb +10 -1
  11. data/app/components/marty/auth_app/client/auth_app.js +4 -0
  12. data/app/components/marty/extras/layout.rb +2 -2
  13. data/app/components/marty/extras/misc.rb +1 -1
  14. data/app/components/marty/log_view.rb +0 -1
  15. data/app/components/marty/main_auth_app.rb +9 -12
  16. data/app/components/marty/promise_view.rb +5 -0
  17. data/app/components/marty/promise_view/client/promise_view.js +11 -0
  18. data/app/components/marty/schedule_jobs_dashboard.rb +30 -96
  19. data/app/components/marty/schedule_jobs_grid.rb +118 -0
  20. data/app/controllers/marty/application_controller.rb +6 -2
  21. data/app/models/marty/base.rb +48 -48
  22. data/app/models/marty/config.rb +14 -2
  23. data/app/models/marty/user.rb +12 -0
  24. data/app/services/marty/background_job/fetch_missing_in_schedule_cron_jobs.rb +19 -0
  25. data/app/services/marty/enums/report.rb +18 -0
  26. data/app/services/marty/promises/delorean/create.rb +16 -27
  27. data/app/services/marty/promises/ruby/create.rb +10 -1
  28. data/app/views/layouts/marty/application.html.erb +4 -1
  29. data/app/views/marty/diagnostic/op.html.erb +3 -1
  30. data/config/locales/en.yml +2 -0
  31. data/db/migrate/512_add_promise_priority.rb +9 -0
  32. data/db/migrate/513_add_priority_to_promise_view.rb +44 -0
  33. data/db/migrate/514_remove_marty_events.rb +13 -0
  34. data/delorean/enum_report.dl +11 -0
  35. data/delorean/table_report.dl +7 -0
  36. data/docker-compose.dummy.yml +7 -4
  37. data/lib/marty.rb +5 -2
  38. data/lib/marty/api/base.rb +15 -9
  39. data/lib/marty/cache_adapters.rb +2 -0
  40. data/lib/marty/cache_adapters/mcfly_ruby_cache.rb +1 -5
  41. data/lib/marty/cache_adapters/memory_and_redis.rb +93 -0
  42. data/lib/marty/cache_adapters/redis.rb +63 -0
  43. data/lib/marty/delayed_job/scheduled_job_plugin.rb +33 -0
  44. data/lib/marty/diagnostic/database.rb +1 -2
  45. data/lib/marty/logger.rb +50 -17
  46. data/lib/marty/monkey.rb +26 -6
  47. data/lib/marty/promise_ruby_job.rb +2 -0
  48. data/lib/marty/rails_app.rb +29 -0
  49. data/lib/marty/railtie.rb +1 -0
  50. data/lib/marty/version.rb +1 -1
  51. data/marty.gemspec +1 -0
  52. data/spec/controllers/job_controller_spec.rb +2 -2
  53. data/spec/dummy/app/components/gemini/cm_auth_app.rb +12 -0
  54. data/spec/dummy/app/components/gemini/simple_view.rb +17 -0
  55. data/spec/dummy/app/jobs/test_failing_job.rb +14 -0
  56. data/spec/dummy/app/models/gemini/helper.rb +90 -1
  57. data/spec/dummy/config/application.rb +1 -0
  58. data/spec/dummy/db/migrate/20191101132729_add_activity_flag_to_simple.rb +10 -0
  59. data/spec/dummy/delorean/blame_report.dl +1 -0
  60. data/spec/dummy/delorean/enum_report.dl +1 -0
  61. data/spec/dummy/delorean/marty_fields.dl +1 -0
  62. data/spec/dummy/delorean/table_report.dl +1 -0
  63. data/spec/features/data_blame_report_spec.rb +66 -0
  64. data/spec/features/data_grid_spec.rb +1 -1
  65. data/spec/features/enum_values_report_spec.rb +76 -0
  66. data/spec/features/inline_editing_spec.rb +33 -0
  67. data/spec/features/rule_spec.rb +1 -1
  68. data/spec/features/schedule_jobs_dashboard_spec.rb +1 -1
  69. data/spec/features/scripting_spec.rb +1 -1
  70. data/spec/features/user_list_report_spec.rb +74 -0
  71. data/spec/fixtures/misc/struct_compare_tests.txt +15 -5
  72. data/spec/job_helper.rb +39 -0
  73. data/spec/jobs/cron_job_spec.rb +91 -0
  74. data/spec/lib/mcfly_model_spec.rb +9 -0
  75. data/spec/models/promise_spec.rb +168 -1
  76. data/spec/other/diagnostic/delayed_job_workers_spec.rb +1 -1
  77. data/spec/performance/caching_spec.rb +99 -0
  78. data/spec/services/background_job/fetch_missing_in_schedule_cron_jobs_spec.rb +34 -0
  79. data/spec/support/delayed_job_helpers.rb +3 -3
  80. data/spec/support/shared_connection.rb +9 -1
  81. data/spec/support/structure_compare.rb +19 -3
  82. metadata +39 -6
  83. data/app/components/marty/event_view.rb +0 -129
  84. data/app/models/marty/event.rb +0 -317
  85. data/spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb +0 -8
  86. data/spec/dummy/delorean/blame_report.dl +0 -268
  87. data/spec/dummy/delorean/marty_fields.dl +0 -63
  88. data/spec/dummy/delorean/table_report.dl +0 -34
  89. data/spec/models/event_spec.rb +0 -272
@@ -0,0 +1,118 @@
1
+ class Marty::ScheduleJobsGrid < Marty::Grid
2
+ ACCESSIBLE_BY = [:admin]
3
+
4
+ has_marty_permissions(
5
+ read: ACCESSIBLE_BY,
6
+ create: ACCESSIBLE_BY,
7
+ update: ACCESSIBLE_BY,
8
+ delete: ACCESSIBLE_BY,
9
+ destroy: ACCESSIBLE_BY,
10
+ job_run: ACCESSIBLE_BY,
11
+ edit_window__edit_form__submit: ACCESSIBLE_BY,
12
+ add_window__add_form__submit: ACCESSIBLE_BY
13
+ )
14
+
15
+ def configure(c)
16
+ super
17
+
18
+ c.title ||= I18n.t('schedule_jobs_dashboard_view_title', default: 'Schedule Jobs Dashboard')
19
+ c.model = 'Marty::BackgroundJob::Schedule'
20
+ c.paging = :buffered
21
+ c.editing = :in_form
22
+ c.multi_select = false
23
+
24
+ c.attributes = [
25
+ :job_class,
26
+ :cron,
27
+ :state
28
+ ]
29
+ end
30
+
31
+ def default_context_menu
32
+ []
33
+ end
34
+
35
+ def default_bbar
36
+ super + [:do_job_run]
37
+ end
38
+
39
+ attribute :job_class do |c|
40
+ c.width = 400
41
+ end
42
+
43
+ attribute :cron do |c|
44
+ c.width = 400
45
+ end
46
+
47
+ attribute :state do |c|
48
+ c.width = 150
49
+ editor_config = {
50
+ trigger_action: :all,
51
+ xtype: :combo,
52
+ store: Marty::BackgroundJob::Schedule::ALL_STATES,
53
+ forceSelection: true,
54
+ }
55
+
56
+ c.column_config = { editor: editor_config }
57
+ c.field_config = editor_config
58
+ end
59
+
60
+ action :do_job_run do |a|
61
+ a.text = 'Run'
62
+ a.tooltip = 'Run'
63
+ a.icon_cls = 'fa fa-play glyph'
64
+ a.disabled = true
65
+ end
66
+
67
+ endpoint :edit_window__edit_form__submit do |params|
68
+ result = super(params)
69
+ next result if result.empty?
70
+
71
+ obj_hash = result.first
72
+ Marty::BackgroundJob::UpdateSchedule.call(id: obj_hash['id'], job_class: obj_hash['job_class'])
73
+
74
+ result
75
+ end
76
+
77
+ endpoint :add_window__add_form__submit do |params|
78
+ result = super(params)
79
+ next result if result.empty?
80
+
81
+ obj_hash = result.first
82
+ Marty::BackgroundJob::UpdateSchedule.call(id: obj_hash['id'], job_class: obj_hash['job_class'])
83
+
84
+ result
85
+ end
86
+
87
+ endpoint :multiedit_window__multiedit_form__submit do |_params|
88
+ client.netzke_notify 'Multiediting is disabled for cron schedules'
89
+ end
90
+
91
+ endpoint :destroy do |params|
92
+ res = params.each_with_object({}) do |id, hash|
93
+ job_class = model.find_by(id: id)&.job_class
94
+ result = super([id])
95
+
96
+ # Do nothing If it wasn't destroyed
97
+ next hash.merge(result) unless result[id.to_i] == 'ok'
98
+
99
+ Marty::BackgroundJob::UpdateSchedule.call(id: id, job_class: job_class)
100
+ hash.merge(result)
101
+ end
102
+
103
+ res
104
+ end
105
+
106
+ endpoint :job_run do
107
+ begin
108
+ s = Marty::BackgroundJob::Schedule.find(client_config['selected'])
109
+ klass = s.job_class
110
+ klass.constantize.new.perform
111
+ rescue StandardError => e
112
+ next client.netzke_notify(e.message)
113
+ end
114
+ client.netzke_notify("#{klass.demodulize} ran successfully.")
115
+ end
116
+ end
117
+
118
+ ScheduleJobsGrid = Marty::ScheduleJobsGrid
@@ -113,8 +113,8 @@ class Marty::ApplicationController < ActionController::Base
113
113
  end
114
114
 
115
115
  def failed_authentication(login)
116
- logger.info("Failed authentication for '#{login}' " +
117
- "from #{request.remote_ip} at #{Time.now.utc}")
116
+ logger.info("Failed authentication for '#{login}' " +
117
+ "from #{request.remote_ip} at #{Time.now.utc}")
118
118
  end
119
119
 
120
120
  def successful_authentication(user)
@@ -122,4 +122,8 @@ class Marty::ApplicationController < ActionController::Base
122
122
  "from #{request.remote_ip} at #{Time.now.utc}")
123
123
  set_user(user)
124
124
  end
125
+
126
+ def toggle_dark_mode
127
+ cookies[:dark_mode] = cookies[:dark_mode] != 'true'
128
+ end
125
129
  end
@@ -2,62 +2,62 @@ class Marty::Base < ActiveRecord::Base
2
2
  self.table_name_prefix = 'marty_'
3
3
  self.abstract_class = true
4
4
 
5
- def self.mcfly_pt(pt)
6
- tb = table_name
7
- where("#{tb}.obsoleted_dt >= ? AND #{tb}.created_dt < ?", pt, pt)
8
- end
9
-
10
5
  class << self
11
6
  attr_accessor :struct_attrs
12
- end
13
7
 
14
- def self.get_struct_attrs
15
- self.struct_attrs ||=
16
- attribute_names - Mcfly::COLUMNS.to_a -
17
- (const_defined?('MCFLY_UNIQUENESS') &&
18
- const_get('MCFLY_UNIQUENESS') || []).map(&:to_s)
19
- end
8
+ def get_struct_attrs
9
+ self.struct_attrs ||=
10
+ attribute_names - Mcfly::COLUMNS.to_a -
11
+ (const_defined?('MCFLY_UNIQUENESS') &&
12
+ const_get('MCFLY_UNIQUENESS') || []).map(&:to_s)
13
+ end
20
14
 
21
- def self.get_final_attrs
22
- final_attrs = get_struct_attrs
23
- return final_attrs if final_attrs.present?
24
-
25
- # otherwise raise with error line
26
- raise "Marty::Base: no attributes for #{self}"
27
-
28
- # for more detailed debugging use this code instead
29
- # st = caller.detect{|s|s.starts_with?('DELOREAN__')}
30
- # re = /DELOREAN__([A-Z][a-zA-Z0-9]*)[:]([0-9]+)[:]in `([a-z_0-9]+)__D'/
31
- # m = re.match(st)
32
- # if !m
33
- # st = "No attributes #{st} #{self}"
34
- # puts st unless File.readlines(Rails.root.join('tmp','dlchk')).
35
- # map(&:chop).detect{|l|l==st}
36
- # else
37
- # loc = "#{m[1]}::#{self}::#{m[2]}"
38
- # str = "*** No attributes %-40s %-20s %s" % [loc, m[3], attr]
39
- # puts str unless File.readlines(Rails.root.join('tmp','dlchk')).
40
- # map(&:chop).detect{|l|l==str}
41
- # end
42
- end
15
+ def mcfly_pt(pt)
16
+ tb = table_name
17
+ where("#{tb}.obsoleted_dt >= ? AND #{tb}.created_dt < ?", pt, pt)
18
+ end
43
19
 
44
- def self.make_hash(inst)
45
- fa = get_final_attrs
46
- inst.attributes.slice(*fa)
47
- end
20
+ def get_final_attrs
21
+ final_attrs = get_struct_attrs
22
+ return final_attrs if final_attrs.present?
23
+
24
+ # otherwise raise with error line
25
+ raise "Marty::Base: no attributes for #{self}"
26
+
27
+ # for more detailed debugging use this code instead
28
+ # st = caller.detect{|s|s.starts_with?('DELOREAN__')}
29
+ # re = /DELOREAN__([A-Z][a-zA-Z0-9]*)[:]([0-9]+)[:]in `([a-z_0-9]+)__D'/
30
+ # m = re.match(st)
31
+ # if !m
32
+ # st = "No attributes #{st} #{self}"
33
+ # puts st unless File.readlines(Rails.root.join('tmp','dlchk')).
34
+ # map(&:chop).detect{|l|l==st}
35
+ # else
36
+ # loc = "#{m[1]}::#{self}::#{m[2]}"
37
+ # str = "*** No attributes %-40s %-20s %s" % [loc, m[3], attr]
38
+ # puts str unless File.readlines(Rails.root.join('tmp','dlchk')).
39
+ # map(&:chop).detect{|l|l==str}
40
+ # end
41
+ end
48
42
 
49
- def self.make_openstruct(inst)
50
- return nil unless inst
43
+ def make_hash(inst)
44
+ fa = get_final_attrs
45
+ inst.attributes.slice(*fa)
46
+ end
51
47
 
52
- fa = get_final_attrs
53
- os = OpenStruct.new(inst.attributes.slice(*fa))
54
- if self == Marty::DataGrid
55
- def os.lookup_grid_distinct_entry(pt, params)
56
- dgh = to_h.stringify_keys.slice(
57
- 'id', 'group_id', 'created_dt', 'metadata', 'data_type')
58
- Marty::DataGrid.lookup_grid_distinct_entry_h(pt, params, dgh)
48
+ def make_openstruct(inst)
49
+ return nil unless inst
50
+
51
+ fa = get_final_attrs
52
+ os = OpenStruct.new(inst.attributes.slice(*fa))
53
+ if self == Marty::DataGrid
54
+ def os.lookup_grid_distinct_entry(pt, params)
55
+ dgh = to_h.stringify_keys.slice(
56
+ 'id', 'group_id', 'created_dt', 'metadata', 'data_type')
57
+ Marty::DataGrid.lookup_grid_distinct_entry_h(pt, params, dgh)
58
+ end
59
59
  end
60
+ os
60
61
  end
61
- os
62
62
  end
63
63
  end
@@ -23,6 +23,19 @@ class Marty::Config < Marty::Base
23
23
  self.value = [v]
24
24
  end
25
25
 
26
+ def self.fetch(*args)
27
+ unless (1..2).cover?(args.size)
28
+ raise ArgumentError, 'wrong number of arguments '\
29
+ "(given #{args.size}, expected 1..2)"
30
+ end
31
+
32
+ entry = find_by_key(args[0])
33
+ return entry.get_value if entry
34
+ return args[1] if args.size > 1
35
+
36
+ raise KeyError, "key not found: \"#{args[0]}\""
37
+ end
38
+
26
39
  def self.[]=(key, value)
27
40
  entry = find_by_key(key)
28
41
  if !entry
@@ -36,8 +49,7 @@ class Marty::Config < Marty::Base
36
49
  end
37
50
 
38
51
  def self.[](key)
39
- entry = find_by_key(key)
40
- entry and entry.get_value
52
+ fetch(key, nil)
41
53
  end
42
54
 
43
55
  def self.del(key)
@@ -102,6 +102,18 @@ class Marty::User < Marty::Base
102
102
  mr.any? { |ur| ur.role == role }
103
103
  end
104
104
 
105
+ delorean_fn :export_for_report do
106
+ Marty::User.includes(:user_roles).map do |user|
107
+ {
108
+ 'login' => user.login,
109
+ 'firstname' => user.firstname,
110
+ 'lastname' => user.lastname,
111
+ 'active' => user.active,
112
+ 'roles' => user.roles.sort.join(', ')
113
+ }
114
+ end
115
+ end
116
+
105
117
  private
106
118
 
107
119
  def verify_changes
@@ -0,0 +1,19 @@
1
+ module Marty
2
+ module BackgroundJob
3
+ module FetchMissingInScheduleCronJobs
4
+ def self.call
5
+ in_dashboard = Marty::BackgroundJob::Schedule.pluck(:job_class)
6
+
7
+ names_conditions = in_dashboard.map do |job_class_name|
8
+ "%job_class: #{job_class_name}\n%"
9
+ end
10
+
11
+ Delayed::Job.
12
+ where.not(cron: nil).
13
+ where.not(cron: '').
14
+ where("handler ILIKE '%job_class:%'").
15
+ where.not('handler ILIKE ANY ( array[?] )', names_conditions)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Marty
2
+ module Enums
3
+ module Report
4
+ extend Delorean::Functions
5
+
6
+ delorean_fn :call do
7
+ ActiveRecord::Base.connection.execute(
8
+ 'SELECT
9
+ t.typname AS enum_name,
10
+ e.enumlabel AS value
11
+ FROM pg_type t
12
+ JOIN pg_enum e ON t.oid = e.enumtypid
13
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace'
14
+ ).to_a
15
+ end
16
+ end
17
+ end
18
+ end
@@ -5,14 +5,24 @@ module Marty
5
5
  def self.call(params:, script:, node_name:, attr:, args:, tag:)
6
6
  default_timeout = Marty::Promise::DEFAULT_PROMISE_TIMEOUT
7
7
 
8
- title = params['p_title'] || "#{script}::#{node_name.demodulize}"
9
- timeout = params['p_timeout'] || default_timeout
10
- hook = params['p_hook']
8
+ promise_params = params.with_indifferent_access
11
9
 
10
+ title = promise_params['p_title'] || "#{script}::#{node_name.demodulize}"
11
+ timeout = promise_params['p_timeout'] || default_timeout
12
+ hook = promise_params['p_hook']
13
+
14
+ default_priority = 0
15
+ pid = promise_params[:_parent_id]
16
+ if pid
17
+ ppr = Marty::Promise.find_by(id: pid)
18
+ default_priority = ppr.priority if ppr
19
+ end
20
+ priority = promise_params['p_priority'] || default_priority
12
21
  promise = Marty::Promise.create(
13
22
  title: title,
14
- user_id: params[:_user_id],
15
- parent_id: params[:_parent_id],
23
+ user_id: promise_params[:_user_id],
24
+ parent_id: promise_params[:_parent_id],
25
+ priority: priority,
16
26
  promise_type: 'delorean'
17
27
  )
18
28
 
@@ -30,7 +40,7 @@ module Marty
30
40
  hook
31
41
  )
32
42
 
33
- job = Delayed::Job.enqueue(promise_job)
43
+ job = Delayed::Job.enqueue(promise_job, priority: priority)
34
44
  rescue StandardError => e
35
45
  # log "CALLERR #{exc}"
36
46
  res = ::Delorean::Engine.grok_runtime_exception(e)
@@ -46,27 +56,6 @@ module Marty
46
56
  promise.job_id = job.id
47
57
  promise.save!
48
58
 
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
59
  Marty::PromiseProxy.new(promise.id, timeout, attr)
71
60
  end
72
61
  end
@@ -11,10 +11,19 @@ module Marty
11
11
  timeout = promise_params['p_timeout'] || default_timeout
12
12
  hook = promise_params['p_hook']
13
13
 
14
+ default_priority = 0
15
+ pid = promise_params[:_parent_id]
16
+ if pid
17
+ ppr = Marty::Promise.find_by(id: pid)
18
+ default_priority = ppr.priority if ppr
19
+ end
20
+
21
+ priority = promise_params['p_priority'] || default_priority
14
22
  promise = Marty::Promise.create(
15
23
  title: title,
16
24
  user_id: promise_params[:_user_id],
17
25
  parent_id: promise_params[:_parent_id],
26
+ priority: priority,
18
27
  promise_type: 'ruby'
19
28
  )
20
29
 
@@ -28,7 +37,7 @@ module Marty
28
37
  hook
29
38
  )
30
39
 
31
- job = Delayed::Job.enqueue(promise_job)
40
+ job = Delayed::Job.enqueue(promise_job, priority: priority)
32
41
  rescue StandardError => e
33
42
  res = { 'error' => e.message }
34
43
  promise.set_start
@@ -5,11 +5,14 @@
5
5
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
6
6
  integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ"
7
7
  crossorigin="anonymous">
8
- <title><%= Rails.application.class.parent %></title>
8
+ <title><%= ::Marty::RailsApp.application_name %></title>
9
9
  <%= load_netzke theme: Rails.configuration.marty.extjs_theme %>
10
10
  <%= csrf_meta_tag %>
11
11
  <%= javascript_include_tag 'marty/application' %>
12
12
  <%= stylesheet_link_tag 'marty/application' %>
13
+ <% if cookies[:dark_mode] == 'true' %>
14
+ <%= stylesheet_link_tag 'marty/dark_mode', media: 'all', 'data-turbolinks-track': 'reload' %>
15
+ <% end %>
13
16
  </head>
14
17
  <body>
15
18
  <%= yield %>