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
@@ -0,0 +1,23 @@
1
+ require 'marty/notifications/grid_view'
2
+
3
+ module Marty
4
+ module Notifications
5
+ class Window < Netzke::Window::Base
6
+ def configure(c)
7
+ super
8
+
9
+ c.title = 'Notifications'
10
+ c.modal = true
11
+ c.items = [:grid_view]
12
+ c.lazy_loading = true
13
+ c.width = 800
14
+ c.height = 550
15
+ end
16
+
17
+ component :grid_view do |c|
18
+ c.klass = Marty::Notifications::GridView
19
+ c.rows_per_page = 30
20
+ end
21
+ end
22
+ end
23
+ end
@@ -50,7 +50,7 @@ class Marty::PromiseView < Netzke::Tree::Base
50
50
  end
51
51
 
52
52
  def bbar
53
- [:clear, '->', :refresh, :download]
53
+ [:clear, :cancel_job, '->', :refresh, :download]
54
54
  end
55
55
 
56
56
  action :clear do |a|
@@ -60,6 +60,13 @@ class Marty::PromiseView < Netzke::Tree::Base
60
60
  a.hidden = !self.class.has_perm?(:admin)
61
61
  end
62
62
 
63
+ action :cancel_job do |a|
64
+ a.text = a.tooltip = 'Cancel Job'
65
+ a.disabled = false
66
+ a.icon_cls = 'fa fa-minus glyph'
67
+ a.hidden = !self.class.has_perm?(:admin)
68
+ end
69
+
63
70
  action :download do |a|
64
71
  a.text = a.tooltip = 'Download'
65
72
  a.disabled = true
@@ -77,6 +84,11 @@ class Marty::PromiseView < Netzke::Tree::Base
77
84
  client.netzke_on_refresh
78
85
  end
79
86
 
87
+ endpoint :cancel_job do |id|
88
+ Marty::Promises::Cancel.call(id)
89
+ client.netzke_on_refresh
90
+ end
91
+
80
92
  def get_records params
81
93
  search_scope = config[:live_search_scope] || :live_search
82
94
  Marty::VwPromise.children_for_id(params[:id], params[search_scope])
@@ -64,5 +64,23 @@
64
64
  // extra request for every node expand event.
65
65
  netzkeOnNodeStateChange: function() {
66
66
  return;
67
+ },
68
+
69
+ netzkeOnCancelJob: function (params) {
70
+ var me = this;
71
+ var sel = this.getSelectionModel().getSelection();
72
+ if (sel.length != 1) {
73
+ return this.netzkeNotify('select one job to cancel')
74
+ }
75
+ Ext.Msg.show({
76
+ title: 'Cancel this Job?',
77
+ msg: 'Enter CANCEL and press OK to cancel the job',
78
+ width: 375,
79
+ buttons: Ext.Msg.OKCANCEL,
80
+ prompt: true,
81
+ fn: function (btn, value) {
82
+ (btn == "ok" && value == "CANCEL") && me.server.cancelJob(sel[0].getId());
83
+ }
84
+ });
67
85
  }
68
86
  }
@@ -18,6 +18,7 @@
18
18
  });
19
19
 
20
20
  this.setRouting();
21
+ this.netzkeInitComponentCallback();
21
22
  },
22
23
 
23
24
  setRouting: function () {
@@ -54,5 +55,8 @@
54
55
 
55
56
  onToggleConfigMode: function (params) {
56
57
  this.toggleConfigMode();
57
- }
58
+ },
59
+
60
+ netzkeInitComponentCallback: function() {
61
+ },
58
62
  }
@@ -0,0 +1,177 @@
1
+ module Marty
2
+ class Users
3
+ class UserView < Marty::Grid
4
+ has_marty_permissions create: [:admin, :user_manager],
5
+ read: :any,
6
+ update: [:admin, :user_manager],
7
+ delete: [:admin, :user_manager]
8
+
9
+ # list of columns to be displayed in the grid view
10
+ def self.user_columns
11
+ [
12
+ :login,
13
+ :firstname,
14
+ :lastname,
15
+ :active,
16
+ :user_roles,
17
+ ]
18
+ end
19
+
20
+ def configure(c)
21
+ super
22
+
23
+ c.attributes ||= self.class.user_columns
24
+ c.title ||= I18n.t('users', default: 'Users')
25
+ c.model = 'Marty::User'
26
+ c.editing = :in_form
27
+ c.paging = :pagination
28
+ c.multi_select = false
29
+ if c.attributes.include?(:login)
30
+ c.store_config[:sorters] = [{ property: :login,
31
+ direction: 'ASC', }]
32
+ end
33
+ c.scope = ->(arel) { arel.includes(:user_roles) }
34
+ end
35
+
36
+ def self.set_roles(roles, user)
37
+ roles = [] unless roles.present?
38
+
39
+ roles = ::Marty::RoleType.from_nice_names(roles)
40
+
41
+ roles_in_user = user.user_roles.map(&:role)
42
+ roles_to_delete = roles_in_user - roles
43
+ roles_to_add = roles - roles_in_user
44
+
45
+ Marty::User.transaction do
46
+ user.user_roles.where(role: roles_to_delete).map(&:destroy!)
47
+
48
+ roles_to_add.each do |role|
49
+ user.user_roles.create!(role: role)
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.create_edit_user(data)
55
+ # Creates initial place-holder user object and validate
56
+ user = data['id'].nil? ? User.new : User.find(data['id'])
57
+
58
+ user_columns.each do |c|
59
+ user.send("#{c}=", data[c.to_s]) unless c == :user_roles
60
+ end
61
+
62
+ if user.valid?
63
+ user.save
64
+ set_roles(data['user_roles'], user)
65
+ end
66
+
67
+ user
68
+ end
69
+
70
+ # override the add_in_form and edit_in_form endpoint. User creation/update
71
+ # needs to use the create_edit_user method.
72
+
73
+ endpoint :add_window__add_form__submit do |params|
74
+ data = ActiveSupport::JSON.decode(params[:data])
75
+
76
+ data['id'] = nil
77
+
78
+ unless self.class.can_perform_action?(:create)
79
+ client.netzke_notify 'Permission Denied'
80
+ return
81
+ end
82
+
83
+ user = self.class.create_edit_user(data)
84
+ if user.valid?
85
+ client.success = true
86
+ client.netzke_on_submit_success
87
+ else
88
+ client.netzke_notify(model_adapter.errors_array(user).join("\n"))
89
+ end
90
+ end
91
+
92
+ endpoint :edit_window__edit_form__submit do |params|
93
+ data = ActiveSupport::JSON.decode(params[:data])
94
+ unless self.class.can_perform_action?(:update)
95
+ client.netzke_notify 'Permission Denied'
96
+ return
97
+ end
98
+
99
+ user = self.class.create_edit_user(data)
100
+ if user.valid?
101
+ client.success = true
102
+ client.netzke_on_submit_success
103
+ else
104
+ client.netzke_notify(model_adapter.errors_array(user).join("\n"))
105
+ end
106
+ end
107
+
108
+ action :add do |a|
109
+ super(a)
110
+ a.text = I18n.t('user_grid.new')
111
+ a.tooltip = I18n.t('user_grid.new')
112
+ a.icon_cls = 'fa fa-user-plus glyph'
113
+ end
114
+
115
+ action :edit do |a|
116
+ super(a)
117
+ a.icon_cls = 'fa fa-user-cog glyph'
118
+ end
119
+
120
+ action :delete do |a|
121
+ super(a)
122
+ a.icon_cls = 'fa fa-user-minus glyph'
123
+ end
124
+
125
+ def default_context_menu
126
+ []
127
+ end
128
+
129
+ attribute :login do |c|
130
+ c.width = 100
131
+ c.label = I18n.t('user_grid.login')
132
+ end
133
+
134
+ attribute :firstname do |c|
135
+ c.width = 100
136
+ c.label = I18n.t('user_grid.firstname')
137
+ end
138
+
139
+ attribute :lastname do |c|
140
+ c.width = 100
141
+ c.label = I18n.t('user_grid.lastname')
142
+ end
143
+
144
+ attribute :active do |c|
145
+ c.width = 60
146
+ c.label = I18n.t('user_grid.active')
147
+ end
148
+
149
+ attribute :user_roles do |c|
150
+ c.width = 100
151
+ c.flex = 1
152
+ c.label = I18n.t('user_grid.roles')
153
+ c.type = :string
154
+
155
+ c.getter = lambda do |r|
156
+ Marty::RoleType.to_nice_names(r.user_roles.map(&:role))
157
+ end
158
+
159
+ store = ::Marty::RoleType.to_nice_names(::Marty::RoleType::VALUES.sort)
160
+
161
+ c.editor_config = {
162
+ multi_select: true,
163
+ empty_text: I18n.t('user_grid.select_roles'),
164
+ store: store,
165
+ type: :string,
166
+ xtype: :combo,
167
+ }
168
+ end
169
+
170
+ attribute :created_dt do |c|
171
+ c.label = I18n.t('user_grid.created_dt')
172
+ c.format = 'Y-m-d H:i'
173
+ c.read_only = true
174
+ end
175
+ end
176
+ end
177
+ end
@@ -22,6 +22,7 @@ class Marty::ApplicationController < ActionController::Base
22
22
  if session[:user_id]
23
23
  if session_expired? && !try_to_autologin
24
24
  reset_session
25
+ reset_signed_cookies
25
26
  else
26
27
  session[:atime] = Time.now.utc.to_i
27
28
  end
@@ -50,6 +51,8 @@ class Marty::ApplicationController < ActionController::Base
50
51
  session[:user_id] = user.id
51
52
  session[:ctime] = Time.now.utc.to_i
52
53
  session[:atime] = Time.now.utc.to_i
54
+
55
+ set_signed_cookies
53
56
  end
54
57
 
55
58
  def user_setup
@@ -78,6 +81,7 @@ class Marty::ApplicationController < ActionController::Base
78
81
  user = Marty::User.try_to_autologin(cookies[:autologin])
79
82
  if user
80
83
  reset_session
84
+ reset_signed_cookies
81
85
  start_user_session(user)
82
86
  end
83
87
  user
@@ -87,6 +91,7 @@ class Marty::ApplicationController < ActionController::Base
87
91
  # Sets the logged in user
88
92
  def set_user(user)
89
93
  reset_session
94
+ reset_signed_cookies
90
95
  if user && user.is_a?(Marty::User)
91
96
  Marty::User.current = user
92
97
  start_user_session(user)
@@ -123,6 +128,14 @@ class Marty::ApplicationController < ActionController::Base
123
128
  set_user(user)
124
129
  end
125
130
 
131
+ def set_signed_cookies
132
+ cookies.signed[:user_id] = session[:user_id]
133
+ end
134
+
135
+ def reset_signed_cookies
136
+ cookies.signed[:user_id] = nil
137
+ end
138
+
126
139
  def toggle_dark_mode
127
140
  cookies[:dark_mode] = cookies[:dark_mode] != 'true'
128
141
  end
@@ -249,7 +249,7 @@ class Marty::DataGrid < Marty::Base
249
249
 
250
250
  # private method used to cache lookup_grid_distinct_entry_h result
251
251
  delorean_fn :lookup_grid_h_priv,
252
- private: true, cache: true, sig: 4 do |pt, dgh, h, distinct|
252
+ to_hash: false, private: true, cache: true, sig: 4 do |pt, dgh, h, distinct|
253
253
  lookup_grid_distinct_entry_h(
254
254
  pt, h, dgh, nil, true, false, distinct)['result']
255
255
  end
@@ -0,0 +1,4 @@
1
+ module Marty
2
+ module Notifications
3
+ end
4
+ end
@@ -0,0 +1,25 @@
1
+ module Marty
2
+ module Notifications
3
+ class Config < Marty::Base
4
+ self.table_name = 'marty_notifications_configs'
5
+
6
+ AVAILABLE_TYPES = ::Marty::Notifications::Delivery.
7
+ state_machines[:delivery_type].states.map(&:value)
8
+
9
+ belongs_to :recipient, class_name: '::Marty::User'
10
+
11
+ validates :recipient, presence: true
12
+
13
+ validates :delivery_type, presence: true, inclusion: { in: AVAILABLE_TYPES }
14
+
15
+ validates :delivery_type,
16
+ presence: true,
17
+ uniqueness: { scope: [:event_type, :recipient_id] }
18
+
19
+ state_machine :state, initial: :on do
20
+ state :off
21
+ state :on
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,56 @@
1
+ module Marty
2
+ module Notifications
3
+ class Delivery < Marty::Base
4
+ self.table_name = 'marty_notifications_deliveries'
5
+
6
+ belongs_to :notification, class_name: '::Marty::Notifications::Notification'
7
+ belongs_to :recipient, class_name: '::Marty::User'
8
+
9
+ validates :state, presence: true
10
+
11
+ # One delivery per type per notification for user
12
+ validates :delivery_type,
13
+ presence: true,
14
+ uniqueness: { scope: [:notification_id, :recipient_id] }
15
+
16
+ state_machine :delivery_type, initial: :web do
17
+ state :web do
18
+ def processor
19
+ Marty::Notifications::Processors::Web
20
+ end
21
+ end
22
+
23
+ # state :email do
24
+ # def processor
25
+ # Marty::Notifications::Processors::Email
26
+ # end
27
+ # end
28
+ #
29
+ # state :sms do
30
+ # def processor
31
+ # Marty::Notifications::Processors::Sms
32
+ # end
33
+ # end
34
+ end
35
+
36
+ state_machine :state, initial: :pending do
37
+ state :pending
38
+ state :sent
39
+ state :delivered
40
+ state :failed
41
+
42
+ event :set_sent do
43
+ transition [:pending, :failed] => :sent, sent: same
44
+ end
45
+
46
+ event :set_failed do
47
+ transition [:pending, :sent] => :failed, failed: same
48
+ end
49
+
50
+ event :set_delivered do
51
+ transition all => :delivered
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ module Marty
2
+ module Notifications
3
+ class EventType < Marty::Base
4
+ extend Marty::PgEnum
5
+
6
+ VALUES = Set[
7
+ 'API reached the limit'
8
+ ]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module Marty
2
+ module Notifications
3
+ class Notification < Marty::Base
4
+ self.table_name = 'marty_notifications'
5
+
6
+ validates :state, :event_type, presence: true
7
+
8
+ has_many(
9
+ :deliveries,
10
+ class_name: '::Marty::Notifications::Delivery',
11
+ dependent: :destroy,
12
+ foreign_key: :notification_id
13
+ )
14
+
15
+ state_machine :state, initial: :pending do
16
+ state :pending
17
+ state :processed
18
+
19
+ event :set_processed do
20
+ transition processed: same, pending: :processed
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end