dbwatcher 0.1.5 → 1.0.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/controllers/dbwatcher/base_controller.rb +95 -0
  4. data/app/controllers/dbwatcher/dashboard_controller.rb +12 -0
  5. data/app/controllers/dbwatcher/queries_controller.rb +24 -0
  6. data/app/controllers/dbwatcher/sessions_controller.rb +15 -20
  7. data/app/controllers/dbwatcher/tables_controller.rb +38 -0
  8. data/app/helpers/dbwatcher/application_helper.rb +103 -0
  9. data/app/helpers/dbwatcher/formatting_helper.rb +108 -0
  10. data/app/helpers/dbwatcher/session_helper.rb +27 -0
  11. data/app/views/dbwatcher/dashboard/index.html.erb +177 -0
  12. data/app/views/dbwatcher/queries/index.html.erb +240 -0
  13. data/app/views/dbwatcher/sessions/index.html.erb +120 -27
  14. data/app/views/dbwatcher/sessions/show.html.erb +326 -129
  15. data/app/views/dbwatcher/shared/_badge.html.erb +4 -0
  16. data/app/views/dbwatcher/shared/_data_table.html.erb +20 -0
  17. data/app/views/dbwatcher/shared/_header.html.erb +7 -0
  18. data/app/views/dbwatcher/shared/_page_layout.html.erb +20 -0
  19. data/app/views/dbwatcher/shared/_section_panel.html.erb +9 -0
  20. data/app/views/dbwatcher/shared/_stats_card.html.erb +11 -0
  21. data/app/views/dbwatcher/shared/_tab_bar.html.erb +6 -0
  22. data/app/views/dbwatcher/tables/changes.html.erb +225 -0
  23. data/app/views/dbwatcher/tables/index.html.erb +123 -0
  24. data/app/views/dbwatcher/tables/show.html.erb +86 -0
  25. data/app/views/layouts/dbwatcher/application.html.erb +375 -26
  26. data/config/routes.rb +17 -3
  27. data/lib/dbwatcher/configuration.rb +9 -1
  28. data/lib/dbwatcher/engine.rb +12 -7
  29. data/lib/dbwatcher/logging.rb +72 -0
  30. data/lib/dbwatcher/services/dashboard_data_aggregator.rb +121 -0
  31. data/lib/dbwatcher/services/query_filter_processor.rb +114 -0
  32. data/lib/dbwatcher/services/table_statistics_collector.rb +119 -0
  33. data/lib/dbwatcher/sql_logger.rb +107 -0
  34. data/lib/dbwatcher/storage/api/base_api.rb +134 -0
  35. data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +172 -0
  36. data/lib/dbwatcher/storage/api/query_api.rb +95 -0
  37. data/lib/dbwatcher/storage/api/session_api.rb +134 -0
  38. data/lib/dbwatcher/storage/api/table_api.rb +86 -0
  39. data/lib/dbwatcher/storage/base_storage.rb +113 -0
  40. data/lib/dbwatcher/storage/change_processor.rb +65 -0
  41. data/lib/dbwatcher/storage/concerns/data_normalizer.rb +134 -0
  42. data/lib/dbwatcher/storage/concerns/error_handler.rb +75 -0
  43. data/lib/dbwatcher/storage/concerns/timestampable.rb +74 -0
  44. data/lib/dbwatcher/storage/concerns/validatable.rb +117 -0
  45. data/lib/dbwatcher/storage/date_helper.rb +21 -0
  46. data/lib/dbwatcher/storage/errors.rb +86 -0
  47. data/lib/dbwatcher/storage/file_manager.rb +122 -0
  48. data/lib/dbwatcher/storage/null_session.rb +39 -0
  49. data/lib/dbwatcher/storage/query_storage.rb +338 -0
  50. data/lib/dbwatcher/storage/query_validator.rb +24 -0
  51. data/lib/dbwatcher/storage/session.rb +58 -0
  52. data/lib/dbwatcher/storage/session_operations.rb +37 -0
  53. data/lib/dbwatcher/storage/session_query.rb +71 -0
  54. data/lib/dbwatcher/storage/session_storage.rb +322 -0
  55. data/lib/dbwatcher/storage/table_storage.rb +237 -0
  56. data/lib/dbwatcher/storage.rb +112 -85
  57. data/lib/dbwatcher/tracker.rb +4 -55
  58. data/lib/dbwatcher/version.rb +1 -1
  59. data/lib/dbwatcher.rb +12 -2
  60. metadata +47 -1
@@ -1,109 +1,136 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "storage/base_storage"
4
+ require_relative "storage/concerns/error_handler"
5
+ require_relative "storage/concerns/timestampable"
6
+ require_relative "storage/concerns/validatable"
7
+ require_relative "storage/concerns/data_normalizer"
8
+ require_relative "storage/session_storage"
9
+ require_relative "storage/query_storage"
10
+ require_relative "storage/table_storage"
11
+ require_relative "storage/session_query"
12
+ require_relative "storage/api/base_api"
13
+ require_relative "storage/api/query_api"
14
+ require_relative "storage/api/table_api"
15
+ require_relative "storage/api/session_api"
16
+ require_relative "storage/session"
17
+ require_relative "storage/errors"
18
+
3
19
  module Dbwatcher
4
- class Storage
20
+ # Storage module provides the main interface for database monitoring data persistence
21
+ #
22
+ # This module acts as a facade for different storage backends and provides
23
+ # clean API entry points for sessions, queries, and tables. It manages
24
+ # storage instances and provides cleanup operations.
25
+ #
26
+ # @example Basic usage
27
+ # Dbwatcher::Storage.sessions.create("My Session")
28
+ # Dbwatcher::Storage.sessions.recent.with_changes
29
+ # Dbwatcher::Storage.queries.save(query_data)
30
+ # Dbwatcher::Storage.tables.changes_for("users")
31
+ #
32
+ # @example Cleanup operations
33
+ # Dbwatcher::Storage.cleanup_old_sessions
34
+ # Dbwatcher::Storage.clear_all
35
+ # @see SessionAPI
36
+ # @see QueryAPI
37
+ # @see TableAPI
38
+ module Storage
5
39
  class << self
6
- def save_session(session)
7
- return unless session&.id
8
-
9
- ensure_storage_directory
10
-
11
- # Save individual session file
12
- session_file = File.join(sessions_path, "#{session.id}.json")
13
- File.write(session_file, JSON.pretty_generate(session.to_h))
14
-
15
- # Update index
16
- update_index(session)
17
-
18
- # Clean old sessions if needed
19
- cleanup_old_sessions
20
- rescue StandardError => e
21
- warn "Failed to save session #{session&.id}: #{e.message}"
40
+ # Provides access to session operations
41
+ #
42
+ # @return [SessionAPI] session API interface
43
+ # @example
44
+ # Dbwatcher::Storage.sessions.create("My Session")
45
+ # Dbwatcher::Storage.sessions.all
46
+ # Dbwatcher::Storage.sessions.recent.with_changes
47
+ def sessions
48
+ @sessions ||= Api::SessionAPI.new(session_storage)
22
49
  end
23
50
 
24
- def load_session(id)
25
- return nil if id.nil? || id.empty?
26
-
27
- session_file = File.join(sessions_path, "#{id}.json")
28
- return nil unless File.exist?(session_file)
29
-
30
- data = JSON.parse(File.read(session_file), symbolize_names: true)
31
- Tracker::Session.new(data)
32
- rescue JSON::ParserError => e
33
- warn "Failed to parse session file #{id}: #{e.message}"
34
- nil
35
- rescue StandardError => e
36
- warn "Failed to load session #{id}: #{e.message}"
37
- nil
51
+ # Provides access to query operations
52
+ #
53
+ # @return [QueryAPI] query API interface
54
+ # @example
55
+ # Dbwatcher::Storage.queries.save(query_data)
56
+ # Dbwatcher::Storage.queries.for_date(Date.today)
57
+ def queries
58
+ @queries ||= Api::QueryAPI.new(query_storage)
38
59
  end
39
60
 
40
- def all_sessions
41
- index_file = File.join(storage_path, "index.json")
42
- return [] unless File.exist?(index_file)
43
-
44
- JSON.parse(File.read(index_file), symbolize_names: true)
45
- rescue JSON::ParserError => e
46
- warn "Failed to parse sessions index: #{e.message}"
47
- []
48
- rescue StandardError => e
49
- warn "Failed to load sessions: #{e.message}"
50
- []
61
+ # Provides access to table operations
62
+ #
63
+ # @return [TableAPI] table API interface
64
+ # @example
65
+ # Dbwatcher::Storage.tables.changes_for("users")
66
+ # Dbwatcher::Storage.tables.recent_changes
67
+ def tables
68
+ @tables ||= Api::TableAPI.new(table_storage)
51
69
  end
52
70
 
53
- def reset!
54
- FileUtils.rm_rf(storage_path)
55
- ensure_storage_directory
71
+ # Resets all cached storage instances (primarily for testing)
72
+ #
73
+ # This method clears all memoized storage instances, forcing them
74
+ # to be recreated on next access. Useful for testing scenarios.
75
+ #
76
+ # @return [void]
77
+ # @example
78
+ # Dbwatcher::Storage.reset_storage_instances!
79
+ def reset_storage_instances!
80
+ @session_storage = nil
81
+ @query_storage = nil
82
+ @table_storage = nil
83
+ @sessions = nil
84
+ @queries = nil
85
+ @tables = nil
56
86
  end
57
87
 
58
- private
88
+ # Cleanup operations
59
89
 
60
- def storage_path
61
- Dbwatcher.configuration.storage_path
62
- end
63
-
64
- def sessions_path
65
- File.join(storage_path, "sessions")
90
+ # Removes old session files based on configuration
91
+ #
92
+ # Automatically removes session files that exceed the configured
93
+ # retention period. This helps manage storage space usage.
94
+ #
95
+ # @return [void]
96
+ # @see Configuration#auto_clean_after_days
97
+ def cleanup_old_sessions
98
+ session_storage.cleanup_old_sessions
66
99
  end
67
100
 
68
- def ensure_storage_directory
69
- FileUtils.mkdir_p(sessions_path)
101
+ # Direct access to storage instances (for internal use)
70
102
 
71
- # Create index if it doesn't exist
72
- index_file = File.join(storage_path, "index.json")
73
- File.write(index_file, "[]") unless File.exist?(index_file)
103
+ # Returns the session storage instance
104
+ #
105
+ # @return [SessionStorage] the session storage instance
106
+ # @api private
107
+ def session_storage
108
+ @session_storage ||= SessionStorage.new
74
109
  end
75
110
 
76
- def update_index(session)
77
- index_file = File.join(storage_path, "index.json")
78
- index = JSON.parse(File.read(index_file), symbolize_names: true)
79
-
80
- # Add new session summary to index
81
- index.unshift({
82
- id: session.id,
83
- name: session.name,
84
- started_at: session.started_at,
85
- ended_at: session.ended_at,
86
- change_count: session.changes.count
87
- })
88
-
89
- # Keep only max_sessions
90
- index = index.first(Dbwatcher.configuration.max_sessions)
91
-
92
- File.write(index_file, JSON.pretty_generate(index))
93
- rescue StandardError => e
94
- warn "Failed to update sessions index: #{e.message}"
111
+ # Returns the query storage instance
112
+ #
113
+ # @return [QueryStorage] the query storage instance
114
+ # @api private
115
+ def query_storage
116
+ @query_storage ||= QueryStorage.new
95
117
  end
96
118
 
97
- def cleanup_old_sessions
98
- return unless Dbwatcher.configuration.auto_clean_after_days
99
-
100
- cutoff_date = Time.now - (Dbwatcher.configuration.auto_clean_after_days * 24 * 60 * 60)
119
+ # Returns the table storage instance
120
+ #
121
+ # @return [TableStorage] the table storage instance
122
+ # @api private
123
+ def table_storage
124
+ @table_storage ||= TableStorage.new(session_storage)
125
+ end
101
126
 
102
- Dir.glob(File.join(sessions_path, "*.json")).each do |file|
103
- File.delete(file) if File.mtime(file) < cutoff_date
104
- end
105
- rescue StandardError => e
106
- warn "Failed to cleanup old sessions: #{e.message}"
127
+ # Clears all storage data
128
+ #
129
+ # @return [Integer] total number of files removed
130
+ def clear_all
131
+ session_count = session_storage.clear_all
132
+ query_count = query_storage.clear_all
133
+ session_count + query_count
107
134
  end
108
135
  end
109
136
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "storage/session"
4
+
3
5
  module Dbwatcher
4
6
  class Tracker
5
7
  class << self
@@ -30,7 +32,7 @@ module Dbwatcher
30
32
  private
31
33
 
32
34
  def create_session(name, metadata)
33
- Session.new(
35
+ Storage::Session.new(
34
36
  id: SecureRandom.uuid,
35
37
  name: name || "Session #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}",
36
38
  metadata: metadata || {},
@@ -50,63 +52,10 @@ module Dbwatcher
50
52
 
51
53
  def finalize_session(session)
52
54
  session.ended_at = Time.now.strftime("%Y-%m-%dT%H:%M:%S%z")
53
- Storage.save_session(session)
55
+ Storage.sessions.create(session)
54
56
  rescue StandardError
55
57
  nil
56
58
  end
57
59
  end
58
-
59
- class Session
60
- attr_accessor :id, :name, :metadata, :started_at, :ended_at, :changes
61
-
62
- def initialize(attrs = {})
63
- # Set default values
64
- @changes = []
65
- @metadata = {}
66
-
67
- # Set provided attributes
68
- attrs.each do |key, value|
69
- setter_method = "#{key}="
70
- send(setter_method, value) if respond_to?(setter_method)
71
- end
72
- end
73
-
74
- def to_h
75
- {
76
- id: id,
77
- name: name,
78
- metadata: metadata,
79
- started_at: started_at,
80
- ended_at: ended_at,
81
- changes: changes
82
- }
83
- end
84
-
85
- def summary
86
- return {} unless changes.is_a?(Array)
87
-
88
- valid_changes = filter_valid_changes
89
- group_changes_by_operation(valid_changes)
90
- rescue StandardError => e
91
- warn "Failed to calculate session summary: #{e.message}"
92
- {}
93
- end
94
-
95
- private
96
-
97
- def filter_valid_changes
98
- changes.select { |change| valid_change?(change) }
99
- end
100
-
101
- def valid_change?(change)
102
- change.is_a?(Hash) && change[:table_name] && change[:operation]
103
- end
104
-
105
- def group_changes_by_operation(valid_changes)
106
- valid_changes
107
- .group_by { |change| "#{change[:table_name]},#{change[:operation]}" }
108
- .transform_values(&:count)
109
- end
110
- end
111
60
  end
112
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dbwatcher
4
- VERSION = "0.1.5"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/dbwatcher.rb CHANGED
@@ -3,12 +3,19 @@
3
3
  require "json"
4
4
  require "fileutils"
5
5
  require "securerandom"
6
+ require "singleton"
7
+ require "logger"
6
8
  require_relative "dbwatcher/version"
7
9
  require_relative "dbwatcher/configuration"
10
+ require_relative "dbwatcher/logging"
8
11
  require_relative "dbwatcher/tracker"
9
12
  require_relative "dbwatcher/storage"
13
+ require_relative "dbwatcher/sql_logger"
10
14
  require_relative "dbwatcher/model_extension"
11
15
  require_relative "dbwatcher/middleware"
16
+ require_relative "dbwatcher/services/table_statistics_collector"
17
+ require_relative "dbwatcher/services/dashboard_data_aggregator"
18
+ require_relative "dbwatcher/services/query_filter_processor"
12
19
  require_relative "dbwatcher/engine" if defined?(Rails)
13
20
 
14
21
  module Dbwatcher
@@ -33,8 +40,11 @@ module Dbwatcher
33
40
  Tracker.current_session
34
41
  end
35
42
 
36
- def reset!
37
- Storage.reset!
43
+ # Clears all stored data (sessions and queries)
44
+ #
45
+ # @return [Integer] total number of files removed
46
+ def clear_all
47
+ Storage.clear_all
38
48
  end
39
49
  end
40
50
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbwatcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Huy Nguyen
@@ -149,9 +149,28 @@ extra_rdoc_files: []
149
149
  files:
150
150
  - README.md
151
151
  - Rakefile
152
+ - app/controllers/dbwatcher/base_controller.rb
153
+ - app/controllers/dbwatcher/dashboard_controller.rb
154
+ - app/controllers/dbwatcher/queries_controller.rb
152
155
  - app/controllers/dbwatcher/sessions_controller.rb
156
+ - app/controllers/dbwatcher/tables_controller.rb
157
+ - app/helpers/dbwatcher/application_helper.rb
158
+ - app/helpers/dbwatcher/formatting_helper.rb
159
+ - app/helpers/dbwatcher/session_helper.rb
160
+ - app/views/dbwatcher/dashboard/index.html.erb
161
+ - app/views/dbwatcher/queries/index.html.erb
153
162
  - app/views/dbwatcher/sessions/index.html.erb
154
163
  - app/views/dbwatcher/sessions/show.html.erb
164
+ - app/views/dbwatcher/shared/_badge.html.erb
165
+ - app/views/dbwatcher/shared/_data_table.html.erb
166
+ - app/views/dbwatcher/shared/_header.html.erb
167
+ - app/views/dbwatcher/shared/_page_layout.html.erb
168
+ - app/views/dbwatcher/shared/_section_panel.html.erb
169
+ - app/views/dbwatcher/shared/_stats_card.html.erb
170
+ - app/views/dbwatcher/shared/_tab_bar.html.erb
171
+ - app/views/dbwatcher/tables/changes.html.erb
172
+ - app/views/dbwatcher/tables/index.html.erb
173
+ - app/views/dbwatcher/tables/show.html.erb
155
174
  - app/views/layouts/dbwatcher/application.html.erb
156
175
  - bin/console
157
176
  - bin/release
@@ -160,9 +179,36 @@ files:
160
179
  - lib/dbwatcher.rb
161
180
  - lib/dbwatcher/configuration.rb
162
181
  - lib/dbwatcher/engine.rb
182
+ - lib/dbwatcher/logging.rb
163
183
  - lib/dbwatcher/middleware.rb
164
184
  - lib/dbwatcher/model_extension.rb
185
+ - lib/dbwatcher/services/dashboard_data_aggregator.rb
186
+ - lib/dbwatcher/services/query_filter_processor.rb
187
+ - lib/dbwatcher/services/table_statistics_collector.rb
188
+ - lib/dbwatcher/sql_logger.rb
165
189
  - lib/dbwatcher/storage.rb
190
+ - lib/dbwatcher/storage/api/base_api.rb
191
+ - lib/dbwatcher/storage/api/concerns/table_analyzer.rb
192
+ - lib/dbwatcher/storage/api/query_api.rb
193
+ - lib/dbwatcher/storage/api/session_api.rb
194
+ - lib/dbwatcher/storage/api/table_api.rb
195
+ - lib/dbwatcher/storage/base_storage.rb
196
+ - lib/dbwatcher/storage/change_processor.rb
197
+ - lib/dbwatcher/storage/concerns/data_normalizer.rb
198
+ - lib/dbwatcher/storage/concerns/error_handler.rb
199
+ - lib/dbwatcher/storage/concerns/timestampable.rb
200
+ - lib/dbwatcher/storage/concerns/validatable.rb
201
+ - lib/dbwatcher/storage/date_helper.rb
202
+ - lib/dbwatcher/storage/errors.rb
203
+ - lib/dbwatcher/storage/file_manager.rb
204
+ - lib/dbwatcher/storage/null_session.rb
205
+ - lib/dbwatcher/storage/query_storage.rb
206
+ - lib/dbwatcher/storage/query_validator.rb
207
+ - lib/dbwatcher/storage/session.rb
208
+ - lib/dbwatcher/storage/session_operations.rb
209
+ - lib/dbwatcher/storage/session_query.rb
210
+ - lib/dbwatcher/storage/session_storage.rb
211
+ - lib/dbwatcher/storage/table_storage.rb
166
212
  - lib/dbwatcher/tracker.rb
167
213
  - lib/dbwatcher/version.rb
168
214
  homepage: https://github.com/patrick204nqh/dbwatcher