rails_nexus 2.0.0 → 2.0.2

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/rails_nexus/backup_controller.rb +74 -53
  3. data/app/models/rails_nexus/backup_config.rb +90 -0
  4. data/app/services/rails_nexus/backup_runner.rb +62 -0
  5. data/app/services/rails_nexus/backup_service.rb +215 -214
  6. data/app/views/layouts/rails_nexus/application.html.erb +8 -0
  7. data/app/views/rails_nexus/backup/_form.html.erb +185 -0
  8. data/app/views/rails_nexus/backup/config_history.html.erb +67 -0
  9. data/app/views/rails_nexus/backup/edit.html.erb +15 -0
  10. data/app/views/rails_nexus/backup/history.html.erb +60 -0
  11. data/app/views/rails_nexus/backup/index.html.erb +91 -147
  12. data/app/views/rails_nexus/backup/new.html.erb +15 -0
  13. data/config/routes.rb +9 -6
  14. data/lib/generators/rails_nexus/install_generator.rb +1 -13
  15. data/lib/generators/rails_nexus/templates/migration.rb +119 -11
  16. data/lib/generators/rails_nexus/templates/rails_nexus.rb +1 -1
  17. data/lib/rails_nexus/version.rb +1 -1
  18. data/lib/tasks/rails_nexus.rake +71 -0
  19. metadata +8 -16
  20. data/app/models/rails_nexus/database_stat.rb +0 -128
  21. data/app/models/rails_nexus/event.rb +0 -101
  22. data/app/models/rails_nexus/metric.rb +0 -107
  23. data/app/models/rails_nexus/nginx_metric.rb +0 -112
  24. data/app/models/rails_nexus/server_metric.rb +0 -168
  25. data/app/views/rails_nexus/backup/files.html.erb +0 -53
  26. data/app/views/rails_nexus/backup/settings.html.erb +0 -158
  27. data/config/initializers/rails_nexus.rb +0 -18
  28. data/db/migrate/20240330122311_create_rails_nexus_logged_exceptions.rb +0 -22
  29. data/lib/generators/rails_nexus/templates/migration_advanced_features.rb +0 -30
  30. data/lib/generators/rails_nexus/templates/migration_cron_jobs.rb +0 -22
  31. data/lib/generators/rails_nexus/templates/migration_new_schema.rb +0 -120
  32. data/lib/generators/rails_nexus/templates/migration_platform_detection.rb +0 -11
  33. data/lib/generators/rails_nexus/templates/migration_webhook_deliveries.rb +0 -21
  34. data/lib/generators/rails_nexus/templates/migration_workflow.rb +0 -29
@@ -0,0 +1,15 @@
1
+ <% page_title "New Backup Config" %>
2
+
3
+ <div class="space-y-6">
4
+ <nav class="rn-breadcrumbs" aria-label="Breadcrumb">
5
+ <a href="<%= root_path %>" class="rn-breadcrumb">Dashboard</a>
6
+ <span class="rn-breadcrumb-sep">/</span>
7
+ <a href="<%= backup_path %>" class="rn-breadcrumb">Backups</a>
8
+ <span class="rn-breadcrumb-sep">/</span>
9
+ <span class="rn-breadcrumb-current">New Config</span>
10
+ </nav>
11
+
12
+ <h1 class="text-2xl font-bold" style="color: var(--rn-text)">New Backup Configuration</h1>
13
+
14
+ <%= render "form" %>
15
+ </div>
data/config/routes.rb CHANGED
@@ -42,12 +42,15 @@ RailsNexus::Engine.routes.draw do
42
42
  get "n1_patterns", to: "n1_patterns#index", as: :n1_patterns
43
43
 
44
44
  # Backup management
45
- get "backup", to: "backup#index", as: :backup
46
- get "backup/files", to: "backup#files", as: :backup_files
47
- post "backup/trigger/:model", to: "backup#trigger", as: :backup_trigger
48
- get "backup/health", to: "backup#health", as: :backup_health
49
- get "backup/settings", to: "backup#settings", as: :backup_settings
50
- patch "backup/settings", to: "backup#update_settings", as: :backup_update_settings
45
+ get "backup", to: "backup#index", as: :backup
46
+ get "backup/new", to: "backup#new", as: :new_backup
47
+ post "backup", to: "backup#create"
48
+ get "backup/history", to: "backup#all_history", as: :backup_history
49
+ get "backup/:id/edit", to: "backup#edit", as: :edit_backup
50
+ patch "backup/:id", to: "backup#update"
51
+ delete "backup/:id", to: "backup#destroy", as: :backup_destroy
52
+ post "backup/:id/trigger", to: "backup#trigger", as: :backup_trigger
53
+ get "backup/:id/history", to: "backup#history", as: :backup_config_history
51
54
 
52
55
  # Settings and webhook management
53
56
  get "settings", to: "settings#index", as: :settings
@@ -8,16 +8,6 @@ module RailsNexus
8
8
  class InstallGenerator < Rails::Generators::Base
9
9
  include Rails::Generators::Migration
10
10
 
11
- MIGRATIONS = {
12
- "migration.rb" => "create_rails_nexus_exceptions.rb",
13
- "migration_advanced_features.rb" => "add_advanced_features_to_rails_nexus_exceptions.rb",
14
- "migration_platform_detection.rb" => "add_platform_to_rails_nexus_exceptions.rb",
15
- "migration_workflow.rb" => "add_workflow_to_rails_nexus.rb",
16
- "migration_cron_jobs.rb" => "create_rails_nexus_cron_jobs.rb",
17
- "migration_webhook_deliveries.rb" => "create_rails_nexus_webhook_deliveries.rb",
18
- "migration_new_schema.rb" => "create_rails_nexus_metrics_tables.rb"
19
- }.freeze
20
-
21
11
  source_root File.expand_path("templates", __dir__)
22
12
  desc "Install RailsNexus into your Rails application"
23
13
 
@@ -26,9 +16,7 @@ module RailsNexus
26
16
  end
27
17
 
28
18
  def create_migration_files
29
- MIGRATIONS.each do |source, destination|
30
- migration_template source, "db/migrate/#{destination}"
31
- end
19
+ migration_template "migration.rb", "db/migrate/create_rails_nexus_tables.rb"
32
20
  end
33
21
 
34
22
  def create_initializer
@@ -1,18 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreateRailsNexusLoggedExceptions < ActiveRecord::Migration<%= "[#{ActiveRecord::Migration.current_version}]" %>
3
+ class CreateRailsNexusTables < ActiveRecord::Migration[<%= "[#{ActiveRecord::Migration.current_version}]" %>]
4
4
  def change
5
+ # ─── rails_nexus_exceptions ──────────────────────────────────────
5
6
  create_table :rails_nexus_exceptions do |t|
6
- t.string :exception_class
7
- t.string :controller_name
8
- t.string :action_name
9
- t.text :message
10
- t.text :backtrace
11
- t.text :environment
12
- t.text :request
13
- t.string :user_info
14
- t.string :user_agent
15
- t.string :remote_ip
7
+ # Core exception data
8
+ t.string :exception_class
9
+ t.string :controller_name
10
+ t.string :action_name
11
+ t.text :message
12
+ t.text :backtrace
13
+ t.text :environment
14
+ t.text :request
15
+ t.string :user_info
16
+ t.string :user_agent
17
+ t.string :remote_ip
18
+
19
+ # Fingerprinting & deduplication
20
+ t.string :fingerprint
21
+ t.integer :occurrence_count, default: 1
22
+
23
+ # Platform detection
24
+ t.string :platform, default: "web"
25
+ t.string :platform_version
26
+ t.string :device_type
27
+ t.string :app_version
28
+
29
+ # Advanced features
30
+ t.text :cause_chain
31
+ t.text :breadcrumbs
32
+ t.text :system_health
33
+ t.text :local_variables
34
+ t.text :instance_variables
35
+
36
+ # User impact tracking
37
+ t.string :user_id
38
+ t.string :user_type
39
+
40
+ # Workflow management
41
+ t.string :priority
42
+ t.string :assigned_to
43
+ t.datetime :assigned_at
44
+ t.datetime :snoozed_until
45
+ t.boolean :muted, default: false
46
+ t.datetime :muted_at
47
+ t.integer :comments_count, default: 0
16
48
 
17
49
  t.timestamps
18
50
  end
@@ -20,5 +52,81 @@ class CreateRailsNexusLoggedExceptions < ActiveRecord::Migration<%= "[#{ActiveRe
20
52
  add_index :rails_nexus_exceptions, :created_at
21
53
  add_index :rails_nexus_exceptions, :exception_class
22
54
  add_index :rails_nexus_exceptions, [:controller_name, :action_name]
55
+ add_index :rails_nexus_exceptions, :fingerprint
56
+ add_index :rails_nexus_exceptions, :occurrence_count
57
+ add_index :rails_nexus_exceptions, :platform
58
+ add_index :rails_nexus_exceptions, [:platform, :exception_class]
59
+ add_index :rails_nexus_exceptions, :priority
60
+ add_index :rails_nexus_exceptions, :assigned_to
61
+ add_index :rails_nexus_exceptions, :muted
62
+ add_index :rails_nexus_exceptions, :user_id
63
+
64
+ # ─── rails_nexus_comments ────────────────────────────────────────
65
+ create_table :rails_nexus_comments do |t|
66
+ t.references :logged_exception, null: false, foreign_key: { to_table: :rails_nexus_exceptions }
67
+ t.string :author, null: false
68
+ t.text :body, null: false
69
+ t.string :comment_type, default: "comment"
70
+
71
+ t.timestamps
72
+ end
73
+
74
+ add_index :rails_nexus_comments, [:logged_exception_id, :created_at]
75
+
76
+ # ─── rails_nexus_cron_jobs ───────────────────────────────────────
77
+ create_table :rails_nexus_cron_jobs do |t|
78
+ t.string :name, null: false
79
+ t.string :status, null: false, default: "pending"
80
+ t.text :output
81
+ t.text :error_message
82
+ t.float :duration
83
+ t.string :hostname
84
+ t.json :metadata
85
+ t.datetime :started_at
86
+ t.datetime :finished_at
87
+
88
+ t.timestamps
89
+ end
90
+
91
+ add_index :rails_nexus_cron_jobs, :name
92
+ add_index :rails_nexus_cron_jobs, :status
93
+ add_index :rails_nexus_cron_jobs, :created_at
94
+ add_index :rails_nexus_cron_jobs, :started_at
95
+
96
+ # ─── rails_nexus_webhook_deliveries ──────────────────────────────
97
+ create_table :rails_nexus_webhook_deliveries do |t|
98
+ t.string :url, null: false
99
+ t.string :status, null: false, default: "pending"
100
+ t.integer :response_code
101
+ t.text :request_body
102
+ t.text :response_body
103
+ t.float :duration
104
+ t.text :error_message
105
+ t.string :event_type
106
+ t.json :metadata
107
+
108
+ t.timestamps
109
+ end
110
+
111
+ add_index :rails_nexus_webhook_deliveries, :status
112
+ add_index :rails_nexus_webhook_deliveries, :created_at
113
+ add_index :rails_nexus_webhook_deliveries, :url
114
+
115
+ # ─── rails_nexus_backups ─────────────────────────────────────────
116
+ create_table :rails_nexus_backups do |t|
117
+ t.string :model_name, null: false
118
+ t.string :status, null: false
119
+ t.text :file_path
120
+ t.bigint :file_size
121
+ t.float :duration
122
+ t.text :error_message
123
+ t.string :triggered_by
124
+ t.datetime :started_at, null: false
125
+ t.datetime :completed_at
126
+ end
127
+
128
+ add_index :rails_nexus_backups, [:model_name, :started_at]
129
+ add_index :rails_nexus_backups, :status
130
+ add_index :rails_nexus_backups, :started_at
23
131
  end
24
132
  end
@@ -12,7 +12,7 @@ Rails.application.config.to_prepare do
12
12
  # ─── Authentication ──────────────────────────────────────
13
13
  # Protect the dashboard — only authorized users can access
14
14
  # config.auth_block = lambda do |controller|
15
- # controller.current_user&.admin?
15
+ # controller.current_user&.admin? # adjust to your auth model
16
16
  # end
17
17
 
18
18
  # ─── Exception Data ──────────────────────────────────────
@@ -1,3 +1,3 @@
1
1
  module RailsNexus
2
- VERSION = "2.0.0"
2
+ VERSION = "2.0.2"
3
3
  end
@@ -227,4 +227,75 @@ namespace :rails_nexus do
227
227
  count = RailsNexus::WebhookDelivery.where("created_at < ?", days.days.ago).delete_all
228
228
  puts "✓ Deleted #{count} webhook delivery records older than #{days} days"
229
229
  end
230
+
231
+ # ─── Backup Tasks ──────────────────────────────────────────────
232
+
233
+ desc "Run a specific backup by name: rails_nexus:backup[daily_backup]"
234
+ task :backup, [:name] => :environment do |_t, args|
235
+ name = args[:name]
236
+ if name.blank?
237
+ puts "Usage: rake rails_nexus:backup[config_name]"
238
+ puts "Available configs:"
239
+ RailsNexus::BackupConfig.enabled.pluck(:name).each { |n| puts " - #{n}" }
240
+ next
241
+ end
242
+
243
+ result = RailsNexus::BackupRunner.run(name)
244
+ if result[:success]
245
+ puts "✓ Backup "#{name}" completed successfully (#{result[:record].duration_human})"
246
+ else
247
+ puts "✗ Backup "#{name}" failed: #{result[:error]}"
248
+ exit 1
249
+ end
250
+ end
251
+
252
+ desc "Run all enabled backup configs"
253
+ task backup_all: :environment do
254
+ results = RailsNexus::BackupRunner.run_all
255
+ results.each do |r|
256
+ if r[:success]
257
+ puts "✓ #{r[:record].model_name} completed"
258
+ else
259
+ puts "✗ #{r[:record]&.model_name || '?'} failed: #{r[:error]}"
260
+ end
261
+ end
262
+ end
263
+
264
+ desc "Run backups matching current cron schedule"
265
+ task backup_scheduled: :environment do
266
+ results = RailsNexus::BackupRunner.run_scheduled
267
+ if results.empty?
268
+ puts "No backups scheduled for this time"
269
+ else
270
+ results.each do |r|
271
+ if r[:success]
272
+ puts "✓ #{r[:record].model_name} completed"
273
+ else
274
+ puts "✗ #{r[:record]&.model_name || '?'} failed: #{r[:error]}"
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ desc "Clean up old backup records and files (default: 30 days)"
281
+ task :backup_cleanup, [:days] => :environment do |_t, args|
282
+ days = (args[:days] || 30).to_i
283
+ count = RailsNexus::Backup.where("started_at < ?", days.days.ago).delete_all
284
+ puts "✓ Deleted #{count} backup records older than #{days} days"
285
+ end
286
+
287
+ desc "List all backup configs and their status"
288
+ task backup_list: :environment do
289
+ configs = RailsNexus::BackupConfig.order(:name)
290
+ if configs.empty?
291
+ puts "No backup configs found"
292
+ else
293
+ configs.each do |c|
294
+ status = c.enabled? ? "✓" : "✗"
295
+ last = c.recent_backups(limit: 1).first
296
+ last_str = last ? "#{last.status} #{last.started_at&.strftime('%b %d %H:%M')}" : "never"
297
+ puts "#{status} #{c.name.ljust(20)} #{c.adapter.ljust(12)} #{c.database_name.ljust(20)} last: #{last_str}"
298
+ end
299
+ end
300
+ end
230
301
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_nexus
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tamiru Hailu
@@ -139,23 +139,23 @@ files:
139
139
  - app/mailers/rails_nexus/application_mailer.rb
140
140
  - app/models/rails_nexus/application_record.rb
141
141
  - app/models/rails_nexus/backup.rb
142
+ - app/models/rails_nexus/backup_config.rb
142
143
  - app/models/rails_nexus/base_record.rb
143
144
  - app/models/rails_nexus/comment.rb
144
145
  - app/models/rails_nexus/cron_job.rb
145
- - app/models/rails_nexus/database_stat.rb
146
- - app/models/rails_nexus/event.rb
147
146
  - app/models/rails_nexus/logged_exception.rb
148
147
  - app/models/rails_nexus/logged_exception_n1.rb
149
- - app/models/rails_nexus/metric.rb
150
- - app/models/rails_nexus/nginx_metric.rb
151
- - app/models/rails_nexus/server_metric.rb
152
148
  - app/models/rails_nexus/webhook_delivery.rb
149
+ - app/services/rails_nexus/backup_runner.rb
153
150
  - app/services/rails_nexus/backup_service.rb
154
151
  - app/views/layouts/rails_nexus/application.html.erb
155
152
  - app/views/rails_nexus/analytics/index.html.erb
156
- - app/views/rails_nexus/backup/files.html.erb
153
+ - app/views/rails_nexus/backup/_form.html.erb
154
+ - app/views/rails_nexus/backup/config_history.html.erb
155
+ - app/views/rails_nexus/backup/edit.html.erb
156
+ - app/views/rails_nexus/backup/history.html.erb
157
157
  - app/views/rails_nexus/backup/index.html.erb
158
- - app/views/rails_nexus/backup/settings.html.erb
158
+ - app/views/rails_nexus/backup/new.html.erb
159
159
  - app/views/rails_nexus/cron_jobs/index.html.erb
160
160
  - app/views/rails_nexus/database_health/index.html.erb
161
161
  - app/views/rails_nexus/logged_exceptions/_comments_list.html.erb
@@ -178,10 +178,8 @@ files:
178
178
  - app/views/rails_nexus/stats/index.html.erb
179
179
  - config/importmap.rb
180
180
  - config/initializers/date_formats.rb
181
- - config/initializers/rails_nexus.rb
182
181
  - config/locales/en.yml
183
182
  - config/routes.rb
184
- - db/migrate/20240330122311_create_rails_nexus_logged_exceptions.rb
185
183
  - lib/generators/rails_nexus/backup_generator.rb
186
184
  - lib/generators/rails_nexus/customize_generator.rb
187
185
  - lib/generators/rails_nexus/install_generator.rb
@@ -198,12 +196,6 @@ files:
198
196
  - lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_sidebar_controller.js
199
197
  - lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_theme_controller.js
200
198
  - lib/generators/rails_nexus/templates/migration.rb
201
- - lib/generators/rails_nexus/templates/migration_advanced_features.rb
202
- - lib/generators/rails_nexus/templates/migration_cron_jobs.rb
203
- - lib/generators/rails_nexus/templates/migration_new_schema.rb
204
- - lib/generators/rails_nexus/templates/migration_platform_detection.rb
205
- - lib/generators/rails_nexus/templates/migration_webhook_deliveries.rb
206
- - lib/generators/rails_nexus/templates/migration_workflow.rb
207
199
  - lib/generators/rails_nexus/templates/rails_nexus.rb
208
200
  - lib/generators/rails_nexus/templates/views/layouts/rails_nexus/application.html.erb
209
201
  - lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/_exceptions.html.erb
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RailsNexus
4
- class DatabaseStat < BaseRecord
5
- self.table_name = "rails_nexus_database_stats"
6
-
7
- validates :recorded_at, presence: true
8
-
9
- scope :recent, ->(hours = 24) { where("recorded_at >= ?", hours.hours.ago) }
10
- scope :with_tables, -> { where.not(table_name: nil) }
11
- scope :pool_stats, -> { where(table_name: nil) }
12
-
13
- # Collect database health snapshot
14
- def self.collect!
15
- now = Time.current
16
- adapter = ActiveRecord::Base.connection.adapter_name.downcase
17
-
18
- # Connection pool stats
19
- pool = ActiveRecord::Base.connection_pool
20
- stats = pool_stats(pool)
21
- create!(stats.merge(recorded_at: now))
22
-
23
- # Table stats (MySQL only)
24
- if adapter.include?("mysql")
25
- collect_mysql_stats(now)
26
- elsif adapter.include?("postgresql")
27
- collect_postgresql_stats(now)
28
- end
29
- end
30
-
31
- # Get pool utilization over time
32
- def self.pool_utilization(hours: 24)
33
- pool_stats
34
- .where("recorded_at >= ?", hours.hours.ago)
35
- .order(:recorded_at)
36
- .map { |s| { time: s.recorded_at, utilization: s.utilization, busy: s.busy, idle: s.idle } }
37
- end
38
-
39
- # Get slow queries
40
- def self.slow_queries(limit: 10)
41
- where.not(slow_queries: nil)
42
- .order(recorded_at: :desc)
43
- .limit(1)
44
- .flat_map(&:slow_queries)
45
- .first(limit)
46
- end
47
-
48
- # Get N+1 patterns
49
- def self.n1_patterns
50
- where.not(n1_patterns: nil)
51
- .order(recorded_at: :desc)
52
- .limit(1)
53
- .flat_map(&:n1_patterns)
54
- end
55
-
56
- private
57
-
58
- def self.pool_stats(pool)
59
- {
60
- pool_size: pool.size,
61
- busy: pool.connections.size,
62
- idle: pool.size - pool.connections.size,
63
- dead: 0,
64
- waiting: pool.stat[:waiting] || 0,
65
- utilization: pool.size > 0 ? (pool.connections.size.to_f / pool.size * 100).round(1) : 0
66
- }
67
- end
68
-
69
- def self.collect_mysql_stats(now)
70
- conn = ActiveRecord::Base.connection
71
- tables = conn.execute(<<~SQL)
72
- SELECT table_name, table_rows, data_length, index_length, data_free
73
- FROM information_schema.tables
74
- WHERE table_schema = DATABASE()
75
- ORDER BY data_length + index_length DESC
76
- LIMIT 50
77
- SQL
78
-
79
- tables.each do |row|
80
- create!(
81
- table_name: row["TABLE_NAME"],
82
- table_rows: row["TABLE_ROWS"],
83
- table_size: format_size(row["DATA_LENGTH"]),
84
- index_size: format_size(row["INDEX_LENGTH"]),
85
- data_free: format_size(row["DATA_FREE"]),
86
- recorded_at: now
87
- )
88
- end
89
- rescue StandardError => e
90
- Rails.logger.error("[RailsNexus] Failed to collect MySQL stats: #{e.message}") if defined?(Rails)
91
- end
92
-
93
- def self.collect_postgresql_stats(now)
94
- conn = ActiveRecord::Base.connection
95
- tables = conn.execute(<<~SQL)
96
- SELECT schemaname, relname, n_tup_ins, n_tup_upd, n_tup_del,
97
- pg_size_pretty(pg_total_relation_size(relid)) as total_size,
98
- pg_size_pretty(pg_indexes_size(relid)) as index_size
99
- FROM pg_stat_user_tables
100
- ORDER BY pg_total_relation_size(relid) DESC
101
- LIMIT 50
102
- SQL
103
-
104
- tables.each do |row|
105
- create!(
106
- table_name: row["relname"],
107
- table_rows: (row["n_tup_ins"].to_i + row["n_tup_upd"].to_i - row["n_tup_del"].to_i),
108
- table_size: row["total_size"],
109
- index_size: row["index_size"],
110
- recorded_at: now
111
- )
112
- end
113
- rescue StandardError => e
114
- Rails.logger.error("[RailsNexus] Failed to collect PostgreSQL stats: #{e.message}") if defined?(Rails)
115
- end
116
-
117
- def self.format_size(bytes)
118
- return "0 B" unless bytes
119
- units = ["B", "KB", "MB", "GB", "TB"]
120
- size = bytes.to_f
121
- units.each do |unit|
122
- return "#{size.round(1)} #{unit}" if size < 1024
123
- size /= 1024
124
- end
125
- "#{size.round(1)} PB"
126
- end
127
- end
128
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RailsNexus
4
- class Event < BaseRecord
5
- self.table_name = "rails_nexus_events"
6
-
7
- belongs_to :eventable, polymorphic: true, optional: true
8
-
9
- validates :event_type, presence: true, inclusion: { in: %w[
10
- breadcrumb storm_start storm_end
11
- assignment priority_change snooze unsnooze
12
- mute unmute comment note status_change
13
- backup_start backup_complete backup_failed
14
- exception_created exception_resolved
15
- ] }
16
-
17
- validates :created_at, presence: true
18
-
19
- scope :recent, ->(hours = 24) { where("created_at >= ?", hours.hours.ago) }
20
- scope :by_type, ->(type) { where(event_type: type) }
21
- scope :breadcrumbs, -> { by_type("breadcrumb") }
22
- scope :workflow_events, -> { where(event_type: %w[assignment priority_change snooze unsnooze mute unmute comment note]) }
23
- scope :storm_events, -> { where(event_type: %w[storm_start storm_end]) }
24
- scope :audit_trail, ->(eventable) { where(eventable: eventable).order(created_at: :desc) }
25
-
26
- # Record a breadcrumb
27
- def self.breadcrumb!(message:, metadata: {}, author: nil)
28
- create!(
29
- event_type: "breadcrumb",
30
- message: message,
31
- metadata: metadata,
32
- author: author,
33
- created_at: Time.current
34
- )
35
- end
36
-
37
- # Record a storm event
38
- def self.storm_start!(threshold:, current_rate:)
39
- create!(
40
- event_type: "storm_start",
41
- message: "Error storm detected: #{current_rate} errors/sec (threshold: #{threshold}/sec)",
42
- metadata: { threshold: threshold, current_rate: current_rate },
43
- created_at: Time.current
44
- )
45
- end
46
-
47
- def self.storm_end!(duration_seconds:)
48
- create!(
49
- event_type: "storm_end",
50
- message: "Error storm ended after #{duration_seconds.round(1)}s",
51
- metadata: { duration_seconds: duration_seconds },
52
- created_at: Time.current
53
- )
54
- end
55
-
56
- # Record a workflow event
57
- def self.workflow!(event_type:, eventable:, message:, author: nil, metadata: {})
58
- create!(
59
- event_type: event_type,
60
- eventable: eventable,
61
- message: message,
62
- author: author,
63
- metadata: metadata,
64
- created_at: Time.current
65
- )
66
- end
67
-
68
- # Get event timeline for an exception
69
- def self.timeline_for(exception)
70
- where(eventable: exception).order(created_at: :asc)
71
- end
72
-
73
- # Cleanup old events
74
- def self.cleanup!(retention_days: 30)
75
- where("created_at < ?", retention_days.days.ago).delete_all
76
- end
77
-
78
- # Human-readable event type
79
- def type_label
80
- case event_type
81
- when "breadcrumb" then "🔍 Breadcrumb"
82
- when "storm_start" then "🌪️ Storm Start"
83
- when "storm_end" then "☀️ Storm End"
84
- when "assignment" then "👤 Assigned"
85
- when "priority_change" then " priority Changed"
86
- when "snooze" then "😴 Snoozed"
87
- when "unsnooze" then "⏰ Unsnoozed"
88
- when "mute" then "🔇 Muted"
89
- when "unmute" then "🔊 Unmuted"
90
- when "comment" then "💬 Comment"
91
- when "note" then "📝 Note"
92
- when "backup_start" then "💾 Backup Started"
93
- when "backup_complete" then "✅ Backup Complete"
94
- when "backup_failed" then "❌ Backup Failed"
95
- when "exception_created" then "🚨 Exception Created"
96
- when "exception_resolved" then "✓ Exception Resolved"
97
- else event_type.humanize
98
- end
99
- end
100
- end
101
- end