marty 8.0.0 → 8.2.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 (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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba9d8e9da88bf08b792bd49d882e4320dc0e68f338ab5ed35577a4c1c741ae90
4
- data.tar.gz: b9b76371c794c41b8383c52a30d03d0ecccbfc428bfe88f598c8ec6778e4e63f
3
+ metadata.gz: ef38a20a14482c03a791b80eb8743f9764151a8e8d09c682c76ffeea62d76828
4
+ data.tar.gz: 480f31e3d956e235fd4941be08c209c05a7c338d3fbdeb6d2b85a4d4af7b0c28
5
5
  SHA512:
6
- metadata.gz: ddf969f74e26013d76713537bcddeba889983d83d80ea60d5ace5cb92cc44a77096e73a356524def77ca10a00b473574fa9a94acffbfa1182bbc61c0927b413f
7
- data.tar.gz: 4a3f20060711fc70cce936e90197f88a1256f1c814d4cf2fbadde12b28715eeffde3c096e92db1c1a7693d9ad268966e77e4aa2b1ee20b3d7078e93838869f78
6
+ metadata.gz: 5263a4562cfa44272654a399ef3f5e2f52cb95e27fd027c130b0477b93dad83d1b83631d5efc8946344ed5485ef5b654980282882580fd472687fd99d6b71ddc
7
+ data.tar.gz: 07e53433346be6dbd718fa631aa7535179ec944139ebbcb6fd69ef9b5182a175dad233df397ca14300581da995d553a667dbb0126e6f988ea3e5339e4fc365aa
@@ -10,7 +10,14 @@ before_script:
10
10
  - RAILS_ENV=test bundle exec rails db:create db:migrate
11
11
 
12
12
  .base-test:
13
+ # Cancel if new commits where pushed
14
+ interruptible: true
15
+ # Run only when there is an MR
16
+ only:
17
+ - merge_requests
18
+
13
19
  stage: test
20
+
14
21
  # Use only the following CI runners
15
22
  tags:
16
23
  - gitlabci-runner-eks-shared-dev
@@ -0,0 +1,12 @@
1
+ //= require action_cable
2
+ //= require_self
3
+
4
+ (function() {
5
+ this.RailsApp || (this.RailsApp = {});
6
+
7
+ if (window.location.port === "") {
8
+ RailsApp.cable = ActionCable.createConsumer(`ws://${window.location.hostname}/cable`);
9
+ } else {
10
+ RailsApp.cable = ActionCable.createConsumer(`ws://${window.location.hostname}:${window.location.port}/cable`);
11
+ }
12
+ }).call(this);
@@ -0,0 +1,4 @@
1
+ .notification-counter {
2
+ color: red;
3
+ font-weight: 400;
4
+ }
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ::ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ module ApplicationCable
2
+ class Connection < ::ActionCable::Connection::Base
3
+ identified_by :current_user
4
+
5
+ def connect
6
+ self.current_user = find_verified_user
7
+ end
8
+
9
+ private
10
+
11
+ def find_verified_user
12
+ return unless cookies.signed[:user_id].present?
13
+
14
+ ::Marty::User.find_by(id: cookies.signed[:user_id])
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Marty
2
+ class NotificationChannel < ::ApplicationCable::Channel
3
+ def subscribed
4
+ reject && return unless current_user.present?
5
+ stream_from "marty_notifications_#{current_user.id}"
6
+ end
7
+ end
8
+ end
@@ -2,6 +2,8 @@
2
2
  #
3
3
  # == Extending Marty::AuthApp
4
4
  # DOCFIX
5
+ require 'marty/notifications/window'
6
+
5
7
  class Marty::AuthApp < Marty::SimpleApp
6
8
  client_class do |c|
7
9
  c.include :auth_app
@@ -12,22 +14,54 @@ class Marty::AuthApp < Marty::SimpleApp
12
14
  [].tap do |menu|
13
15
  user = Mcfly.whodunnit
14
16
  if !user.nil?
15
- menu << '->' << {
16
- text: user.name,
17
- tooltip: 'Current user',
18
- menu: user_menu,
19
- name: 'sign_out',
20
- }
17
+ menu <<
18
+ '->' <<
19
+ notification_menu_item <<
20
+ current_user_menu_item(user)
21
21
  else
22
22
  menu << '->' << :sign_in
23
23
  end
24
24
  end
25
25
  end
26
26
 
27
+ def notification_menu_item
28
+ :notifications_window
29
+ end
30
+
31
+ def current_user_menu_item(user)
32
+ {
33
+ text: user.name,
34
+ tooltip: 'Current user',
35
+ menu: user_menu,
36
+ name: 'sign_out',
37
+ }
38
+ end
39
+
27
40
  def user_menu
28
41
  [:sign_out, :toggle_dark_mode]
29
42
  end
30
43
 
44
+ def unread_notifications_count
45
+ user = Mcfly.whodunnit
46
+
47
+ return 0 unless user.present?
48
+
49
+ user.unread_web_notifications_count
50
+ end
51
+
52
+ action :notifications_window do |c|
53
+ c.icon_cls = 'fa fa-bell gylph '
54
+ c.tooltip = 'Show notifications'
55
+
56
+ c.text = nil
57
+
58
+ notifications_count = unread_notifications_count
59
+
60
+ next if notifications_count.zero?
61
+
62
+ c.text = "<span class='notification-counter'>#{notifications_count}</span>"
63
+ end
64
+
31
65
  action :sign_in do |c|
32
66
  c.icon_cls = 'fa fa-sign-in-alt gylph'
33
67
  end
@@ -58,4 +92,18 @@ class Marty::AuthApp < Marty::SimpleApp
58
92
  endpoint :toggle_dark_mode do
59
93
  Netzke::Base.controller.toggle_dark_mode
60
94
  end
95
+
96
+ endpoint :mark_web_notifications_delivered do
97
+ user = Mcfly.whodunnit
98
+ deliveries = user.notification_deliveries.where(
99
+ delivery_type: :web,
100
+ state: [:sent]
101
+ )
102
+
103
+ deliveries.each(&:set_delivered!)
104
+ end
105
+
106
+ component :notifications_window do |c|
107
+ c.klass = ::Marty::Notifications::Window
108
+ end
61
109
  end
@@ -91,5 +91,58 @@
91
91
 
92
92
  netzkeOnToggleDarkMode: function() {
93
93
  this.server.toggleDarkMode(() => { window.location.href = "/" });
94
+ },
95
+
96
+ netzkeOnNotificationsWindow: function () {
97
+ this.netzkeLoadComponent("notifications_window", {
98
+ callback: function (w) {
99
+ w.show();
100
+
101
+ this.server.markWebNotificationsDelivered();
102
+
103
+ var notificationsButton = this.menuBar.items.items.find(
104
+ function(item) { return item.name === "notificationsWindow" }
105
+ );
106
+
107
+ notificationsButton.setText(''); // Remove the counter
108
+ },
109
+ });
110
+ },
111
+
112
+ netzkeInitComponentCallback: function() {
113
+ try {
114
+ var subscription = RailsApp.cable.subscriptions.subscriptions.find(
115
+ (sub) => sub.identifier === '{"channel":"Marty::NotificationChannel"}'
116
+ )
117
+
118
+ // In case if component is initialized twice
119
+ if (subscription) {
120
+ return
121
+ }
122
+
123
+ RailsApp.cable.subscriptions.create(
124
+ 'Marty::NotificationChannel',
125
+ {
126
+ received: (data) => {
127
+ var notificationsButton = this.menuBar.items.items.find(
128
+ function(item) { return item.name === "notificationsWindow" }
129
+ );
130
+
131
+ if (data.unread_notifications_count > 0) {
132
+ notificationsButton.setText(
133
+ `<span class='notification-counter'>${data.unread_notifications_count}</span>`
134
+ );
135
+ } else {
136
+ notificationsButton.setText('');
137
+ }
138
+ }
139
+ }
140
+ );
141
+ }
142
+ catch(error) {
143
+ console.log('ActionCable connection failed')
144
+ console.error(error);
145
+ }
94
146
  }
95
147
  }
148
+
@@ -13,6 +13,8 @@ require 'marty/promise_view'
13
13
  require 'marty/reporting'
14
14
  require 'marty/scripting'
15
15
  require 'marty/user_view'
16
+ require 'marty/notifications/config_view'
17
+ require 'marty/notifications/deliveries_view'
16
18
 
17
19
  class Marty::MainAuthApp < Marty::AuthApp
18
20
  extend ::Marty::Permissions
@@ -86,7 +88,7 @@ class Marty::MainAuthApp < Marty::AuthApp
86
88
  :config_view,
87
89
  :reload_scripts,
88
90
  :load_seed,
89
- ] + background_jobs_menu + log_menu + api_menu
91
+ ] + background_jobs_menu + notifications_menu + log_menu + api_menu
90
92
  }
91
93
  end
92
94
 
@@ -121,6 +123,22 @@ class Marty::MainAuthApp < Marty::AuthApp
121
123
  ]
122
124
  end
123
125
 
126
+ def notifications_menu
127
+ disabled = !(self.class.has_perm?(:admin) ||
128
+ self.class.has_perm?(:user_manager))
129
+ [
130
+ {
131
+ text: 'Notifications',
132
+ icon_cls: 'fa fa-bell glyph',
133
+ disabled: disabled,
134
+ menu: [
135
+ :notifications_config_view,
136
+ :notifications_deliveries_view,
137
+ ]
138
+ },
139
+ ]
140
+ end
141
+
124
142
  def warped
125
143
  Marty::Util.warped?
126
144
  end
@@ -319,6 +337,25 @@ class Marty::MainAuthApp < Marty::AuthApp
319
337
  a.disabled = !self.class.has_perm?(:admin)
320
338
  end
321
339
 
340
+ # action 'Notifications::ConfigView' do |a|
341
+ action :notifications_config_view do |a|
342
+ a.text = 'User Notification Rules'
343
+ a.tooltip = 'Configure notification rules for users'
344
+ a.handler = :netzke_load_component_by_action
345
+ a.icon_cls = 'fa fa-sliders-h glyph'
346
+ a.disabled = !(self.class.has_perm?(:admin) ||
347
+ self.class.has_perm?(:user_manager))
348
+ end
349
+
350
+ action :notifications_deliveries_view do |a|
351
+ a.text = 'Notificaiton messages'
352
+ a.tooltip = 'Show all notification messages'
353
+ a.handler = :netzke_load_component_by_action
354
+ a.icon_cls = 'fa fa-list glyph'
355
+ a.disabled = !(self.class.has_perm?(:admin) ||
356
+ self.class.has_perm?(:dev))
357
+ end
358
+
322
359
  ######################################################################
323
360
 
324
361
  def bg_command(subcmd)
@@ -408,6 +445,7 @@ class Marty::MainAuthApp < Marty::AuthApp
408
445
  component :config_view
409
446
 
410
447
  component :data_grid_view
448
+
411
449
  component :data_grid_user_view
412
450
 
413
451
  component :import_type_view
@@ -420,6 +458,14 @@ class Marty::MainAuthApp < Marty::AuthApp
420
458
  c.disabled = Marty::Util.warped? || !self.class.has_posting_perm?
421
459
  end
422
460
 
461
+ component :notifications_config_view do |c|
462
+ c.klass = ::Marty::Notifications::ConfigView
463
+ end
464
+
465
+ component :notifications_deliveries_view do |c|
466
+ c.klass = ::Marty::Notifications::DeliveriesView
467
+ end
468
+
423
469
  component :posting_window
424
470
 
425
471
  component :promise_view
@@ -0,0 +1,56 @@
1
+ module Marty
2
+ module Notifications
3
+ class ConfigView < Marty::Grid
4
+ include Marty::Extras::Layout
5
+
6
+ has_marty_permissions create: [:admin, :user_manager],
7
+ read: [:admin, :user_manager],
8
+ update: [:admin, :user_manager],
9
+ delete: [:admin, :user_manager]
10
+
11
+ def configure(c)
12
+ super
13
+
14
+ c.attributes = [:event_type, :recipient__name, :delivery_type, :state, :text]
15
+
16
+ c.title ||= I18n.t('notifications_config', default: 'Notifications Configuration')
17
+ c.model = 'Marty::Notifications::Config'
18
+ c.editing = :in_form
19
+ c.paging = :pagination
20
+ c.store_config.merge!(
21
+ sorters: [{ property: :id, direction: 'DESC', }]
22
+ )
23
+ end
24
+
25
+ attribute :event_type do |c|
26
+ c.width = 200
27
+ enum_column(c, ::Marty::Notifications::EventType, nil, false)
28
+ end
29
+
30
+ attribute :recipient__name do |c|
31
+ c.width = 200
32
+ end
33
+
34
+ attribute :delivery_type do |c|
35
+ enum_column(c, model::AVAILABLE_TYPES, nil, false)
36
+ c.width = 70
37
+ end
38
+
39
+ attribute :state do |c|
40
+ enum_column(c, model.state_machines[:state].states.map(&:value), nil, false)
41
+ c.width = 70
42
+ c.label = I18n.t('notifications_config_state', default: 'On/Off')
43
+ end
44
+
45
+ attribute :text do |c|
46
+ c.width = 400
47
+ c.label = I18n.t('notifications_config_text', default: 'Message text')
48
+ c.setter = lambda do |record, value|
49
+ next record.text = '' if value.nil?
50
+
51
+ record.text = value
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,50 @@
1
+ module Marty
2
+ module Notifications
3
+ class DeliveriesView < Marty::Grid
4
+ ROLES_WITH_ACCESS = [:admin, :dev]
5
+
6
+ has_marty_permissions read: ROLES_WITH_ACCESS,
7
+ create: nil,
8
+ update: nil,
9
+ delete: ROLES_WITH_ACCESS
10
+
11
+ def configure(c)
12
+ super
13
+
14
+ c.header = false
15
+ c.model = 'Marty::Notifications::Delivery'
16
+ c.attributes = [:created_at, :notification__event_type, :recipient__name,
17
+ :state, :delivery_type, :text, :error_text]
18
+ c.store_config.merge!(
19
+ sorters: [{ property: :id, direction: 'DESC' }],
20
+ page_size: 30
21
+ )
22
+
23
+ c.scope = ->(arel) { arel.includes(:notification).includes(:recipient) }
24
+ end
25
+
26
+ attribute :notification__event_type do |config|
27
+ config.width = 150
28
+ config.label = 'Event'
29
+ end
30
+
31
+ attribute :recipient__name do |config|
32
+ config.width = 150
33
+ config.label = 'Recipient'
34
+ end
35
+
36
+ attribute :text do |config|
37
+ config.width = 400
38
+
39
+ config.getter = lambda do |record|
40
+ [record.notification.text, record.text].join(', ')
41
+ end
42
+ end
43
+
44
+ attribute :error_text do |config|
45
+ config.width = 300
46
+ config.label = 'Error'
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ module Marty
2
+ module Notifications
3
+ class GridView < Marty::Grid
4
+ has_marty_permissions read: :any,
5
+ create: nil,
6
+ update: nil,
7
+ delete: nil
8
+
9
+ def configure(c)
10
+ super
11
+
12
+ c.header = false
13
+ c.model = 'Marty::Notifications::Delivery'
14
+ c.attributes = [:created_at, :notification__event_type, :text, :error_text]
15
+ c.store_config.merge!(
16
+ sorters: [{ property: :created_at, direction: 'DESC' }],
17
+ page_size: 30
18
+ )
19
+
20
+ c.scope = ->(arel) { arel.includes(:notification) }
21
+ end
22
+
23
+ def get_records(params)
24
+ model.where(
25
+ delivery_type: :web,
26
+ state: [:sent, :delivered],
27
+ recipient_id: Mcfly.whodunnit
28
+ ).scoping do
29
+ super
30
+ end
31
+ end
32
+
33
+ def default_bbar
34
+ []
35
+ end
36
+
37
+ attribute :notification__event_type do |config|
38
+ config.width = 150
39
+ config.label = 'Event'
40
+ end
41
+
42
+ attribute :text do |config|
43
+ config.width = 300
44
+
45
+ config.getter = lambda do |record|
46
+ [record.notification.text, record.text].join(', ')
47
+ end
48
+ end
49
+
50
+ attribute :error_text do |config|
51
+ config.width = 300
52
+ config.label = 'Error'
53
+ end
54
+ end
55
+ end
56
+ end