marty 6.1.0 → 8.0.0

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