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