bugwatch-ruby 0.4.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9d4ff0ad03b9ab70f5f37f3a4656f7b1746a86c22ee33e429242c08c0527036
4
- data.tar.gz: 0a9c5bc904036439926f2e7097d07db78d8e5139ed3d2df81a57c98836661a97
3
+ metadata.gz: c4cb56c42da0cab5b2e855deca671231a744b1624b5fcbd6dbef365f30bc9d08
4
+ data.tar.gz: 92889ce7b6cec458ee2c75c15759756acbaa32981051704c9a3534a42e58fe1d
5
5
  SHA512:
6
- metadata.gz: 59abfb1a125beba2a9aff1676e0299625e6b352cb4b71005b6e4a2854cff3d19758578ce6a91addadbd64f8a7656449e3766da0b3edc5659dc6f49a3953ce733
7
- data.tar.gz: 579309e1a64318f07275fc4139481021d94cd01fb816f36b42cd5b8f8a0eac273d6e690b920649cd8787eee59b555b371592fe308f16396b91c84c118b2925df
6
+ metadata.gz: 479c753810d0c158b339281f7f7c5c083134bfe4321cfe7739a28d24d14fc3c82fb26e17330de1bc92f13e41e66e2726c4fe0b42b2d5110301c3410e566fc564
7
+ data.tar.gz: 6b2a55bf2cdd3a01fd82bd96a51c75f50fbbc48a373b8102c7c6a301ab748c75f7434084402d56cbf3b2009a838648205fa95c4706eb07655a423ef290f35d49
data/README.md CHANGED
@@ -121,6 +121,66 @@ release: bundle exec rails db:migrate && bundle exec rails bugwatch:track_deploy
121
121
 
122
122
  The build will write the commit SHA to `REVISION`, and the release phase will read it back and report the deploy to BugWatch.
123
123
 
124
+ ## User Feedback Widget
125
+
126
+ Drop a feedback form into any view so users can report issues directly from your app. The form submits to BugWatch's feedback API — no controller code needed.
127
+
128
+ ```erb
129
+ <%%= bugwatch_feedback_widget %>
130
+ ```
131
+
132
+ The helper renders a plain, unstyled HTML form with CSS classes you can target:
133
+
134
+ | Class | Element |
135
+ |-------|---------|
136
+ | `.bugwatch-feedback-form` | The `<form>` wrapper |
137
+ | `.bugwatch-feedback-field` | Each field's `<div>` |
138
+ | `.bugwatch-feedback-label` | `<label>` elements |
139
+ | `.bugwatch-feedback-input` | Text/email `<input>` |
140
+ | `.bugwatch-feedback-textarea` | The message `<textarea>` |
141
+ | `.bugwatch-feedback-submit` | Submit `<button>` |
142
+ | `.bugwatch-feedback-success` | Success message (hidden by default) |
143
+ | `.bugwatch-feedback-error` | Error message (hidden by default) |
144
+
145
+ ### Options
146
+
147
+ ```erb
148
+ <%%= bugwatch_feedback_widget(
149
+ user_email: current_user&.email,
150
+ user_name: current_user&.name,
151
+ placeholder: "Describe the issue...",
152
+ submit_text: "Report Issue",
153
+ success_message: "We got it — thanks!",
154
+ issue_id: @issue_id, # optional: link to a specific BugWatch issue
155
+ metadata: { page: "checkout" }
156
+ ) %>
157
+ ```
158
+
159
+ ### Styling example (Tailwind)
160
+
161
+ ```css
162
+ .bugwatch-feedback-form { @apply space-y-4 max-w-md; }
163
+ .bugwatch-feedback-label { @apply block text-sm font-medium text-gray-700; }
164
+ .bugwatch-feedback-input,
165
+ .bugwatch-feedback-textarea { @apply w-full border rounded-lg px-3 py-2 text-sm; }
166
+ .bugwatch-feedback-submit { @apply bg-blue-600 text-white px-4 py-2 rounded-lg text-sm; }
167
+ .bugwatch-feedback-success { @apply text-green-600 text-sm; }
168
+ .bugwatch-feedback-error { @apply text-red-600 text-sm; }
169
+ ```
170
+
171
+ ### Server-side feedback
172
+
173
+ You can also send feedback from Ruby (e.g. from a controller that handles your own form):
174
+
175
+ ```ruby
176
+ Bugwatch.send_feedback(
177
+ params[:message],
178
+ email: current_user.email,
179
+ name: current_user.name,
180
+ url: request.original_url
181
+ )
182
+ ```
183
+
124
184
  ## How it works
125
185
 
126
186
  1. `Bugwatch::Middleware` wraps your entire Rack stack.
@@ -9,7 +9,11 @@ module Bugwatch
9
9
  :logger,
10
10
  :enable_performance_tracking,
11
11
  :sample_rate,
12
- :ignore_request_paths
12
+ :ignore_request_paths,
13
+ :enable_db_tracking,
14
+ :db_sample_rate,
15
+ :db_query_threshold_ms,
16
+ :max_queries_per_request
13
17
 
14
18
  def initialize
15
19
  @endpoint = nil
@@ -20,6 +24,10 @@ module Bugwatch
20
24
  @enable_performance_tracking = true
21
25
  @sample_rate = 1.0
22
26
  @ignore_request_paths = []
27
+ @enable_db_tracking = true
28
+ @db_sample_rate = 1.0
29
+ @db_query_threshold_ms = 0.0
30
+ @max_queries_per_request = 200
23
31
  end
24
32
 
25
33
  def notify_for_release_stage?
@@ -0,0 +1,61 @@
1
+ module Bugwatch
2
+ class DbQueryBuffer
3
+ BATCH_SIZE = 50
4
+ FLUSH_INTERVAL = 15 # seconds
5
+
6
+ def initialize(config: Bugwatch.configuration)
7
+ @config = config
8
+ @sender = DbQuerySender.new(config: config)
9
+ @mutex = Mutex.new
10
+ @buffer = []
11
+ start_flusher
12
+ end
13
+
14
+ def push(payload)
15
+ should_flush = false
16
+
17
+ @mutex.synchronize do
18
+ @buffer << payload
19
+ should_flush = @buffer.size >= BATCH_SIZE
20
+ end
21
+
22
+ flush if should_flush
23
+ end
24
+
25
+ def flush
26
+ batch = @mutex.synchronize do
27
+ items = @buffer
28
+ @buffer = []
29
+ items
30
+ end
31
+
32
+ @sender.send_batch(batch) unless batch.empty?
33
+ rescue StandardError
34
+ # Never let flushing break the app
35
+ end
36
+
37
+ def shutdown
38
+ stop_flusher
39
+ flush
40
+ end
41
+
42
+ private
43
+
44
+ def start_flusher
45
+ @flusher = Thread.new do
46
+ loop do
47
+ sleep FLUSH_INTERVAL
48
+ flush
49
+ end
50
+ rescue StandardError
51
+ # Silently handle thread errors
52
+ end
53
+ @flusher.abort_on_exception = false
54
+ @flusher.daemon = true if @flusher.respond_to?(:daemon=)
55
+ end
56
+
57
+ def stop_flusher
58
+ @flusher&.kill
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,66 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "json"
4
+
5
+ module Bugwatch
6
+ class DbQuerySender
7
+ TIMEOUT = 3
8
+
9
+ def initialize(config: Bugwatch.configuration)
10
+ @config = config
11
+ end
12
+
13
+ def send_batch(grouped_payloads)
14
+ return if grouped_payloads.empty?
15
+ return unless @config.api_key
16
+ return unless @config.endpoint
17
+
18
+ Thread.new do
19
+ post_batch(grouped_payloads)
20
+ rescue StandardError
21
+ # Fire-and-forget: swallow all errors
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def post_batch(grouped_payloads)
28
+ records = grouped_payloads.flat_map do |group|
29
+ group[:queries].map do |q|
30
+ {
31
+ transaction_name: group[:transaction_name],
32
+ environment: group[:environment],
33
+ occurred_at: group[:occurred_at],
34
+ sql: q[:sql],
35
+ raw_sql: q[:raw_sql],
36
+ duration_ms: q[:duration_ms],
37
+ name: q[:name],
38
+ operation: q[:operation],
39
+ caller_location: q[:caller_location]
40
+ }
41
+ end
42
+ end
43
+
44
+ return if records.empty?
45
+
46
+ uri = URI.parse("#{@config.endpoint.chomp("/")}/api/v1/db_queries/batch")
47
+
48
+ http = Net::HTTP.new(uri.host, uri.port)
49
+ http.use_ssl = uri.scheme == "https"
50
+ http.open_timeout = TIMEOUT
51
+ http.read_timeout = TIMEOUT
52
+ http.write_timeout = TIMEOUT
53
+
54
+ request = Net::HTTP::Post.new(uri.path)
55
+ request["Content-Type"] = "application/json"
56
+ request["X-Api-Key"] = @config.api_key
57
+ request["X-BugWatch-Ruby"] = Bugwatch::VERSION
58
+
59
+ request.body = JSON.generate({ queries: records })
60
+
61
+ http.request(request)
62
+ rescue StandardError
63
+ # Silently discard network errors
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,94 @@
1
+ require "active_support/notifications"
2
+
3
+ module Bugwatch
4
+ module DbTracker
5
+ THREAD_KEY = :bugwatch_db_tracker
6
+ IGNORED_NAMES = %w[SCHEMA EXPLAIN].freeze
7
+ IGNORED_SQL_PATTERNS = /\A\s*(BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE SAVEPOINT)/i
8
+
9
+ CALLER_FILTER = %r{/(active_record|bugwatch|ruby/gems)/}
10
+
11
+ module_function
12
+
13
+ def subscribe!
14
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
15
+ event = ActiveSupport::Notifications::Event.new(*args)
16
+ handle_event(event)
17
+ end
18
+ end
19
+
20
+ def start_request(collecting:)
21
+ Thread.current[THREAD_KEY] = {
22
+ queries: [],
23
+ total_db_ms: 0.0,
24
+ collecting: collecting
25
+ }
26
+ end
27
+
28
+ def finish_request
29
+ state = Thread.current[THREAD_KEY]
30
+ Thread.current[THREAD_KEY] = nil
31
+ state
32
+ end
33
+
34
+ def clear
35
+ Thread.current[THREAD_KEY] = nil
36
+ end
37
+
38
+ def handle_event(event)
39
+ state = Thread.current[THREAD_KEY]
40
+ return unless state
41
+
42
+ name = event.payload[:name].to_s
43
+ sql = event.payload[:sql].to_s
44
+ return if IGNORED_NAMES.include?(name)
45
+ return if sql.match?(IGNORED_SQL_PATTERNS)
46
+
47
+ duration_ms = event.duration
48
+
49
+ state[:total_db_ms] += duration_ms
50
+
51
+ return unless state[:collecting]
52
+
53
+ config = Bugwatch.configuration
54
+ return if duration_ms < config.db_query_threshold_ms
55
+ return if state[:queries].size >= config.max_queries_per_request
56
+
57
+ state[:queries] << {
58
+ sql: sanitize_sql(sql),
59
+ raw_sql: sql,
60
+ duration_ms: duration_ms.round(2),
61
+ name: name.presence,
62
+ operation: extract_operation(sql),
63
+ caller_location: extract_caller
64
+ }
65
+ rescue StandardError
66
+ # Never let tracking break the app
67
+ end
68
+
69
+ def sanitize_sql(sql)
70
+ s = sql.dup
71
+ s.gsub!(/'(?:[^'\\]|\\.)*'/, "?")
72
+ s.gsub!(/"(?:[^"\\]|\\.)*"/, "?") unless s.include?(".")
73
+ s.gsub!(/\b\d+(\.\d+)?\b/, "?")
74
+ s.gsub!(/\b(TRUE|FALSE|NULL)\b/i, "?")
75
+ s.gsub!(/IN\s*\(\s*\?(?:\s*,\s*\?)*\s*\)/i, "IN (?)")
76
+ s
77
+ end
78
+
79
+ def extract_operation(sql)
80
+ sql.strip.split(/\s/, 2).first&.upcase
81
+ end
82
+
83
+ def extract_caller
84
+ caller_locations(4, 30)&.each do |loc|
85
+ path = loc.path.to_s
86
+ next if path.match?(CALLER_FILTER)
87
+ return "#{loc.path}:#{loc.lineno}"
88
+ end
89
+ nil
90
+ end
91
+
92
+ private_class_method :handle_event, :sanitize_sql, :extract_operation, :extract_caller
93
+ end
94
+ end
@@ -0,0 +1,86 @@
1
+ module Bugwatch
2
+ module FeedbackHelper
3
+ # Renders a minimal, unstyled feedback widget form.
4
+ #
5
+ # The form submits via fetch to the BugWatch feedback API.
6
+ # All markup uses "bugwatch-feedback-*" CSS classes so you
7
+ # can style it however you want without collisions.
8
+ #
9
+ # Options:
10
+ # :user_email - Pre-fill email (e.g. current_user.email)
11
+ # :user_name - Pre-fill name (e.g. current_user.name)
12
+ # :issue_id - Link feedback to a specific BugWatch issue
13
+ # :metadata - Hash of extra data to attach
14
+ # :placeholder - Textarea placeholder text
15
+ # :submit_text - Submit button label (default: "Send Feedback")
16
+ # :success_message - Message shown after successful submit
17
+ #
18
+ def bugwatch_feedback_widget(user_email: nil, user_name: nil, issue_id: nil, metadata: {}, placeholder: "What happened? Tell us about the issue...", submit_text: "Send Feedback", success_message: "Thanks for your feedback!")
19
+ config = Bugwatch.configuration
20
+ endpoint = "#{config.endpoint&.chomp("/")}/api/v1/feedback"
21
+ api_key = config.api_key
22
+ widget_id = "bugwatch-feedback-#{SecureRandom.hex(4)}"
23
+
24
+ html = <<~HTML
25
+ <form id="#{widget_id}" class="bugwatch-feedback-form" data-bugwatch-feedback>
26
+ <div class="bugwatch-feedback-field">
27
+ <label for="#{widget_id}-name" class="bugwatch-feedback-label">Name</label>
28
+ <input type="text" id="#{widget_id}-name" name="name" value="#{ERB::Util.html_escape(user_name.to_s)}" class="bugwatch-feedback-input" />
29
+ </div>
30
+ <div class="bugwatch-feedback-field">
31
+ <label for="#{widget_id}-email" class="bugwatch-feedback-label">Email</label>
32
+ <input type="email" id="#{widget_id}-email" name="email" value="#{ERB::Util.html_escape(user_email.to_s)}" class="bugwatch-feedback-input" />
33
+ </div>
34
+ <div class="bugwatch-feedback-field">
35
+ <label for="#{widget_id}-message" class="bugwatch-feedback-label">Message</label>
36
+ <textarea id="#{widget_id}-message" name="message" required class="bugwatch-feedback-textarea" placeholder="#{ERB::Util.html_escape(placeholder)}"></textarea>
37
+ </div>
38
+ <button type="submit" class="bugwatch-feedback-submit">#{ERB::Util.html_escape(submit_text)}</button>
39
+ <div class="bugwatch-feedback-success" style="display:none">#{ERB::Util.html_escape(success_message)}</div>
40
+ <div class="bugwatch-feedback-error" style="display:none"></div>
41
+ </form>
42
+ <script>
43
+ (function() {
44
+ var form = document.getElementById("#{widget_id}");
45
+ form.addEventListener("submit", function(e) {
46
+ e.preventDefault();
47
+ var btn = form.querySelector(".bugwatch-feedback-submit");
48
+ var success = form.querySelector(".bugwatch-feedback-success");
49
+ var error = form.querySelector(".bugwatch-feedback-error");
50
+ success.style.display = "none";
51
+ error.style.display = "none";
52
+ btn.disabled = true;
53
+
54
+ var body = {
55
+ api_key: #{api_key.to_json},
56
+ message: form.querySelector("[name=message]").value,
57
+ name: form.querySelector("[name=name]").value || undefined,
58
+ email: form.querySelector("[name=email]").value || undefined,
59
+ url: window.location.href
60
+ };
61
+ #{issue_id ? "body.issue_id = #{issue_id.to_json};" : ""}
62
+ #{metadata.any? ? "body.metadata = #{metadata.to_json};" : ""}
63
+
64
+ fetch(#{endpoint.to_json}, {
65
+ method: "POST",
66
+ headers: { "Content-Type": "application/json" },
67
+ body: JSON.stringify(body)
68
+ }).then(function(r) {
69
+ if (!r.ok) throw new Error("Request failed");
70
+ form.querySelector("[name=message]").value = "";
71
+ success.style.display = "";
72
+ btn.disabled = false;
73
+ }).catch(function(err) {
74
+ error.textContent = "Something went wrong. Please try again.";
75
+ error.style.display = "";
76
+ btn.disabled = false;
77
+ });
78
+ });
79
+ })();
80
+ </script>
81
+ HTML
82
+
83
+ html.html_safe
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,58 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "json"
4
+
5
+ module Bugwatch
6
+ class FeedbackSender
7
+ TIMEOUT = 3
8
+
9
+ def initialize(message:, email: nil, name: nil, url: nil, issue_id: nil, metadata: {}, config: Bugwatch.configuration)
10
+ @message = message
11
+ @email = email
12
+ @name = name
13
+ @url = url
14
+ @issue_id = issue_id
15
+ @metadata = metadata
16
+ @config = config
17
+ end
18
+
19
+ def deliver
20
+ return unless @config.api_key
21
+ return unless @config.endpoint
22
+
23
+ Thread.new do
24
+ post_feedback
25
+ rescue StandardError
26
+ # Fire-and-forget: never affect the host app
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def post_feedback
33
+ uri = URI.parse("#{@config.endpoint.chomp("/")}/api/v1/feedback")
34
+
35
+ http = Net::HTTP.new(uri.host, uri.port)
36
+ http.use_ssl = uri.scheme == "https"
37
+ http.open_timeout = TIMEOUT
38
+ http.read_timeout = TIMEOUT
39
+ http.write_timeout = TIMEOUT
40
+
41
+ request = Net::HTTP::Post.new(uri.path)
42
+ request["Content-Type"] = "application/json"
43
+ request["X-Api-Key"] = @config.api_key
44
+ request["X-BugWatch-Ruby"] = Bugwatch::VERSION
45
+
46
+ payload = { message: @message }
47
+ payload[:email] = @email if @email
48
+ payload[:name] = @name if @name
49
+ payload[:url] = @url if @url
50
+ payload[:issue_id] = @issue_id if @issue_id
51
+ payload[:metadata] = @metadata if @metadata.any?
52
+
53
+ request.body = JSON.generate(payload)
54
+
55
+ http.request(request)
56
+ end
57
+ end
58
+ end
@@ -9,6 +9,10 @@ module Bugwatch
9
9
  BreadcrumbCollector.clear
10
10
  UserContext.clear
11
11
 
12
+ config = Bugwatch.configuration
13
+ collecting = config.enable_db_tracking && (rand < config.db_sample_rate)
14
+ DbTracker.start_request(collecting: collecting)
15
+
12
16
  begin
13
17
  status, headers, body = @app.call(env)
14
18
  record_transaction(env, status, start)
@@ -24,6 +28,8 @@ module Bugwatch
24
28
 
25
29
  record_transaction(env, 500, start)
26
30
  raise
31
+ ensure
32
+ DbTracker.clear
27
33
  end
28
34
  end
29
35
 
@@ -35,6 +41,7 @@ module Bugwatch
35
41
  return unless config.track_request?(req.path)
36
42
 
37
43
  duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
44
+ db_result = DbTracker.finish_request
38
45
 
39
46
  payload = {
40
47
  name: "#{req.request_method} #{req.path}",
@@ -46,7 +53,18 @@ module Bugwatch
46
53
  occurred_at: Time.now.utc.iso8601
47
54
  }
48
55
 
56
+ payload[:db_duration_ms] = db_result[:total_db_ms].round(1) if db_result
57
+
49
58
  Bugwatch.transaction_buffer.push(payload)
59
+
60
+ if db_result && db_result[:queries].any?
61
+ Bugwatch.db_query_buffer.push({
62
+ transaction_name: payload[:name],
63
+ environment: config.release_stage,
64
+ occurred_at: payload[:occurred_at],
65
+ queries: db_result[:queries]
66
+ })
67
+ end
50
68
  rescue StandardError
51
69
  # Never let tracking break the app
52
70
  end
@@ -19,6 +19,18 @@ module Bugwatch
19
19
  end
20
20
  end
21
21
 
22
+ initializer "bugwatch.view_helpers" do
23
+ ActiveSupport.on_load(:action_view) do
24
+ include Bugwatch::FeedbackHelper
25
+ end
26
+ end
27
+
28
+ initializer "bugwatch.db_tracking" do
29
+ ActiveSupport.on_load(:active_record) do
30
+ Bugwatch::DbTracker.subscribe! if Bugwatch.configuration.enable_db_tracking
31
+ end
32
+ end
33
+
22
34
  initializer "bugwatch.error_subscriber" do
23
35
  if defined?(Rails.error) && Rails.error.respond_to?(:subscribe)
24
36
  Rails.error.subscribe(Bugwatch::ErrorSubscriber.new)
@@ -1,3 +1,3 @@
1
1
  module Bugwatch
2
- VERSION = "0.4.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/bugwatch.rb CHANGED
@@ -11,6 +11,11 @@ require_relative "bugwatch/report_builder"
11
11
  require_relative "bugwatch/notification"
12
12
  require_relative "bugwatch/transaction_sender"
13
13
  require_relative "bugwatch/transaction_buffer"
14
+ require_relative "bugwatch/db_tracker"
15
+ require_relative "bugwatch/db_query_sender"
16
+ require_relative "bugwatch/db_query_buffer"
17
+ require_relative "bugwatch/feedback_sender"
18
+ require_relative "bugwatch/feedback_helper"
14
19
  require_relative "bugwatch/middleware"
15
20
  require_relative "bugwatch/active_job_handler"
16
21
  require_relative "bugwatch/error_subscriber"
@@ -43,6 +48,15 @@ module Bugwatch
43
48
  Notification.new(payload).deliver
44
49
  end
45
50
 
51
+ def send_feedback(message, email: nil, name: nil, url: nil, issue_id: nil, metadata: {})
52
+ return if message.to_s.strip.empty?
53
+
54
+ FeedbackSender.new(
55
+ message: message, email: email, name: name,
56
+ url: url, issue_id: issue_id, metadata: metadata
57
+ ).deliver
58
+ end
59
+
46
60
  def set_user(id: nil, email: nil, name: nil, **custom)
47
61
  UserContext.set(id: id, email: email, name: name, **custom)
48
62
  end
@@ -109,7 +123,14 @@ module Bugwatch
109
123
  def transaction_buffer
110
124
  @transaction_buffer ||= TransactionBuffer.new(config: configuration)
111
125
  end
126
+
127
+ def db_query_buffer
128
+ @db_query_buffer ||= DbQueryBuffer.new(config: configuration)
129
+ end
112
130
  end
113
131
  end
114
132
 
115
- at_exit { Bugwatch.transaction_buffer.shutdown rescue nil }
133
+ at_exit do
134
+ Bugwatch.transaction_buffer.shutdown rescue nil
135
+ Bugwatch.db_query_buffer.shutdown rescue nil
136
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bugwatch-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BugWatch
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-04-13 00:00:00.000000000 Z
10
+ date: 2026-04-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: railties
@@ -52,8 +52,13 @@ files:
52
52
  - lib/bugwatch/backtrace_cleaner.rb
53
53
  - lib/bugwatch/breadcrumb_collector.rb
54
54
  - lib/bugwatch/configuration.rb
55
+ - lib/bugwatch/db_query_buffer.rb
56
+ - lib/bugwatch/db_query_sender.rb
57
+ - lib/bugwatch/db_tracker.rb
55
58
  - lib/bugwatch/error_builder.rb
56
59
  - lib/bugwatch/error_subscriber.rb
60
+ - lib/bugwatch/feedback_helper.rb
61
+ - lib/bugwatch/feedback_sender.rb
57
62
  - lib/bugwatch/middleware.rb
58
63
  - lib/bugwatch/notification.rb
59
64
  - lib/bugwatch/railtie.rb