marty 8.0.0 → 8.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +7 -0
  3. data/app/assets/javascripts/marty/cable.js +12 -0
  4. data/app/assets/stylesheets/marty/codemirror/notifications.css +4 -0
  5. data/app/channels/application_cable/channel.rb +4 -0
  6. data/app/channels/application_cable/connection.rb +17 -0
  7. data/app/channels/marty/notification_channel.rb +8 -0
  8. data/app/components/marty/auth_app.rb +54 -6
  9. data/app/components/marty/auth_app/client/auth_app.js +53 -0
  10. data/app/components/marty/main_auth_app.rb +47 -1
  11. data/app/components/marty/notifications/config_view.rb +56 -0
  12. data/app/components/marty/notifications/deliveries_view.rb +50 -0
  13. data/app/components/marty/notifications/grid_view.rb +56 -0
  14. data/app/components/marty/notifications/window.rb +23 -0
  15. data/app/components/marty/promise_view.rb +13 -1
  16. data/app/components/marty/promise_view/client/promise_view.js +18 -0
  17. data/app/components/marty/{schedule_jobs_dashboard/client/schedule_jobs_dashboard.js → schedule_jobs_grid/client/schedule_jobs_grid.js} +0 -0
  18. data/app/components/marty/simple_app/client/simple_app.js +5 -1
  19. data/app/components/marty/users/user_view.rb +177 -0
  20. data/app/controllers/marty/application_controller.rb +13 -0
  21. data/app/models/marty/data_grid.rb +1 -1
  22. data/app/models/marty/notifications.rb +4 -0
  23. data/app/models/marty/notifications/config.rb +25 -0
  24. data/app/models/marty/notifications/delivery.rb +56 -0
  25. data/app/models/marty/notifications/event_type.rb +11 -0
  26. data/app/models/marty/notifications/notification.rb +25 -0
  27. data/app/models/marty/promise.rb +1 -0
  28. data/app/models/marty/user.rb +14 -0
  29. data/app/services/marty/notifications/create.rb +39 -0
  30. data/app/services/marty/notifications/create_deliveries.rb +25 -0
  31. data/app/services/marty/notifications/process_delivery.rb +11 -0
  32. data/app/services/marty/notifications/processors/email.rb +13 -0
  33. data/app/services/marty/notifications/processors/sms.rb +13 -0
  34. data/app/services/marty/notifications/processors/web.rb +27 -0
  35. data/app/services/marty/promises/cancel.rb +37 -0
  36. data/app/services/marty/promises/delorean/create.rb +4 -0
  37. data/app/services/marty/promises/ruby/create.rb +4 -1
  38. data/app/views/marty/diagnostic/op.html.erb +4 -0
  39. data/db/migrate/514_remove_marty_events.rb +1 -1
  40. data/db/migrate/519_create_marty_notifications_event_types.rb +16 -0
  41. data/db/migrate/520_create_marty_notifications.rb +11 -0
  42. data/db/migrate/521_create_marty_notifications_deliveries.rb +22 -0
  43. data/db/migrate/522_create_marty_notifications_config.rb +21 -0
  44. data/db/sql/lookup_grid_distinct_v1.sql +6 -6
  45. data/lib/marty.rb +2 -0
  46. data/lib/marty/diagnostic/version.rb +36 -26
  47. data/lib/marty/engine.rb +7 -1
  48. data/lib/marty/mcfly_model.rb +27 -6
  49. data/lib/marty/railtie.rb +1 -0
  50. data/lib/marty/version.rb +1 -1
  51. data/marty.gemspec +3 -0
  52. data/spec/controllers/diagnostic/controller_spec.rb +1 -1
  53. data/spec/dummy/app/models/gemini/fannie_bup.rb +27 -13
  54. data/spec/dummy/app/models/gemini/helper.rb +62 -0
  55. data/spec/features/notifications_spec.rb +224 -0
  56. data/spec/job_helper.rb +14 -1
  57. data/spec/lib/mcfly_model_spec.rb +14 -7
  58. data/spec/models/promise_spec.rb +121 -0
  59. data/spec/services/notifications/create_spec.rb +82 -0
  60. metadata +73 -3
@@ -42,6 +42,7 @@ class Marty::Promise < Marty::Base
42
42
  def set_result(res)
43
43
  # log "SETRES #{Process.pid} #{self}"
44
44
 
45
+ reload
45
46
  # promise must have been started and not yet ended
46
47
  if !start_dt || end_dt || result != {}
47
48
  # log "SETERR #{Process.pid} #{self}"
@@ -9,6 +9,13 @@ class Marty::User < Marty::Base
9
9
 
10
10
  has_many :user_roles, dependent: :destroy
11
11
 
12
+ has_many(
13
+ :notification_deliveries,
14
+ class_name: '::Marty::Notifications::Delivery',
15
+ dependent: :destroy,
16
+ foreign_key: :recipient_id
17
+ )
18
+
12
19
  scope :active, -> { where(active: true) }
13
20
 
14
21
  validate :verify_changes
@@ -114,6 +121,13 @@ class Marty::User < Marty::Base
114
121
  end
115
122
  end
116
123
 
124
+ def unread_web_notifications_count
125
+ notification_deliveries.where(
126
+ delivery_type: :web,
127
+ state: [:sent]
128
+ ).count
129
+ end
130
+
117
131
  private
118
132
 
119
133
  def verify_changes
@@ -0,0 +1,39 @@
1
+ module Marty
2
+ module Notifications
3
+ module Create
4
+ extend Delorean::Functions
5
+
6
+ delorean_fn :call do |event_type:, text:|
7
+ notification, deliveries = ActiveRecord::Base.transaction do
8
+ notification = ::Marty::Notifications::Notification.create!(
9
+ event_type: event_type,
10
+ text: text,
11
+ state: :pending
12
+ )
13
+
14
+ # FIXME: We should consider processing deliveries in the background
15
+ deliveries = ::Marty::Notifications::CreateDeliveries.call(
16
+ notification: notification
17
+ )
18
+
19
+ [notification, deliveries]
20
+ end
21
+
22
+ deliveries.each do |delivery|
23
+ ::Marty::Notifications::ProcessDelivery.call(
24
+ delivery: delivery
25
+ )
26
+ end
27
+
28
+ notification.set_processed!
29
+
30
+ {
31
+ id: notification.id,
32
+ event_type: event_type,
33
+ text: text,
34
+ state: notification.state,
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module Marty
2
+ module Notifications
3
+ module CreateDeliveries
4
+ class << self
5
+ def call(notification:)
6
+ configs(notification).map do |config|
7
+ notification.deliveries.create!(
8
+ state: :pending,
9
+ delivery_type: config.delivery_type,
10
+ recipient: config.recipient,
11
+ text: config.text
12
+ )
13
+ end
14
+ end
15
+
16
+ def configs(notification)
17
+ Marty::Notifications::Config.where(
18
+ state: :on,
19
+ event_type: notification.event_type
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Marty
2
+ module Notifications
3
+ module ProcessDelivery
4
+ class << self
5
+ def call(delivery:)
6
+ delivery.processor.call(delivery: delivery)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Marty
2
+ module Notifications
3
+ module Processors
4
+ module Email
5
+ class << self
6
+ def call(delivery:)
7
+ raise 'Not implemented!'
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Marty
2
+ module Notifications
3
+ module Processors
4
+ module Sms
5
+ class << self
6
+ def call(delivery:)
7
+ raise 'Not implemented!'
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ module Marty
2
+ module Notifications
3
+ module Processors
4
+ module Web
5
+ class << self
6
+ def call(delivery:)
7
+ delivery.set_sent!
8
+ notify_websocket(delivery: delivery)
9
+ end
10
+
11
+ private
12
+
13
+ def notify_websocket(delivery:)
14
+ return unless Rails.application.config.marty.enable_action_cable
15
+
16
+ unread_notifications_count = delivery.recipient&.unread_web_notifications_count
17
+
18
+ ActionCable.server.broadcast(
19
+ "marty_notifications_#{delivery.recipient_id}",
20
+ unread_notifications_count: unread_notifications_count
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ module Marty::Promises
2
+ module Cancel
3
+ class << self
4
+ def call(id)
5
+ ids = get_all_ids(id)
6
+ promises = Marty::Promise.where(id: ids)
7
+ jobids = promises.map(&:job_id).compact.sort
8
+ Delayed::Job.where(id: jobids).destroy_all
9
+ promises.each do |p|
10
+ p.update!(status: false,
11
+ end_dt: p.end_dt || Time.zone.now,
12
+ result: p.result + { error: 'Cancelled' })
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ # Promises are nodes/leaves on a tree. Given a promise id
19
+ # from anywhere on the tree, find the ids of all promises
20
+ # on that tree.
21
+ def get_all_ids(id)
22
+ get_base = lambda do |pid|
23
+ p = Marty::Promise.find_by(id: pid)
24
+ p.parent_id ? get_base.call(p.parent_id) : pid
25
+ end
26
+ base_id = get_base.call(id)
27
+ get_children = lambda do |pid|
28
+ children = Marty::Promise.where(parent_id: pid).pluck(:id)
29
+ children.present? ?
30
+ (children + children.map { |cid| get_children.call(cid) }).flatten :
31
+ children
32
+ end
33
+ [base_id] + get_children.call(base_id)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -15,9 +15,13 @@ module Marty
15
15
  pid = promise_params[:_parent_id]
16
16
  if pid
17
17
  ppr = Marty::Promise.find_by(id: pid)
18
+ # make sure parent isn't cancelled
19
+ return if ppr&.result&.[]('error') == 'Cancelled'
20
+
18
21
  default_priority = ppr.priority if ppr
19
22
  end
20
23
  priority = promise_params['p_priority'] || default_priority
24
+
21
25
  promise = Marty::Promise.create(
22
26
  title: title,
23
27
  user_id: promise_params[:_user_id],
@@ -15,10 +15,13 @@ module Marty
15
15
  pid = promise_params[:_parent_id]
16
16
  if pid
17
17
  ppr = Marty::Promise.find_by(id: pid)
18
+ # make sure parent isn't cancelled
19
+ return if ppr&.result&.[]('error') == 'Cancelled'
20
+
18
21
  default_priority = ppr.priority if ppr
19
22
  end
20
-
21
23
  priority = promise_params['p_priority'] || default_priority
24
+
22
25
  promise = Marty::Promise.create(
23
26
  title: title,
24
27
  user_id: promise_params[:_user_id],
@@ -1,5 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
+ <% if cookies[:dark_mode] == 'true' %>
4
+ <%= stylesheet_link_tag 'marty/dark_mode', media: 'all', 'data-turbolinks-track': 'reload' %>
5
+ <% end %>
6
+
3
7
  <head>
4
8
  <title><%=Rails.application.class.parent_name%> Diagnostic</title>
5
9
  <style type="text/css">
@@ -3,7 +3,7 @@ class RemoveMartyEvents < ActiveRecord::Migration[4.2]
3
3
  drop_table :marty_events
4
4
 
5
5
  execute <<-SQL
6
- DROP TYPE enum_event_operations;
6
+ DROP TYPE IF EXISTS enum_event_operations;
7
7
  SQL
8
8
  end
9
9
 
@@ -0,0 +1,16 @@
1
+ class CreateMartyNotificationsEventTypes < ActiveRecord::Migration[4.2]
2
+ def up
3
+ values = ::Marty::Notifications::EventType::VALUES
4
+ str_values = values.map {|v| ActiveRecord::Base.connection.quote v}.join ','
5
+
6
+ execute <<-SQL
7
+ CREATE TYPE marty_notifications_event_types AS ENUM (#{str_values});
8
+ SQL
9
+ end
10
+
11
+ def down
12
+ execute <<-SQL
13
+ DROP TYPE marty_notifications_event_types;
14
+ SQL
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ class CreateMartyNotifications < ActiveRecord::Migration[4.2]
2
+ def change
3
+ create_table :marty_notifications do |t|
4
+ t.pg_enum :event_type, enum: :marty_notifications_event_types, null: false
5
+ t.string :state, null: false
6
+ t.text :text, null: false
7
+
8
+ t.timestamps null: false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ class CreateMartyNotificationsDeliveries < ActiveRecord::Migration[4.2]
2
+ def change
3
+ create_table :marty_notifications_deliveries do |t|
4
+ t.integer :notification_id, null: false
5
+ t.integer :recipient_id
6
+ t.string :delivery_type, null: false
7
+ t.string :state, null: false
8
+ t.text :text, null: false, default: ''
9
+ t.string :error_text, null: false, default: ''
10
+
11
+ t.timestamps null: false
12
+ end
13
+
14
+ add_foreign_key :marty_notifications_deliveries, :marty_notifications,
15
+ column: :notification_id, on_delete: :cascade
16
+ add_foreign_key :marty_notifications_deliveries, :marty_users,
17
+ column: :recipient_id, on_delete: :nullify
18
+
19
+ add_index :marty_notifications_deliveries, [:notification_id, :recipient_id, :delivery_type],
20
+ unique: true, name: :index_marty_notifications_deliveries_on_n_id_r_id_and_type
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ class CreateMartyNotificationsConfig < ActiveRecord::Migration[4.2]
2
+ def change
3
+ create_table :marty_notifications_configs do |t|
4
+ t.pg_enum :event_type, enum: :marty_notifications_event_types, null: false
5
+ t.integer :recipient_id
6
+ t.string :delivery_type, null: false
7
+ t.string :state, null: false
8
+ t.text :text, null: false, default: ''
9
+
10
+ t.timestamps null: false
11
+ end
12
+
13
+ add_foreign_key :marty_notifications_configs, :marty_users,
14
+ column: :recipient_id, on_delete: :nullify
15
+
16
+ add_index :marty_notifications_configs,
17
+ [:event_type, :recipient_id, :delivery_type],
18
+ unique: true,
19
+ name: :index_marty_notifications_configs_on_event_recipient_delivery
20
+ end
21
+ end
@@ -37,7 +37,7 @@ DECLARE
37
37
 
38
38
  result JSONB;
39
39
  return_json JSONB;
40
-
40
+
41
41
  error_extra JSONB;
42
42
 
43
43
  target RECORD;
@@ -58,7 +58,7 @@ BEGIN
58
58
  END IF;
59
59
  END LOOP;
60
60
 
61
- FOREACH direction IN ARRAY directions LOOP
61
+ FOREACH direction IN ARRAY directions LOOP
62
62
 
63
63
  IF direction = 'h' THEN
64
64
  data_grid_metadata_current := data_grid_metadata_h;
@@ -89,7 +89,7 @@ BEGIN
89
89
 
90
90
  query_index_result := empty_jsonb_array;
91
91
 
92
- -- execute the SQL query that has been received before and
92
+ -- execute the SQL query that has been received before and
93
93
  -- add it's (possibly multiplt) results to query_index_result variable
94
94
  FOR target IN EXECUTE query_dir_result ->> 0 USING query_dir_result -> 1 LOOP
95
95
  query_index_result := query_index_result || to_jsonb(target.index);
@@ -105,7 +105,7 @@ BEGIN
105
105
  END IF;
106
106
 
107
107
 
108
- IF dis AND jsonb_array_length(query_index_result) > 1 THEN
108
+ IF dis AND jsonb_array_length(query_index_result) > 1 THEN
109
109
  RAISE EXCEPTION 'matches > 1';
110
110
  END IF;
111
111
 
@@ -114,7 +114,7 @@ BEGIN
114
114
  vertical_indexes := COALESCE(vertical_indexes, empty_jsonb_array);
115
115
  horizontal_indexes := COALESCE(horizontal_indexes, empty_jsonb_array);
116
116
 
117
- IF ((jsonb_array_length(vertical_indexes)) = 0
117
+ IF ((jsonb_array_length(vertical_indexes)) = 0
118
118
  OR (jsonb_array_length(horizontal_indexes)) = 0)
119
119
  AND NOT data_grid_lenient
120
120
  AND NOT return_grid_data THEN
@@ -140,7 +140,7 @@ BEGIN
140
140
  result := data_grid_data -> vertical_index -> horizontal_index;
141
141
  END IF;
142
142
 
143
- IF NOT return_grid_data THEN
143
+ IF NOT return_grid_data THEN
144
144
  data_grid_data := NULL;
145
145
  data_grid_metadata := NULL;
146
146
  END IF;
@@ -7,6 +7,7 @@
7
7
 
8
8
  # Also note that anything required here will need to require in any
9
9
  # classes that they might be overriding methods in
10
+ require 'action_cable/engine'
10
11
 
11
12
  require 'marty/engine'
12
13
  require 'marty/railtie'
@@ -22,6 +23,7 @@ require 'marty/rails_app'
22
23
  # to the Gemfile
23
24
  require 'net-ldap'
24
25
  require 'delayed_cron_job'
26
+ require 'state_machines-activerecord'
25
27
 
26
28
  require 'pathname'
27
29
 
@@ -1,30 +1,40 @@
1
- module Marty::Diagnostic; class Version < Base
2
- diagnostic_fn do
3
- begin
4
- message = `cd #{Rails.root}; git describe --tags --always;`.strip
5
- rescue StandardError
6
- message = error('Failed accessing git')
1
+ module Marty::Diagnostic
2
+ class Version < Base
3
+ diagnostic_fn do
4
+ begin
5
+ submodules = `cd #{Rails.root}; git submodule`.split("\n").map do |s|
6
+ sha, path, tag = s.split
7
+ name = File.basename(path)
8
+ {
9
+ "#{name}_sha".titleize => sha[0..7],
10
+ "#{name}_tag".titleize => tag,
11
+ }
12
+ end.reduce(&:merge) || {}
13
+
14
+ git_tag = `cd #{Rails.root}; git describe --tags --always;`.strip
15
+ git = { 'Root Git' => git_tag }.merge(submodules)
16
+ rescue StandardError
17
+ git = { 'Root Git' => error('Failed accessing git') }
18
+ end
19
+ rbv = "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
20
+ {
21
+ 'Marty' => Marty::VERSION,
22
+ 'Delorean' => Delorean::VERSION,
23
+ 'Mcfly' => Mcfly::VERSION,
24
+ 'Rails' => Rails.version,
25
+ 'Netzke Core' => Netzke::Core::VERSION,
26
+ 'Netzke Basepack' => Netzke::Basepack::VERSION,
27
+ 'Ruby' => rbv,
28
+ 'RubyGems' => Gem::VERSION,
29
+ 'Database Schema Version' => db_schema,
30
+ 'Environment' => Rails.env,
31
+ }.merge(git)
7
32
  end
8
- rbv = "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
9
- {
10
- 'Marty' => Marty::VERSION,
11
- 'Delorean' => Delorean::VERSION,
12
- 'Mcfly' => Mcfly::VERSION,
13
- 'Git' => message,
14
- 'Rails' => Rails.version,
15
- 'Netzke Core' => Netzke::Core::VERSION,
16
- 'Netzke Basepack' => Netzke::Basepack::VERSION,
17
- 'Ruby' => rbv,
18
- 'RubyGems' => Gem::VERSION,
19
- 'Database Schema Version' => db_schema,
20
- 'Environment' => Rails.env,
21
- }
22
- end
23
33
 
24
- def self.db_schema
25
- Database.db_schema
26
- rescue StandardError => e
27
- error(e.message)
34
+ def self.db_schema
35
+ Database.db_schema
36
+ rescue StandardError => e
37
+ error(e.message)
38
+ end
28
39
  end
29
40
  end
30
- end