activerabbit-ai 0.5.0 โ†’ 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 915dd514027b844f4959d32085c1c26dfb69924b911fe31472c9f89eeaba0fd5
4
- data.tar.gz: 128a2fe0aaaf197b72ee457c1f240df1057d63c029fd65dba5a92f890ff18de5
3
+ metadata.gz: d349dcd013eaa9c787d218d695e4e45d779b4c236fc908b84edbda42ac0471b8
4
+ data.tar.gz: 92c559e16a816706558028588f8869212b05e41fb10507d5fc8c40902ca95a62
5
5
  SHA512:
6
- metadata.gz: 382c8d0304b2048f8fa26c481530f1bfe19abac6ec8ec21c9fed57aebac84c2e40750fca4329b9864c301d6e4de7d9b0b411a97bd66d5ee9fe00cfbc58406d6e
7
- data.tar.gz: d6ce4cf362160793d49d1d6d00444352c24b03eda9cb22be079d37ce65e37ee42e918be33ade5b76df761670d8bde4837dcd6c7a3893b09048774aee2a2e5fd2
6
+ metadata.gz: f26d6cd48e6ba74b87cbdfb5adae1c89dfba13c623c0acfc7418c7061bf3559c66a48061a136a166bdb69a5e06cf1a5d6ee5067c35e20932dd4b98966ad03bcf
7
+ data.tar.gz: daf29ace4927b42c216a8c88df12291f783b599d32a4443e19426854f336c00fb571ae63d2f6003d05707409b150768cc0ed1c42fd12d348e7bb80689a96aabb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.5.2] - 2025-12-22
6
+
7
+ ### Fixed
8
+ - **Batch sending resilience**: `HttpClient#post_batch` is now nil-safe and supports both queued items and raw payloads (prevents `undefined method [] for nil` during flush).
9
+ - **Dedupe resilience**: Deduplication key building is now safe when `context` is nil/non-hash.
10
+
11
+ ### Changed
12
+ - **Default API URL**: Default `api_url` aligned to `https://app.activerabbit.ai` (dashboard + API host).
13
+
5
14
  ## [0.5.0] - 2025-12-12
6
15
 
7
16
  ### Changed
data/README.md CHANGED
@@ -38,7 +38,7 @@ Or install it yourself as:
38
38
  ActiveRabbit::Client.configure do |config|
39
39
  config.api_key = ENV['ACTIVERABBIT_API_KEY']
40
40
  config.project_id = ENV['ACTIVERABBIT_PROJECT_ID']
41
- config.api_url = ENV.fetch('ACTIVERABBIT_API_URL', 'https://api.activerabbit.ai')
41
+ config.api_url = ENV.fetch('ACTIVERABBIT_API_URL', 'https://app.activerabbit.ai')
42
42
  config.environment = Rails.env
43
43
  end
44
44
  ```
data/TESTING_GUIDE.md CHANGED
@@ -69,7 +69,7 @@ end
69
69
  RSpec.describe ActiveRabbit::Client::Configuration do
70
70
  describe "environment variable loading" do
71
71
  it "loads API key from environment" do
72
- allow(ENV).to receive(:[]).with("active_rabbit_API_KEY").and_return("test-key")
72
+ allow(ENV).to receive(:[]).with("ACTIVERABBIT_API_KEY").and_return("test-key")
73
73
  config = described_class.new
74
74
  expect(config.api_key).to eq("test-key")
75
75
  end
@@ -486,8 +486,8 @@ puts "๐Ÿงช Testing ActiveRabbit Integration..."
486
486
  # Test 1: Configuration
487
487
  puts "\n1. Testing Configuration..."
488
488
  ActiveRabbit::Client.configure do |config|
489
- config.api_key = ENV['active_rabbit_API_KEY'] || 'test-key'
490
- config.project_id = ENV['active_rabbit_PROJECT_ID'] || 'test-project'
489
+ config.api_key = ENV['ACTIVERABBIT_API_KEY'] || 'test-key'
490
+ config.project_id = ENV['ACTIVERABBIT_PROJECT_ID'] || 'test-project'
491
491
  config.environment = 'test'
492
492
  end
493
493
 
@@ -578,8 +578,8 @@ jobs:
578
578
  bundle exec rspec
579
579
  ruby script/test_activerabbit.rb
580
580
  env:
581
- active_rabbit_API_KEY: ${{ secrets.active_rabbit_API_KEY }}
582
- active_rabbit_PROJECT_ID: ${{ secrets.active_rabbit_PROJECT_ID }}
581
+ ACTIVERABBIT_API_KEY: ${{ secrets.ACTIVERABBIT_API_KEY }}
582
+ ACTIVERABBIT_PROJECT_ID: ${{ secrets.ACTIVERABBIT_PROJECT_ID }}
583
583
  ```
584
584
 
585
585
  This comprehensive testing approach ensures your Rails application and ActiveRabbit gem integration is thoroughly tested before production deployment, covering functionality, performance, and resilience scenarios.
@@ -5,12 +5,12 @@
5
5
  # config/initializers/active_rabbit.rb
6
6
  ActiveRabbit::Client.configure do |config|
7
7
  # Required configuration
8
- config.api_key = ENV['active_rabbit_API_KEY']
9
- config.project_id = ENV['active_rabbit_PROJECT_ID']
8
+ config.api_key = ENV['ACTIVERABBIT_API_KEY']
9
+ config.project_id = ENV['ACTIVERABBIT_PROJECT_ID']
10
10
  config.environment = Rails.env
11
11
 
12
12
  # Optional configuration
13
- config.api_url = ENV.fetch('active_rabbit_API_URL', 'https://api.activerabbit.com')
13
+ config.api_url = ENV.fetch('ACTIVERABBIT_API_URL', 'https://api.activerabbit.com')
14
14
  config.release = ENV['HEROKU_SLUG_COMMIT'] || `git rev-parse HEAD`.chomp
15
15
 
16
16
  # Performance settings
@@ -140,8 +140,8 @@ class BackgroundWorker
140
140
  def initialize
141
141
  # Configure ActiveRabbit for background processes
142
142
  ActiveRabbit::Client.configure do |config|
143
- config.api_key = ENV['active_rabbit_API_KEY']
144
- config.project_id = ENV['active_rabbit_PROJECT_ID']
143
+ config.api_key = ENV['ACTIVERABBIT_API_KEY']
144
+ config.project_id = ENV['ACTIVERABBIT_PROJECT_ID']
145
145
  config.environment = ENV.fetch('ENVIRONMENT', 'development')
146
146
  config.server_name = "worker-#{Socket.gethostname}"
147
147
  end
@@ -249,8 +249,8 @@ namespace :data do
249
249
  desc "Migrate user data"
250
250
  task migrate_users: :environment do
251
251
  ActiveRabbit::Client.configure do |config|
252
- config.api_key = ENV['active_rabbit_API_KEY']
253
- config.project_id = ENV['active_rabbit_PROJECT_ID']
252
+ config.api_key = ENV['ACTIVERABBIT_API_KEY']
253
+ config.project_id = ENV['ACTIVERABBIT_PROJECT_ID']
254
254
  config.environment = 'migration'
255
255
  end
256
256
 
@@ -16,13 +16,14 @@ module ActiveRabbit
16
16
  attr_accessor :before_send_event, :before_send_exception
17
17
  attr_accessor :dedupe_window # Time window in seconds for error deduplication (0 = disabled)
18
18
  attr_accessor :revision
19
+ attr_accessor :auto_release_tracking
19
20
  attr_accessor :disable_console_logs
20
21
 
21
22
  def initialize
22
- @api_url = ENV.fetch("active_rabbit_API_URL", "https://api.activerabbit.ai")
23
- @api_key = ENV["active_rabbit_API_KEY"]
24
- @project_id = ENV["active_rabbit_PROJECT_ID"]
25
- @environment = ENV.fetch("active_rabbit_ENVIRONMENT", detect_environment)
23
+ @api_url = ENV.fetch("ACTIVERABBIT_API_URL", "https://app.activerabbit.ai")
24
+ @api_key = ENV["ACTIVERABBIT_API_KEY"]
25
+ @project_id = ENV["ACTIVERABBIT_PROJECT_ID"]
26
+ @environment = ENV["ACTIVERABBIT_ENVIRONMENT"] || detect_environment
26
27
 
27
28
  # HTTP settings
28
29
  @timeout = 30
@@ -68,9 +69,10 @@ module ActiveRabbit
68
69
  @dedupe_window = 300 # 5 minutes by default
69
70
 
70
71
  # Metadata
71
- @release = detect_release
72
72
  @server_name = detect_server_name
73
73
  @logger = detect_logger
74
+ @auto_release_tracking = default_auto_release_tracking
75
+ @revision = detect_release
74
76
 
75
77
  # Callbacks
76
78
  @before_send_event = nil
@@ -138,6 +140,20 @@ module ActiveRabbit
138
140
  ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
139
141
  end
140
142
 
143
+ def default_auto_release_tracking
144
+ # Allow explicit override via env var
145
+ env_value = ENV["ACTIVERABBIT_AUTO_RELEASE_TRACKING"]
146
+ if env_value
147
+ normalized = env_value.to_s.strip.downcase
148
+ return true if %w[1 true yes y on].include?(normalized)
149
+ return false if %w[0 false no n off].include?(normalized)
150
+ end
151
+
152
+ # Default: enabled outside dev/test
153
+ env_name = @environment.to_s
154
+ !%w[development test].include?(env_name)
155
+ end
156
+
141
157
  def detect_release
142
158
  # Try to detect from common CI/deployment environment variables
143
159
  ENV["HEROKU_SLUG_COMMIT"] ||
@@ -32,7 +32,16 @@ module ActiveRabbit
32
32
 
33
33
  def build_key(exception, context)
34
34
  top = Array(exception.backtrace).first.to_s
35
- req_id = context[:request]&.[](:request_id) || context[:request_id] || context[:requestId]
35
+ ctx = context.is_a?(Hash) ? context : {}
36
+ req = ctx[:request] || ctx["request"]
37
+ req_hash = req.is_a?(Hash) ? req : {}
38
+
39
+ req_id =
40
+ req_hash[:request_id] || req_hash["request_id"] ||
41
+ req_hash[:requestId] || req_hash["requestId"] ||
42
+ ctx[:request_id] || ctx["request_id"] ||
43
+ ctx[:requestId] || ctx["requestId"]
44
+
36
45
  [exception.class.name, top, req_id].join("|")
37
46
  end
38
47
  end
@@ -77,11 +77,23 @@ module ActiveRabbit
77
77
 
78
78
  def post_batch(batch_data)
79
79
  # Transform batch data into the format the API expects
80
- events = batch_data.map do |event|
81
- {
82
- type: event[:data][:event_type] || event[:event_type] || event[:type],
83
- data: event[:data]
84
- }
80
+ events = Array(batch_data).filter_map do |event|
81
+ next if event.nil?
82
+
83
+ # Support both:
84
+ # - queued items: { method:, path:, data:, timestamp: }
85
+ # - raw payloads: { ...event fields... }
86
+ data =
87
+ (event.is_a?(Hash) ? (event[:data] || event["data"]) : nil) ||
88
+ (event.is_a?(Hash) ? event : nil)
89
+
90
+ next unless data.is_a?(Hash)
91
+
92
+ type =
93
+ data[:event_type] || data["event_type"] ||
94
+ (event.is_a?(Hash) ? (event[:event_type] || event["event_type"] || event[:type] || event["type"]) : nil)
95
+
96
+ { type: type, data: data }
85
97
  end
86
98
 
87
99
  # Send batch to API
@@ -92,24 +104,29 @@ module ActiveRabbit
92
104
  response
93
105
  end
94
106
 
95
- def post(path, payload)
96
- uri = URI.join(@base_uri.to_s, path)
97
- req = Net::HTTP::Post.new(uri)
98
- req['Content-Type'] = 'application/json'
99
- req["X-Project-Token"] = configuration.api_key
100
- req.body = payload.to_json
107
+ # Create (or confirm) a release in the ActiveRabbit API.
108
+ # Treats 409 conflict (already exists) as success to support multiple servers/dynos.
109
+ def post_release(release_data)
110
+ payload = stringify_and_sanitize(release_data)
111
+ uri = build_uri("/api/v1/releases")
101
112
 
102
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
103
- http.request(req)
104
- end
113
+ log(:info, "[ActiveRabbit] Pinging release to API...")
114
+ log(:debug, "[ActiveRabbit] Release payload: #{safe_preview(payload)}")
105
115
 
106
- unless res.is_a?(Net::HTTPSuccess)
107
- raise APIError, "ActiveRabbit API request failed: #{res.code} #{res.body}"
108
- end
116
+ response = perform_request(uri, :post, payload)
117
+ code = response.code.to_i
109
118
 
110
- JSON.parse(res.body)
119
+ if (200..299).include?(code)
120
+ parse_json_or_empty(response.body)
121
+ elsif code == 409
122
+ # Already exists; return parsed body (usually includes id/version)
123
+ parse_json_or_empty(response.body)
124
+ else
125
+ # Use shared error handling (raises)
126
+ handle_response(response)
127
+ end
111
128
  rescue => e
112
- log(:error, "[ActiveRabbit] HTTP POST failed: #{e.class}: #{e.message}")
129
+ log(:error, "[ActiveRabbit] Release ping failed: #{e.class}: #{e.message}")
113
130
  nil
114
131
  end
115
132
 
@@ -150,6 +167,20 @@ module ActiveRabbit
150
167
 
151
168
  private
152
169
 
170
+ def build_uri(path)
171
+ current_base = URI(configuration.api_url)
172
+ normalized_path = path.start_with?("/") ? path : "/#{path}"
173
+ URI.join(current_base, normalized_path)
174
+ end
175
+
176
+ def parse_json_or_empty(body)
177
+ return {} if body.nil? || body.empty?
178
+
179
+ JSON.parse(body)
180
+ rescue JSON::ParserError
181
+ body
182
+ end
183
+
153
184
  def enqueue_request(method, path, data)
154
185
  return if @shutdown
155
186
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "logger"
4
+ require "concurrent"
4
5
 
5
6
  begin
6
7
  require "rails/railtie"
@@ -39,6 +40,36 @@ module ActiveRabbit
39
40
  config.release ||= detect_release(app)
40
41
  end
41
42
 
43
+ app.config.after_initialize do
44
+ begin
45
+ cfg = ActiveRabbit::Client.configuration
46
+ next unless cfg
47
+ next unless ActiveRabbit::Client.configured?
48
+ next unless cfg.auto_release_tracking
49
+
50
+ version = cfg.revision || cfg.release
51
+ next if version.nil? || version.to_s.strip.empty?
52
+
53
+ metadata = {
54
+ revision: cfg.revision,
55
+ release: cfg.release,
56
+ server_name: cfg.server_name,
57
+ environment: cfg.environment,
58
+ gem_version: ActiveRabbit::Client::VERSION
59
+ }.compact
60
+
61
+ Concurrent::Future.execute do
62
+ ActiveRabbit::Client.notify_release(
63
+ version: version,
64
+ environment: cfg.environment,
65
+ metadata: metadata
66
+ )
67
+ end
68
+ rescue => e
69
+ Rails.logger.debug "[ActiveRabbit] auto release tracking failed: #{e.class}: #{e.message}" if defined?(Rails) && Rails.logger
70
+ end
71
+ end
72
+
42
73
  # if ActiveRabbit::Client.configuration && !ActiveRabbit::Client.configuration.disable_console_logs
43
74
  # if Rails.env.development?
44
75
  # ar_puts "\n=== ActiveRabbit Post-Configure ==="
@@ -1,5 +1,5 @@
1
1
  module ActiveRabbit
2
2
  module Client
3
- VERSION = "0.5.0"
3
+ VERSION = "0.5.2"
4
4
  end
5
5
  end
@@ -127,19 +127,21 @@ module ActiveRabbit
127
127
  track_exception(exception, context: context, user_id: user_id, tags: tags)
128
128
  end
129
129
 
130
- def notify_deploy(project_slug:, status:, user:, version:, started_at: nil, finished_at: nil)
130
+ def notify_release(version: nil, environment: nil, metadata: {})
131
+ return unless configured?
132
+
133
+ cfg = configuration
134
+ version ||= cfg.revision || cfg.release
135
+ environment ||= cfg.environment
136
+ return if version.nil? || version.to_s.strip.empty?
137
+
131
138
  payload = {
132
- revision: Client.configuration.revision,
133
- environment: Client.configuration.environment,
134
- project_slug: project_slug,
135
139
  version: version,
136
- status: status,
137
- user: user,
138
- started_at: started_at,
139
- finished_at: finished_at
140
+ environment: environment,
141
+ metadata: metadata || {}
140
142
  }
141
143
 
142
- http_client.post("/api/v1/deploys", payload)
144
+ http_client.post_release(payload)
143
145
  end
144
146
 
145
147
  def log(level, message)
@@ -14,9 +14,9 @@ puts "๐Ÿš€ ActiveRabbit Production Readiness Test"
14
14
  puts "=" * 50
15
15
 
16
16
  # Test configuration
17
- TEST_API_KEY = ENV['active_rabbit_API_KEY'] || 'test-key-for-validation'
18
- TEST_PROJECT_ID = ENV['active_rabbit_PROJECT_ID'] || 'test-project'
19
- TEST_API_URL = ENV['active_rabbit_API_URL'] || 'https://api.activerabbit.com'
17
+ TEST_API_KEY = ENV['ACTIVERABBIT_API_KEY'] || 'test-key-for-validation'
18
+ TEST_PROJECT_ID = ENV['ACTIVERABBIT_PROJECT_ID'] || 'test-project'
19
+ TEST_API_URL = ENV['ACTIVERABBIT_API_URL'] || 'https://api.activerabbit.com'
20
20
 
21
21
  # Load the gem
22
22
  begin
@@ -374,7 +374,7 @@ puts "=" * 50
374
374
 
375
375
  if TEST_API_KEY == 'test-key-for-validation'
376
376
  puts "โš ๏ธ Note: Tests run with mock configuration"
377
- puts " Set active_rabbit_API_KEY and active_rabbit_PROJECT_ID"
377
+ puts " Set ACTIVERABBIT_API_KEY and ACTIVERABBIT_PROJECT_ID"
378
378
  puts " environment variables for full integration testing"
379
379
  else
380
380
  puts "โœ… Full integration test completed with real API"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerabbit-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Shapalov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-12-12 00:00:00.000000000 Z
11
+ date: 2025-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -86,7 +86,6 @@ files:
86
86
  - examples/rails_app_testing.rb
87
87
  - examples/rails_integration.rb
88
88
  - examples/standalone_usage.rb
89
- - lib/active_rabbit-client.gemspec
90
89
  - lib/active_rabbit.rb
91
90
  - lib/active_rabbit/client.rb
92
91
  - lib/active_rabbit/client/action_mailer_patch.rb
File without changes