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
@@ -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