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