web47core 2.0.1 → 2.2.15

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +111 -41
  3. data/app/controllers/status_controller.rb +8 -3
  4. data/app/helpers/core_form_helper.rb +6 -6
  5. data/app/helpers/core_helper.rb +1 -1
  6. data/app/helpers/core_link_helper.rb +47 -8
  7. data/app/helpers/core_nav_bar_helper.rb +5 -4
  8. data/app/helpers/core_table_helper.rb +80 -0
  9. data/app/helpers/model_modal_helper.rb +3 -3
  10. data/app/views/common/_create_actions.html.haml +12 -0
  11. data/app/views/common/_update_actions.html.haml +10 -0
  12. data/app/views/cron/_edit.html.haml +15 -17
  13. data/app/views/cron/_index.html.haml +74 -67
  14. data/app/views/delayed_job_metrics/_index.html.haml +27 -0
  15. data/app/views/delayed_job_metrics/index.html.haml +1 -0
  16. data/app/views/delayed_job_workers/_index.html.haml +27 -0
  17. data/app/views/delayed_job_workers/index.html.haml +1 -0
  18. data/app/views/delayed_jobs/_index.html.haml +47 -52
  19. data/app/views/delayed_jobs/_show.html.haml +15 -13
  20. data/app/views/system_configurations/_edit.html.haml +14 -9
  21. data/app/views/system_configurations/_show.html.haml +18 -12
  22. data/config/brakeman.ignore +26 -0
  23. data/config/brakeman.yml +2 -0
  24. data/config/locales/en.yml +21 -3
  25. data/lib/app/controllers/concerns/core_delayed_job_metrics_controller.rb +35 -0
  26. data/lib/app/controllers/concerns/core_delayed_job_workers_controller.rb +35 -0
  27. data/lib/app/controllers/concerns/core_delayed_jobs_controller.rb +2 -3
  28. data/lib/app/jobs/cron/command.rb +1 -4
  29. data/lib/app/jobs/cron/record_delayed_job_metrics.rb +25 -0
  30. data/lib/app/jobs/cron/restart_orphaned_delayed_jobs.rb +44 -0
  31. data/lib/app/jobs/cron/server.rb +32 -15
  32. data/lib/app/jobs/cron/switchboard_sync_configuration.rb +2 -0
  33. data/lib/app/jobs/cron/switchboard_sync_models.rb +2 -0
  34. data/lib/app/jobs/cron/trim_collection.rb +1 -1
  35. data/lib/app/jobs/cron/trim_delayed_job_metrics.rb +29 -0
  36. data/lib/app/jobs/cron/trim_delayed_job_workers.rb +39 -0
  37. data/lib/app/jobs/cron/trim_failed_delayed_jobs.rb +2 -0
  38. data/lib/app/models/api_token.rb +9 -0
  39. data/lib/app/models/command_job.rb +12 -11
  40. data/lib/app/models/concerns/api_tokenable.rb +38 -0
  41. data/lib/app/models/concerns/aws_configuration.rb +65 -0
  42. data/lib/app/models/concerns/cdn_url.rb +7 -0
  43. data/lib/app/models/concerns/core_smtp_configuration.rb +65 -0
  44. data/lib/app/models/concerns/core_system_configuration.rb +18 -201
  45. data/lib/app/models/concerns/delayed_job_configuration.rb +24 -0
  46. data/lib/app/models/concerns/email_able.rb +6 -2
  47. data/lib/app/models/concerns/google_sso_configuration.rb +32 -0
  48. data/lib/app/models/concerns/search_able.rb +16 -0
  49. data/lib/app/models/concerns/server_process_able.rb +69 -0
  50. data/lib/app/models/concerns/slack_configuration.rb +38 -0
  51. data/lib/app/models/concerns/standard_model.rb +5 -2
  52. data/lib/app/models/concerns/switchboard_configuration.rb +43 -0
  53. data/lib/app/models/concerns/twilio_configuration.rb +37 -0
  54. data/lib/app/models/concerns/zendesk_configuration.rb +92 -0
  55. data/lib/app/models/{delayed_job.rb → delayed/backend/delayed_job.rb} +41 -0
  56. data/lib/app/models/delayed/jobs/metric.rb +61 -0
  57. data/lib/app/models/delayed/jobs/run.rb +40 -0
  58. data/lib/app/models/delayed/jobs/worker.rb +43 -0
  59. data/lib/app/models/delayed/plugins/time_keeper.rb +33 -0
  60. data/lib/app/models/delayed/worker.rb +24 -0
  61. data/lib/app/models/email_notification.rb +2 -1
  62. data/lib/app/models/email_template.rb +5 -6
  63. data/lib/app/models/notification.rb +12 -2
  64. data/lib/app/models/sms_notification.rb +9 -6
  65. data/lib/app/models/template.rb +12 -12
  66. data/lib/web47core/version.rb +1 -1
  67. data/lib/web47core.rb +33 -9
  68. metadata +114 -69
@@ -74,6 +74,47 @@ module Delayed
74
74
  rescue StandardError
75
75
  ''
76
76
  end
77
+
78
+ #
79
+ # Is this job running?
80
+ #
81
+ def running?
82
+ locked_by.present?
83
+ end
84
+
85
+ #
86
+ # Retrieve the host we are running on
87
+ #
88
+ def worker
89
+ Delayed::Jobs::Worker.where(host_name: worker_host_name, pid: worker_pid).first
90
+ rescue StandardError => error
91
+ App47Logger.log_warn 'Unable to fetch Worker information', error
92
+ nil
93
+ end
94
+
95
+ def worker_host_name
96
+ parts = locked_by.split
97
+ case parts.count
98
+ when 2
99
+ parts.first
100
+ when 3
101
+ parts[1]
102
+ else
103
+ raise "Unknown locked_by for delayed job #{parts}"
104
+ end.split(':').last
105
+ end
106
+
107
+ def worker_pid
108
+ parts = locked_by.split
109
+ case parts.count
110
+ when 2
111
+ parts.last
112
+ when 3
113
+ parts[2]
114
+ else
115
+ raise "Unknown locked_by for delayed job #{parts}"
116
+ end.split(':').last
117
+ end
77
118
  end
78
119
  end
79
120
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Delayed
4
+ module Jobs
5
+ #
6
+ # Hold the job metric information for a specific job, used to determine if the job should be auto restarted
7
+ #
8
+ class Metric
9
+ include StandardModel
10
+
11
+ #
12
+ # Fields
13
+ #
14
+ field :name, type: String
15
+ field :count, type: Integer, default: 0
16
+ field :max, type: Float, default: 0
17
+ field :min, type: Float, default: 1_000_000_000
18
+ field :total, type: Float, default: 0
19
+ field :last_run_at, type: Time
20
+
21
+ #
22
+ # Validations
23
+ #
24
+ validates :name, uniqueness: true, presence: true
25
+
26
+ #
27
+ # Max allowed time for a currently running job
28
+ #
29
+ def max_allowed_seconds
30
+ send(max_allowed_method) * max_allowed_factor
31
+ rescue StandardError
32
+ max * 5
33
+ end
34
+
35
+ #
36
+ # Return the average time to run this job
37
+ #
38
+ def avg
39
+ count.zero? ? 0 : total / count
40
+ rescue StandardError
41
+ 0
42
+ end
43
+
44
+ private
45
+
46
+ #
47
+ # Pull the method from system configuration to evalutate max allowed time
48
+ #
49
+ def max_allowed_method
50
+ @max_allowed_method ||= SystemConfiguration.delayed_job_max_allowed_method
51
+ end
52
+
53
+ #
54
+ # Pull the factor for max allowed when evaluating allowed time
55
+ #
56
+ def max_allowed_factor
57
+ @max_allowed_factor ||= SystemConfiguration.delayed_job_max_allowed_factor
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Delayed
4
+ module Jobs
5
+ #
6
+ # Single execution of a Job
7
+ #
8
+ class Run
9
+ include StandardModel
10
+ #
11
+ # Fields
12
+ #
13
+ field :name, type: String
14
+ field :locked_at, type: Time
15
+ field :locked_by, type: String
16
+ field :duration, type: Float, default: 0
17
+ #
18
+ # Relationships
19
+ #
20
+ belongs_to :worker, class_name: 'Delayed::Jobs::Worker'
21
+ #
22
+ # Validations
23
+ #
24
+ validates :name, presence: true
25
+ validates :locked_at, presence: true
26
+ validates :duration, numericality: { greater_than_or_equal_to: 0 }
27
+ #
28
+ # Callbacks
29
+ #
30
+ before_validation :record_duration
31
+
32
+ #
33
+ # Finish execution
34
+ #
35
+ def record_duration
36
+ self.duration = duration.zero? ? Time.now.utc - locked_at : duration
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Delayed
4
+ module Jobs
5
+ #
6
+ # Capture a specific worker process
7
+ #
8
+ class Worker
9
+ include StandardModel
10
+ include ServerProcessAble
11
+ #
12
+ # Relationships
13
+ #
14
+ has_many :runs, class_name: 'Delayed::Jobs::Run', dependent: :restrict
15
+
16
+ #
17
+ # Record a job relative to the worker
18
+ #
19
+ def record_job(job)
20
+ stop
21
+ runs.create!(name: job.display_name, locked_at: job.locked_at, locked_by: job.locked_by)
22
+ end
23
+
24
+ #
25
+ # Is this worker dead?, meaning has it reported in a timely manner
26
+ #
27
+ def dead?
28
+ last_check_in_at.present? ? check_in_limit > last_check_in_at : false
29
+ rescue StandardError
30
+ true
31
+ end
32
+
33
+ private
34
+
35
+ #
36
+ # Return the check in time limit based on if we are running or not
37
+ #
38
+ def check_in_limit
39
+ running? ? 3.hour.ago.utc : 1.minute.ago.utc
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ module Delayed
2
+ module Plugins
3
+ #
4
+ # Add callbacks to the lifecycle to capture two main aspects
5
+ # 1. When are workers checking in
6
+ # 2. How long jobs are taking
7
+ #
8
+ class TimeKeeper < Plugin
9
+ callbacks do |lifecycle|
10
+ lifecycle.before(:perform) do |_worker, job|
11
+ job.worker.start
12
+ rescue StandardError => error
13
+ App47Logger.log_warn 'before job perform', error
14
+ end
15
+ lifecycle.after(:perform) do |_worker, job|
16
+ job.worker.record_job(job)
17
+ rescue StandardError => error
18
+ App47Logger.log_warn 'after job perform', error
19
+ end
20
+ lifecycle.before(:loop) do
21
+ Delayed::Jobs::Worker.find_or_create_server.check_in
22
+ rescue StandardError => error
23
+ App47Logger.log_warn 'before worker loop', error
24
+ end
25
+ lifecycle.after(:loop) do
26
+ Delayed::Jobs::Worker.find_or_create_server.check_in
27
+ rescue StandardError => error
28
+ App47Logger.log_warn 'after worker loop', error
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Delayed
4
+ #
5
+ # Override the start and stop of the worker itself to capture start/end of the worker lifecycle
6
+ #
7
+ class Worker
8
+ #
9
+ # Starting the worker
10
+ #
11
+ def start
12
+ super
13
+ Delayed::Jobs::Worker.find_or_create_server
14
+ end
15
+
16
+ #
17
+ # Stop the worker
18
+ #
19
+ def stop
20
+ Delayed::Jobs::Worker.find_server&.stop
21
+ super
22
+ end
23
+ end
24
+ end
@@ -17,6 +17,7 @@
17
17
  #
18
18
  # email = EmailNotification.new
19
19
  # email.to = 'user@abc.com'
20
+ # email.recipient_name = 'users name'
20
21
  # email.sender = 'me@abc.com'
21
22
  # email.subject = 'Today is the day!'
22
23
  # email.message = 'Day is today!'
@@ -217,7 +218,7 @@ class EmailNotification < Notification
217
218
  return html_message if html_message.include?(tracker_url)
218
219
 
219
220
  image_tag = "<img style='height:0;width:0;border:none;display:none;' src='#{tracker_url}' alt=''/></body>"
220
- html_message.sub(%r{<\/body>}, image_tag)
221
+ html_message.sub(%r{</body>}, image_tag)
221
222
  end
222
223
 
223
224
  def subject_from_haml_text(haml_text, locals)
@@ -17,16 +17,15 @@ class EmailTemplate < Template
17
17
 
18
18
  #
19
19
  # Make sure the template is wrapped in html
20
+ #
20
21
  def htmlize_template
21
- if template.present? && !template.strip.start_with?("<")
22
- self.template = "<body><pre>#{template}</pre></body>"
23
- end
22
+ self.template = "<body><pre>#{template}</pre></body>" if template.present? && !template.strip.start_with?('<')
24
23
  end
25
24
 
26
25
  def valid_liquid_template
27
- super && Liquid::Template.parse(self.subject).nil?
28
- rescue Exception => error
29
- self.errors.add(:subject, "Invalid liquid text in subject: #{error.message}")
26
+ super && Liquid::Template.parse(subject).nil?
27
+ rescue StandardError => error
28
+ errors.add(:subject, "Invalid liquid text in subject: #{error.message}")
30
29
  false
31
30
  end
32
31
 
@@ -5,6 +5,7 @@
5
5
  #
6
6
  class Notification
7
7
  include StandardModel
8
+ include SearchAble
8
9
  #
9
10
  # Constants
10
11
  #
@@ -42,6 +43,7 @@ class Notification
42
43
  field :error_message, type: String
43
44
  field :last_viewed_at, type: Time
44
45
  field :viewed_count, type: Integer, default: 0
46
+ field :recipient_name, type: String
45
47
  #
46
48
  # Relationships
47
49
  #
@@ -100,10 +102,11 @@ class Notification
100
102
  #
101
103
  def finish_processing(processing_message = nil)
102
104
  if processing_message.present?
103
- set state: STATE_INVALID, error_message: processing_message
105
+ assign_attributes state: STATE_INVALID, error_message: processing_message
104
106
  else
105
- set state: STATE_PROCESSED, error_message: ''
107
+ assign_attributes state: STATE_PROCESSED, error_message: ''
106
108
  end
109
+ save(validate: false)
107
110
  end
108
111
 
109
112
  #
@@ -242,6 +245,13 @@ class Notification
242
245
  _type
243
246
  end
244
247
 
248
+ #
249
+ # Support search fields
250
+ #
251
+ def search_fields
252
+ %w[to state recipient_name]
253
+ end
254
+
245
255
  private
246
256
 
247
257
  #
@@ -35,12 +35,15 @@ class SmsNotification < Notification
35
35
  account_sid = config.twilio_account_id
36
36
  auth_token = config.twilio_auth_token
37
37
  client = Twilio::REST::Client.new account_sid, auth_token
38
-
39
- twilio_message = client.account.messages.create(
40
- body: message,
41
- to: to,
42
- from: config.twilio_phone_number
43
- )
38
+ twilio_message = if client.respond_to?(:account)
39
+ client.account.messages.create(body: message,
40
+ to: to,
41
+ from: config.twilio_phone_number)
42
+ else
43
+ client.messages.create(body: message,
44
+ to: to,
45
+ from: config.twilio_phone_number)
46
+ end
44
47
  # We are saved in the calling class, no reason to save again
45
48
  set sid: twilio_message.sid
46
49
  end
@@ -20,18 +20,6 @@ class Template
20
20
  validates :template, presence: true
21
21
  validate :valid_liquid_template
22
22
 
23
- private
24
-
25
- #
26
- # Ensure that the template is correct from a liquid statement
27
- #
28
- def valid_liquid_template
29
- Liquid::Template.parse(self.template).nil?
30
- rescue Exception => error
31
- self.errors.add(:template, "Invalid liquid text in template: #{error.message}")
32
- false
33
- end
34
-
35
23
  #
36
24
  # Retrieve the template out of the project
37
25
  #
@@ -45,4 +33,16 @@ class Template
45
33
  rescue StandardError
46
34
  nil
47
35
  end
36
+
37
+ private
38
+
39
+ #
40
+ # Ensure that the template is correct from a liquid statement
41
+ #
42
+ def valid_liquid_template
43
+ Liquid::Template.parse(template).nil?
44
+ rescue StandardError => error
45
+ errors.add(:template, "Invalid liquid text in template: #{error.message}")
46
+ false
47
+ end
48
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Web47core
4
- VERSION = '2.0.1'
4
+ VERSION = '2.2.15'
5
5
  end
data/lib/web47core.rb CHANGED
@@ -1,30 +1,48 @@
1
1
  require 'web47core/config'
2
2
  require 'app/models/concerns/app47_logger'
3
+ require 'app/models/concerns/api_tokenable'
4
+ require 'app/models/concerns/aws_configuration'
5
+ require 'app/models/concerns/core_system_configuration'
6
+ require 'app/models/concerns/core_account'
3
7
  require 'app/models/concerns/cipher_able'
4
8
  require 'app/models/concerns/cdn_url'
9
+ require 'app/models/concerns/delayed_job_configuration'
5
10
  require 'app/models/concerns/email_able'
11
+ require 'app/models/concerns/encrypted_password'
12
+ require 'app/models/concerns/google_sso_configuration'
6
13
  require 'app/models/concerns/search_able'
7
14
  require 'app/models/concerns/role_able'
8
15
  require 'app/models/concerns/time_zone_able'
16
+ require 'app/models/concerns/slack_configuration'
17
+ require 'app/models/concerns/server_process_able'
18
+ require 'app/models/concerns/core_smtp_configuration'
9
19
  require 'app/models/concerns/standard_model'
10
20
  require 'app/models/concerns/switchboard_able'
11
- require 'app/models/concerns/core_system_configuration'
12
- require 'app/models/concerns/core_account'
21
+ require 'app/models/concerns/switchboard_configuration'
13
22
  require 'app/models/concerns/secure_fields'
14
- require 'app/models/concerns/encrypted_password'
23
+ require 'app/models/concerns/twilio_configuration'
24
+ require 'app/models/concerns/zendesk_configuration'
15
25
  require 'app/models/command_job'
16
26
  require 'app/models/command_job_log'
17
- require 'app/models/delayed_job'
18
- require 'app/models/redis_configuration'
27
+ #
28
+ # Delayed jobs
29
+ #
30
+ require 'app/models/delayed/backend/delayed_job'
31
+ require 'app/models/delayed/plugins/time_keeper'
32
+ require 'app/models/delayed/jobs/metric'
33
+ require 'app/models/delayed/jobs/run'
34
+ require 'app/models/delayed/jobs/worker'
35
+
36
+ require 'app/models/api_token'
19
37
  require 'app/models/notification'
20
- require 'app/models/sms_notification'
21
38
  require 'app/models/template'
22
- require 'app/models/notification_template'
23
39
  require 'app/models/email_notification'
24
40
  require 'app/models/email_template'
41
+ require 'app/models/redis_configuration'
42
+ require 'app/models/notification_template'
25
43
  require 'app/models/slack_notification'
26
- require 'app/models/smtp_configuration'
27
44
  require 'app/models/sms_notification'
45
+ require 'app/models/smtp_configuration'
28
46
  #
29
47
  # Cron
30
48
  #
@@ -33,8 +51,12 @@ require 'app/jobs/cron/job'
33
51
  require 'app/jobs/cron/tab'
34
52
  require 'app/jobs/cron/job_tab'
35
53
  require 'app/jobs/cron/command'
36
- require 'app/jobs/cron/server'
37
54
  require 'app/jobs/cron/trim_collection'
55
+ require 'app/jobs/cron/record_delayed_job_metrics'
56
+ require 'app/jobs/cron/restart_orphaned_delayed_jobs'
57
+ require 'app/jobs/cron/trim_delayed_job_metrics'
58
+ require 'app/jobs/cron/trim_delayed_job_workers'
59
+ require 'app/jobs/cron/server'
38
60
  require 'app/jobs/cron/switchboard_sync_configuration'
39
61
  require 'app/jobs/cron/switchboard_sync_models'
40
62
  require 'app/jobs/cron/trim_audit_logs'
@@ -60,3 +82,5 @@ require 'app/controllers/concerns/core_controller'
60
82
  require 'app/controllers/concerns/core_system_configuration_controller'
61
83
  require 'app/controllers/concerns/core_cron_controller'
62
84
  require 'app/controllers/concerns/core_delayed_jobs_controller'
85
+ require 'app/controllers/concerns/core_delayed_job_metrics_controller'
86
+ require 'app/controllers/concerns/core_delayed_job_workers_controller'