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
@@ -52,7 +52,9 @@
52
52
  <body>
53
53
  <div style="text-align:center">
54
54
  <h1 class="application <%= 'error' unless @result['errors'].empty? %>">
55
- <%=ENV['DIAG_TITLE'] || Rails.application.class.parent_name%> Diagnostic
55
+ <%=
56
+ ENV['DIAG_TITLE'] || ::Marty::RailsApp.application_name
57
+ %> Diagnostic
56
58
  </h1>
57
59
  <h3><i><%= DateTime.now %></i></h3>
58
60
  <%== @result['display'] %>
@@ -136,6 +136,8 @@ en:
136
136
  end_dt: End Date
137
137
  user_login: User Login
138
138
  cformat: Format
139
+ schedule_dashboard:
140
+ warnings: Warnings
139
141
 
140
142
  data_import_view:
141
143
  import: Import
@@ -0,0 +1,9 @@
1
+ class AddPromisePriority < ActiveRecord::Migration[4.2]
2
+ def up
3
+ add_column :marty_promises, :priority, :integer
4
+ end
5
+
6
+ def down
7
+ remove_column :marty_promises, :priority
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ class AddPriorityToPromiseView < ActiveRecord::Migration[4.2]
2
+ def up
3
+ execute <<~SQL
4
+ DROP VIEW IF EXISTS marty_vw_promises;
5
+ CREATE OR REPLACE VIEW marty_vw_promises
6
+ AS
7
+ SELECT
8
+ id,
9
+ title,
10
+ user_id,
11
+ cformat,
12
+ parent_id,
13
+ job_id,
14
+ status,
15
+ start_dt,
16
+ end_dt,
17
+ priority
18
+ FROM marty_promises;
19
+
20
+ GRANT SELECT ON marty_vw_promises TO public;
21
+ SQL
22
+ end
23
+
24
+ def down
25
+ execute <<~SQL
26
+ DROP VIEW IF EXISTS marty_vw_promises;
27
+ CREATE OR REPLACE VIEW marty_vw_promises
28
+ AS
29
+ SELECT
30
+ id,
31
+ title,
32
+ user_id,
33
+ cformat,
34
+ parent_id,
35
+ job_id,
36
+ status,
37
+ start_dt,
38
+ end_dt
39
+ FROM marty_promises;
40
+
41
+ GRANT SELECT ON marty_vw_promises TO public;
42
+ SQL
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ class RemoveMartyEvents < ActiveRecord::Migration[4.2]
2
+ def up
3
+ drop_table :marty_events
4
+
5
+ execute <<-SQL
6
+ DROP TYPE enum_event_operations;
7
+ SQL
8
+ end
9
+
10
+ def down
11
+ announce("No-op on RemoveMartyEvents.down")
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ import MartyFields
2
+ import Styles
3
+
4
+ EnumValuesReport:
5
+ title = "Enum Values List"
6
+ roles = ['admin', 'dev', 'data_grid_editor']
7
+ raw = Marty::Enums::Report.call()
8
+ result = Marty::Helper.to_csv(raw)
9
+ form = []
10
+ format = "csv"
11
+
@@ -32,3 +32,10 @@ TableReport:
32
32
  ]
33
33
  format = "csv"
34
34
 
35
+ UserReport:
36
+ title = "User List"
37
+ roles = ['admin', 'user_manager']
38
+ raw = Marty::User.export_for_report()
39
+ result = Marty::Helper.to_csv(raw)
40
+ form = []
41
+ format = "csv"
@@ -17,9 +17,11 @@ services:
17
17
  - "HEADLESS=true"
18
18
  - "PGTZ=America/Los_Angeles"
19
19
  - "BUNDLER_VERSION=2.0.1"
20
+ - "MARTY_REDIS_URL=redis:6379/1"
20
21
  # env_file: ".rbenv-vars"
21
22
  depends_on:
22
23
  - "postgres"
24
+ - "redis"
23
25
  volumes:
24
26
  - .:/opt/app:delegated
25
27
  - '.bash_history.docker:/root/.bash_history'
@@ -34,12 +36,13 @@ services:
34
36
  volumes:
35
37
  - postgresql-data:/var/lib/postgresql/data
36
38
 
37
- bundle_box:
38
- image: busybox
39
+ redis:
40
+ image: 'redis:5.0.6-alpine'
39
41
  volumes:
40
- - /bundle_box
42
+ - redis-data:/data
41
43
 
42
44
  volumes:
43
- postgresql-data:
44
45
  bundle_box:
46
+ postgresql-data:
47
+ redis-data:
45
48
 
data/lib/marty.rb CHANGED
@@ -15,6 +15,7 @@ require 'marty/cache_adapters'
15
15
  require 'marty/monkey'
16
16
  require 'marty/promise_job'
17
17
  require 'marty/json_schema'
18
+ require 'marty/rails_app'
18
19
 
19
20
  # This does not get loaded in via bundler unless it is included in the
20
21
  # application's Gemfile. Requiring it here removes the need to add it
@@ -25,7 +26,9 @@ require 'delayed_cron_job'
25
26
  require 'pathname'
26
27
 
27
28
  module Marty
28
- def self.root
29
- Pathname.new(File.expand_path('..', __dir__))
29
+ class << self
30
+ def root
31
+ Pathname.new(File.expand_path('..', __dir__))
32
+ end
30
33
  end
31
34
  end
@@ -53,14 +53,19 @@ class Marty::Api::Base
53
53
  params = params.deep_dup
54
54
 
55
55
  schema_key = [params[:tag], params[:script], params[:node], params[:attr]]
56
- input_schema = nil
57
- begin
58
- # get_schema will either return a hash with the schema,
59
- # or a string with the error
60
- input_schema = @@schemas[schema_key] ||=
61
- Marty::JsonSchema.get_schema(*schema_key)
62
- rescue StandardError => e
63
- return { error: e.message }
56
+ input_schema = @@schemas[schema_key]
57
+ unless input_schema
58
+ begin
59
+ # get_schema will either return a hash with the schema,
60
+ # or a string with the error
61
+ result_schema = Marty::JsonSchema.get_schema(*schema_key)
62
+
63
+ # only store schema in cache when not error
64
+ @@schemas[schema_key] = result_schema if result_schema.is_a?(Hash)
65
+ input_schema = result_schema
66
+ rescue StandardError => e
67
+ return { error: e.message }
68
+ end
64
69
  end
65
70
 
66
71
  # if schema was found
@@ -165,7 +170,8 @@ class Marty::Api::Base
165
170
  def self.filter_hash hash, filter_params
166
171
  return unless hash
167
172
 
168
- pf = ActionDispatch::Http::ParameterFilter.new(filter_params)
173
+ pf_class = ::Marty::RailsApp.parameter_filter_class
174
+ pf = pf_class.new(filter_params)
169
175
  pf.filter(hash.stringify_keys)
170
176
  end
171
177
 
@@ -1,4 +1,6 @@
1
1
  require 'marty/cache_adapters/mcfly_ruby_cache'
2
+ require 'marty/cache_adapters/memory_and_redis'
3
+ require 'marty/cache_adapters/redis'
2
4
 
3
5
  module Marty
4
6
  module CacheAdapters
@@ -2,11 +2,7 @@ module Marty
2
2
  module CacheAdapters
3
3
  class McflyRubyCache < ::Delorean::Cache::Adapters::RubyCache
4
4
  def cache_item?(klass:, method_name:, args:)
5
- ts = args && args.first
6
-
7
- return false if Mcfly.is_infinity(ts)
8
-
9
- true
5
+ !Mcfly.is_infinity(args&.first)
10
6
  end
11
7
  end
12
8
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Marty
4
+ module CacheAdapters
5
+ class MemoryAndRedis < ::Delorean::Cache::Adapters::Base
6
+ POST = '__RedisCache'
7
+
8
+ def initialize(
9
+ size_per_class: 1000,
10
+ redis_url: Rails.application.config.marty.redis_url,
11
+ redis_expires_in: 48.hours
12
+ )
13
+ @size_per_class = size_per_class
14
+
15
+ @redis_adapter = ::Marty::CacheAdapters::Redis.new(
16
+ redis_url: redis_url,
17
+ expires_in: redis_expires_in
18
+ )
19
+
20
+ @memory_adapter = ::Marty::CacheAdapters::McflyRubyCache.new(
21
+ size_per_class: size_per_class
22
+ )
23
+ end
24
+
25
+ def cache_item(klass:, cache_key:, item:)
26
+ @redis_adapter.cache_item(
27
+ klass: klass,
28
+ cache_key: cache_key,
29
+ item: item
30
+ )
31
+
32
+ @memory_adapter.cache_item(
33
+ klass: klass,
34
+ cache_key: cache_key,
35
+ item: item
36
+ )
37
+ end
38
+
39
+ # When cache is found in local memory, we simply return the cached item.
40
+ # Otherwise we look into Redis, if item is cached there,
41
+ # we copy it to local memory to speed up future lookups.
42
+ def fetch_item(klass:, cache_key:, default: nil)
43
+ memory_item = @memory_adapter.fetch_item(
44
+ klass: klass,
45
+ cache_key: cache_key,
46
+ default: default
47
+ )
48
+
49
+ return memory_item if memory_item != default
50
+
51
+ redis_item = @redis_adapter.fetch_item(
52
+ klass: klass,
53
+ cache_key: cache_key,
54
+ default: default
55
+ )
56
+
57
+ return default if redis_item == default
58
+
59
+ @memory_adapter.cache_item(
60
+ klass: klass,
61
+ cache_key: cache_key,
62
+ item: redis_item
63
+ )
64
+
65
+ redis_item
66
+ end
67
+
68
+ def cache_key(klass:, method_name:, args:)
69
+ r = ["#{klass.name}#{POST}", method_name] + args.map do |arg|
70
+ arg.respond_to?(:id) ? arg.id : arg
71
+
72
+ arg
73
+ end.freeze
74
+
75
+ Marshal.dump r
76
+ end
77
+
78
+ def clear!(klass:)
79
+ @redis_adapter.clear!(klass: klass)
80
+ @memory_adapter.clear!(klass: klass)
81
+ end
82
+
83
+ def clear_all!
84
+ @redis_adapter.clear_all!
85
+ @memory_adapter.clear_all!
86
+ end
87
+
88
+ def cache_item?(klass:, method_name:, args:)
89
+ !Mcfly.is_infinity(args&.first)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis'
4
+
5
+ module Marty
6
+ module CacheAdapters
7
+ class Redis < ::Delorean::Cache::Adapters::Base
8
+ POST = '__RedisCache'
9
+
10
+ def initialize(
11
+ size_per_class: 1000,
12
+ redis_url: Rails.application.config.marty.redis_url,
13
+ expires_in: 48.hours
14
+ )
15
+ @redis = ::Redis.new(url: "redis://#{redis_url}")
16
+ @expires_in = expires_in
17
+ end
18
+
19
+ def cache_item(klass:, cache_key:, item:)
20
+ @redis.set(
21
+ cache_key,
22
+ Marshal.dump(item),
23
+ ex: @expires_in.seconds.to_i
24
+ )
25
+ end
26
+
27
+ def fetch_item(klass:, cache_key:, default: nil)
28
+ r = @redis.get(cache_key)
29
+
30
+ return default if r.nil?
31
+
32
+ Marshal.load(r)
33
+ end
34
+
35
+ def cache_key(klass:, method_name:, args:)
36
+ r = ["#{klass.name}#{POST}", method_name] + args.map do |arg|
37
+ next arg.id if arg.respond_to?(:id)
38
+
39
+ arg
40
+ end.freeze
41
+
42
+ Marshal.dump r
43
+ end
44
+
45
+ def clear!(klass:)
46
+ keys = @redis.keys("*#{klass.name}#{POST}*")
47
+ @redis.pipelined do
48
+ keys.each do |key|
49
+ @redis.del key
50
+ end
51
+ end
52
+ end
53
+
54
+ def clear_all!
55
+ @redis.flushall
56
+ end
57
+
58
+ def cache_item?(klass:, method_name:, args:)
59
+ !Mcfly.is_infinity(args&.first)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,33 @@
1
+ module Marty
2
+ module DelayedJob
3
+ class ScheduledJobPlugin < Delayed::Plugin
4
+ class << self
5
+ def cron?(job)
6
+ job.cron.present?
7
+ end
8
+ end
9
+
10
+ callbacks do |lifecycle|
11
+ # We want to nullify cron column if job schedule was turned off
12
+ # while the job was running, so it won't add a new record to
13
+ # delayed_jobs table
14
+ lifecycle.before(:error) do |worker, job, &block|
15
+ if cron?(job)
16
+ begin
17
+ job_class_str = job.handler.split("\n").find do |line|
18
+ line.include? 'job_class'
19
+ end
20
+ job_class_name = job_class_str.gsub('job_class:', '').strip
21
+ job_class = job_class_name.constantize
22
+ job.cron = job_class.cron_expression
23
+ rescue StandardError
24
+ end
25
+ else
26
+ # No cron job - proceed as normal
27
+ block.call(worker, job)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -22,8 +22,7 @@ module Marty::Diagnostic::Database
22
22
  def self.db_schema
23
23
  current = ActiveRecord::Migrator.current_version
24
24
  raise "Migration is needed.\nCurrent Version: #{current}" if
25
- Rails.version >= '5.2.0' &&
26
- ActiveRecord::Base.connection.migration_context.needs_migration?
25
+ ::Marty::RailsApp.needs_migration?
27
26
 
28
27
  current.to_s
29
28
  end
data/lib/marty/logger.rb CHANGED
@@ -1,27 +1,60 @@
1
1
  class Marty::Logger
2
2
  include Delorean::Model
3
3
 
4
- def self.method_missing(m, *args, &block)
5
- return super unless
6
- [:debug, :info, :warn, :error, :fatal, :unknown].include?(m)
7
-
8
- Marty::Util.logger.send(m, args[0]) if Marty::Util.logger.respond_to?(m)
9
- log(m, *args)
4
+ delorean_fn :dllog, sig: [2, 20] do |*args|
5
+ info args[0], args[1..-1]
10
6
  end
11
7
 
12
- def self.log(type, message, details = nil)
13
- Marty::Log.write_log(type, message, details)
14
- end
8
+ class << self
9
+ def log_event(event_name, *args)
10
+ if Marty::Util.logger.respond_to?(event_name)
11
+ Marty::Util.logger.send(
12
+ event_name,
13
+ args[0]
14
+ )
15
+ end
16
+
17
+ log(event_name, *args)
18
+ end
19
+
20
+ def debug(*args)
21
+ log_event(:debug, *args)
22
+ end
23
+
24
+ def info(*args)
25
+ log_event(:info, *args)
26
+ end
27
+
28
+ def warn(*args)
29
+ log_event(:warn, *args)
30
+ end
31
+
32
+ def error(*args)
33
+ log_event(:error, *args)
34
+ end
15
35
 
16
- def self.with_logging(error_message, error_data)
36
+ def fatal(*args)
37
+ log_event(:fatal, *args)
38
+ end
39
+
40
+ def unknown(*args)
41
+ log_event(:unknown, *args)
42
+ end
43
+
44
+ def log(type, message, details = nil)
45
+ Marty::Log.write_log(type, message, details)
46
+ end
47
+
48
+ def with_logging(error_message, error_data)
17
49
  yield
18
- rescue StandardError => e
19
- error(error_message, 'message' => e.message,
20
- 'data' => error_data)
21
- raise "#{error_message}: #{e.message}"
22
- end
50
+ rescue StandardError => e
51
+ error(
52
+ error_message,
53
+ 'message' => e.message,
54
+ 'data' => error_data
55
+ )
23
56
 
24
- delorean_fn :dllog, sig: [2, 20] do |*args|
25
- info args[0], args[1..-1]
57
+ raise "#{error_message}: #{e.message}"
58
+ end
26
59
  end
27
60
  end