dbwatcher 1.1.2 → 1.1.4

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/dbwatcher_manifest.js +1 -0
  3. data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +184 -97
  4. data/app/assets/javascripts/dbwatcher/components/timeline.js +211 -0
  5. data/app/assets/javascripts/dbwatcher/dbwatcher.js +5 -0
  6. data/app/assets/stylesheets/dbwatcher/application.css +298 -1
  7. data/app/assets/stylesheets/dbwatcher/application.scss +1 -0
  8. data/app/assets/stylesheets/dbwatcher/components/_timeline.scss +326 -0
  9. data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +27 -17
  10. data/app/controllers/dbwatcher/sessions_controller.rb +1 -1
  11. data/app/views/dbwatcher/sessions/_layout.html.erb +5 -2
  12. data/app/views/dbwatcher/sessions/_summary.html.erb +1 -1
  13. data/app/views/dbwatcher/sessions/{_changes.html.erb → _tables.html.erb} +84 -5
  14. data/app/views/dbwatcher/sessions/_timeline.html.erb +260 -0
  15. data/app/views/dbwatcher/sessions/show.html.erb +3 -1
  16. data/app/views/layouts/dbwatcher/application.html.erb +1 -0
  17. data/config/routes.rb +2 -1
  18. data/lib/dbwatcher/configuration.rb +11 -0
  19. data/lib/dbwatcher/logging.rb +23 -1
  20. data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +102 -1
  21. data/lib/dbwatcher/services/api/{changes_data_service.rb → tables_data_service.rb} +6 -6
  22. data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +62 -36
  23. data/lib/dbwatcher/services/diagram_generator.rb +35 -69
  24. data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +23 -9
  25. data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +16 -22
  26. data/lib/dbwatcher/services/diagram_strategies/diagram_strategy_helpers.rb +33 -0
  27. data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +20 -25
  28. data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +20 -25
  29. data/lib/dbwatcher/services/diagram_strategies/standard_diagram_strategy.rb +80 -0
  30. data/lib/dbwatcher/services/diagram_system.rb +14 -1
  31. data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +2 -0
  32. data/lib/dbwatcher/services/mermaid_syntax_builder.rb +10 -8
  33. data/lib/dbwatcher/services/timeline_data_service/enhancement_utilities.rb +100 -0
  34. data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +125 -0
  35. data/lib/dbwatcher/services/timeline_data_service/metadata_builder.rb +93 -0
  36. data/lib/dbwatcher/services/timeline_data_service.rb +130 -0
  37. data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +1 -1
  38. data/lib/dbwatcher/version.rb +1 -1
  39. data/lib/dbwatcher.rb +1 -1
  40. metadata +13 -4
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ module Services
5
+ class TimelineDataService
6
+ # Module for enhancing timeline entries and utility methods
7
+ module EnhancementUtilities
8
+ private
9
+
10
+ # Enhance timeline entries with additional metadata
11
+ #
12
+ # @return [void]
13
+ def enhance_with_metadata
14
+ return if @timeline_entries.empty?
15
+
16
+ session_start_time = @timeline_entries.first[:raw_timestamp]
17
+
18
+ @timeline_entries.each_with_index do |entry, index|
19
+ entry[:relative_time] = calculate_relative_time(entry[:raw_timestamp], session_start_time)
20
+ entry[:duration_from_previous] = calculate_duration_from_previous(entry, index)
21
+ entry[:operation_group] = determine_operation_group(entry, index)
22
+ end
23
+ end
24
+
25
+ # Calculate relative time from session start
26
+ #
27
+ # @param timestamp [Float] entry timestamp
28
+ # @param session_start [Float] session start timestamp
29
+ # @return [String] formatted relative time
30
+ def calculate_relative_time(timestamp, session_start)
31
+ seconds = timestamp - session_start
32
+ format_duration(seconds)
33
+ end
34
+
35
+ # Calculate duration from previous operation
36
+ #
37
+ # @param entry [Hash] current entry
38
+ # @param index [Integer] entry index
39
+ # @return [Integer] duration in milliseconds
40
+ def calculate_duration_from_previous(entry, index)
41
+ return 0 if index.zero?
42
+
43
+ previous_entry = @timeline_entries[index - 1]
44
+ ((entry[:raw_timestamp] - previous_entry[:raw_timestamp]) * 1000).round
45
+ end
46
+
47
+ # Determine operation group for related operations
48
+ #
49
+ # @param entry [Hash] current entry
50
+ # @param index [Integer] entry index
51
+ # @return [String] operation group identifier
52
+ def determine_operation_group(entry, index)
53
+ # Group operations on same table within 1 second
54
+ return "single_op" if index.zero?
55
+
56
+ previous_entry = @timeline_entries[index - 1]
57
+ time_diff = entry[:raw_timestamp] - previous_entry[:raw_timestamp]
58
+
59
+ if time_diff <= 1.0 && entry[:table_name] == previous_entry[:table_name]
60
+ "#{entry[:table_name]}_batch_#{index / 10}" # Group every 10 operations
61
+ else
62
+ "single_op"
63
+ end
64
+ end
65
+
66
+ # Format duration in human-readable format
67
+ #
68
+ # @param seconds [Float] duration in seconds
69
+ # @return [String] formatted duration
70
+ def format_duration(seconds)
71
+ if seconds < 60
72
+ format("%<minutes>02d:%<seconds>02d", minutes: 0, seconds: seconds.to_i)
73
+ elsif seconds < 3600
74
+ minutes = seconds / 60
75
+ format("%<minutes>02d:%<seconds>02d", minutes: minutes.to_i, seconds: (seconds % 60).to_i)
76
+ else
77
+ hours = seconds / 3600
78
+ minutes = (seconds % 3600) / 60
79
+ format("%<hours>02d:%<minutes>02d:%<seconds>02d",
80
+ hours: hours.to_i, minutes: minutes.to_i, seconds: (seconds % 60).to_i)
81
+ end
82
+ end
83
+
84
+ # Get model class for a table using the TableSummaryBuilder service
85
+ #
86
+ # @param table_name [String] database table name
87
+ # @return [String, nil] model class name or nil if not found
88
+ def get_model_class_for_table(table_name)
89
+ # Use cache to avoid repeated lookups
90
+ @model_class_cache ||= {}
91
+ return @model_class_cache[table_name] if @model_class_cache.key?(table_name)
92
+
93
+ # Delegate to TableSummaryBuilder for model class lookup
94
+ builder = Dbwatcher::Services::Analyzers::TableSummaryBuilder.new(@session)
95
+ @model_class_cache[table_name] = builder.send(:find_model_class, table_name)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Dbwatcher
6
+ module Services
7
+ class TimelineDataService
8
+ # Module for building timeline entries
9
+ module EntryBuilder
10
+ private
11
+
12
+ # Create a timeline entry from change data
13
+ #
14
+ # @param change [Hash] change data
15
+ # @param sequence [Integer] sequence number
16
+ # @return [Hash] timeline entry
17
+ def create_timeline_entry(change, sequence)
18
+ timestamp = parse_timestamp(change[:timestamp])
19
+
20
+ {
21
+ id: generate_entry_id(change, sequence),
22
+ timestamp: timestamp,
23
+ sequence: sequence,
24
+ table_name: change[:table_name],
25
+ operation: change[:operation],
26
+ record_id: extract_record_id(change),
27
+ changes: format_changes(change),
28
+ metadata: extract_metadata(change),
29
+ model_class: get_model_class_for_table(change[:table_name]),
30
+ raw_timestamp: timestamp.to_f
31
+ }
32
+ end
33
+
34
+ # Generate unique ID for timeline entry
35
+ #
36
+ # @param change [Hash] change data
37
+ # @param sequence [Integer] sequence number
38
+ # @return [String] unique entry ID
39
+ def generate_entry_id(change, sequence)
40
+ data = "#{change[:table_name]}_#{change[:operation]}_#{sequence}"
41
+ hash = Digest::SHA1.hexdigest(data)[0..7]
42
+ "#{@session.id}_entry_#{sequence}_#{hash}"
43
+ end
44
+
45
+ # Parse timestamp from various formats
46
+ #
47
+ # @param timestamp [String, Time, Integer] timestamp value
48
+ # @return [Time] parsed timestamp
49
+ def parse_timestamp(timestamp)
50
+ case timestamp
51
+ when Time
52
+ timestamp
53
+ when String
54
+ Time.parse(timestamp)
55
+ when Integer, Float
56
+ Time.at(timestamp)
57
+ else
58
+ Time.current
59
+ end
60
+ rescue ArgumentError
61
+ Time.current
62
+ end
63
+
64
+ # Extract record ID from change data
65
+ #
66
+ # @param change [Hash] change data
67
+ # @return [String, nil] record ID if available
68
+ def extract_record_id(change)
69
+ change[:record_id] || change[:id] || change.dig(:changes, :id)
70
+ end
71
+
72
+ # Format changes for timeline display
73
+ #
74
+ # @param change [Hash] change data
75
+ # @return [Hash] formatted changes
76
+ def format_changes(change)
77
+ raw_changes = change[:changes] || change[:data] || {}
78
+ return {} unless raw_changes.is_a?(Hash)
79
+
80
+ raw_changes.transform_values do |value|
81
+ case value
82
+ when Hash
83
+ value # Already formatted as { from: x, to: y }
84
+ else
85
+ { to: value } # Simple value change
86
+ end
87
+ end
88
+ end
89
+
90
+ # Extract metadata from change data
91
+ #
92
+ # @param change [Hash] change data
93
+ # @return [Hash] metadata hash
94
+ def extract_metadata(change)
95
+ {
96
+ duration_ms: change[:duration_ms] || change[:duration],
97
+ affected_rows: change[:affected_rows] || change[:rows_affected] || 1,
98
+ query_fingerprint: change[:query_fingerprint] || change[:sql_fingerprint],
99
+ connection_id: change[:connection_id] || change[:connection],
100
+ query_type: determine_query_type(change[:operation])
101
+ }.compact
102
+ end
103
+
104
+ # Determine query type from operation
105
+ #
106
+ # @param operation [String] database operation
107
+ # @return [String] query type
108
+ def determine_query_type(operation)
109
+ case operation&.upcase
110
+ when "INSERT", "CREATE"
111
+ "write"
112
+ when "UPDATE", "MODIFY"
113
+ "update"
114
+ when "DELETE", "DROP"
115
+ "delete"
116
+ when "SELECT", "SHOW"
117
+ "read"
118
+ else
119
+ "unknown"
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ module Services
5
+ class TimelineDataService
6
+ # Module for building timeline metadata
7
+ module MetadataBuilder
8
+ private
9
+
10
+ # Build timeline metadata
11
+ #
12
+ # @return [Hash] timeline metadata
13
+ def build_timeline_metadata
14
+ return {} if @timeline_entries.empty?
15
+
16
+ {
17
+ total_operations: @timeline_entries.length,
18
+ time_range: calculate_time_range,
19
+ session_duration: calculate_session_duration,
20
+ tables_affected: extract_affected_tables,
21
+ operation_counts: count_operations_by_type,
22
+ peak_activity_periods: find_peak_activity_periods
23
+ }
24
+ end
25
+
26
+ # Calculate time range for the session
27
+ #
28
+ # @return [Hash] time range with start and end
29
+ def calculate_time_range
30
+ return {} if @timeline_entries.empty?
31
+
32
+ start_time = Time.at(@timeline_entries.first[:raw_timestamp])
33
+ end_time = Time.at(@timeline_entries.last[:raw_timestamp])
34
+
35
+ {
36
+ start: start_time.iso8601,
37
+ end: end_time.iso8601
38
+ }
39
+ end
40
+
41
+ # Calculate total session duration
42
+ #
43
+ # @return [String] formatted session duration
44
+ def calculate_session_duration
45
+ return "00:00" if @timeline_entries.length < 2
46
+
47
+ duration = @timeline_entries.last[:raw_timestamp] - @timeline_entries.first[:raw_timestamp]
48
+ format_duration(duration)
49
+ end
50
+
51
+ # Extract list of affected tables
52
+ #
53
+ # @return [Array<String>] unique table names
54
+ def extract_affected_tables
55
+ @timeline_entries.map { |entry| entry[:table_name] }.uniq.sort
56
+ end
57
+
58
+ # Count operations by type
59
+ #
60
+ # @return [Hash] operation counts
61
+ def count_operations_by_type
62
+ @timeline_entries.group_by { |entry| entry[:operation] }
63
+ .transform_values(&:count)
64
+ end
65
+
66
+ # Find peak activity periods
67
+ #
68
+ # @return [Array<Hash>] peak activity periods
69
+ def find_peak_activity_periods
70
+ return [] if @timeline_entries.length < 10
71
+
72
+ # Group operations by 1-minute windows
73
+ windows = @timeline_entries.group_by do |entry|
74
+ timestamp = Time.at(entry[:raw_timestamp])
75
+ Time.new(timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.min, 0)
76
+ end
77
+
78
+ # Find windows with more than average activity
79
+ average_ops = @timeline_entries.length / windows.length.to_f
80
+
81
+ windows.select { |_, ops| ops.length > average_ops * 1.5 }
82
+ .map do |window_start, ops|
83
+ {
84
+ start: window_start.iso8601,
85
+ end: (window_start + 1.minute).iso8601,
86
+ operations_count: ops.length
87
+ }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "timeline_data_service/metadata_builder"
4
+ require_relative "timeline_data_service/entry_builder"
5
+ require_relative "timeline_data_service/enhancement_utilities"
6
+
7
+ module Dbwatcher
8
+ module Services
9
+ # Timeline Data Service for processing session data into chronological timeline format
10
+ #
11
+ # This service transforms session changes into a chronologically ordered timeline
12
+ # with enhanced metadata for visualization and filtering.
13
+ #
14
+ # @example
15
+ # service = TimelineDataService.new(session)
16
+ # result = service.call
17
+ # timeline = result[:timeline]
18
+ # metadata = result[:metadata]
19
+ class TimelineDataService
20
+ include MetadataBuilder
21
+ include EntryBuilder
22
+ include EnhancementUtilities
23
+ # Initialize the timeline data service
24
+ #
25
+ # @param session [Session] session object containing changes data
26
+ def initialize(session)
27
+ @session = session
28
+ @timeline_entries = []
29
+ @start_time = Time.current
30
+ end
31
+
32
+ # Process session data into timeline format
33
+ #
34
+ # @return [Hash] processed timeline data with metadata
35
+ def call
36
+ Rails.logger.info("Processing timeline data for session #{@session.id}")
37
+
38
+ validate_session_data
39
+ build_timeline_entries
40
+ sort_chronologically
41
+ enhance_with_metadata
42
+ result = build_result
43
+
44
+ Rails.logger.info(
45
+ "Timeline processing completed for session #{@session.id} (#{@timeline_entries.length} entries)"
46
+ )
47
+
48
+ result
49
+ rescue StandardError => e
50
+ Rails.logger.error("Timeline processing failed for session #{@session.id}: #{e.message}")
51
+ build_error_result(e)
52
+ end
53
+
54
+ private
55
+
56
+ # Validate session data before processing
57
+ #
58
+ # @raise [ArgumentError] if session data is invalid
59
+ def validate_session_data
60
+ raise ArgumentError, "Session is required" unless @session
61
+ raise ArgumentError, "Session ID is required" unless @session.id
62
+ raise ArgumentError, "Session changes are required" unless @session.changes
63
+ end
64
+
65
+ # Build timeline entries from session changes
66
+ #
67
+ # @return [void]
68
+ def build_timeline_entries
69
+ @session.changes.each_with_index do |change, index|
70
+ next unless valid_change?(change)
71
+
72
+ @timeline_entries << create_timeline_entry(change, index)
73
+ end
74
+ end
75
+
76
+ # Check if a change is valid for timeline processing
77
+ #
78
+ # @param change [Hash] change data
79
+ # @return [Boolean] true if change is valid
80
+ def valid_change?(change)
81
+ change.is_a?(Hash) &&
82
+ change[:table_name] &&
83
+ change[:operation] &&
84
+ change[:timestamp]
85
+ end
86
+
87
+ # Sort timeline entries chronologically
88
+ #
89
+ # @return [void]
90
+ def sort_chronologically
91
+ @timeline_entries.sort_by! { |entry| entry[:raw_timestamp] }
92
+ end
93
+
94
+ # Build final result hash
95
+ #
96
+ # @return [Hash] complete timeline result
97
+ def build_result
98
+ {
99
+ timeline: @timeline_entries,
100
+ metadata: build_timeline_metadata,
101
+ summary: build_timeline_summary,
102
+ errors: []
103
+ }
104
+ end
105
+
106
+ # Build timeline summary
107
+ #
108
+ # @return [Hash] timeline summary
109
+ def build_timeline_summary
110
+ {
111
+ total_entries: @timeline_entries.length,
112
+ processing_time: (Time.current - @start_time).round(3)
113
+ }
114
+ end
115
+
116
+ # Build error result
117
+ #
118
+ # @param error [StandardError] error that occurred
119
+ # @return [Hash] error result
120
+ def build_error_result(error)
121
+ {
122
+ timeline: [],
123
+ metadata: {},
124
+ summary: { error: error.message },
125
+ errors: [{ type: "processing_error", message: error.message }]
126
+ }
127
+ end
128
+ end
129
+ end
130
+ end
@@ -24,7 +24,7 @@ module Dbwatcher
24
24
  # @return [Hash] tables summary hash
25
25
  def build_tables_summary(session)
26
26
  # Delegate to new service while maintaining interface compatibility
27
- Dbwatcher::Services::Analyzers::TableSummaryBuilder.call(session)
27
+ Dbwatcher::Services::Analyzers::TableSummaryBuilder.new(session).call
28
28
  end
29
29
 
30
30
  # Process all changes in a session (legacy method for backward compatibility)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dbwatcher
4
- VERSION = "1.1.2"
4
+ VERSION = "1.1.4"
5
5
  end
data/lib/dbwatcher.rb CHANGED
@@ -74,7 +74,7 @@ require_relative "dbwatcher/services/diagram_system"
74
74
 
75
75
  # API services
76
76
  require_relative "dbwatcher/services/api/base_api_service"
77
- require_relative "dbwatcher/services/api/changes_data_service"
77
+ require_relative "dbwatcher/services/api/tables_data_service"
78
78
  require_relative "dbwatcher/services/api/summary_data_service"
79
79
  require_relative "dbwatcher/services/api/diagram_data_service"
80
80
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbwatcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Huy Nguyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-09 00:00:00.000000000 Z
11
+ date: 2025-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -172,6 +172,7 @@ files:
172
172
  - app/assets/javascripts/dbwatcher/components/dashboard.js
173
173
  - app/assets/javascripts/dbwatcher/components/diagrams.js
174
174
  - app/assets/javascripts/dbwatcher/components/summary.js
175
+ - app/assets/javascripts/dbwatcher/components/timeline.js
175
176
  - app/assets/javascripts/dbwatcher/core/alpine_store.js
176
177
  - app/assets/javascripts/dbwatcher/core/api_client.js
177
178
  - app/assets/javascripts/dbwatcher/core/component_loader.js
@@ -190,6 +191,7 @@ files:
190
191
  - app/assets/stylesheets/dbwatcher/components/_forms.scss
191
192
  - app/assets/stylesheets/dbwatcher/components/_navigation.scss
192
193
  - app/assets/stylesheets/dbwatcher/components/_tabulator.scss
194
+ - app/assets/stylesheets/dbwatcher/components/_timeline.scss
193
195
  - app/assets/stylesheets/dbwatcher/core/_base.scss
194
196
  - app/assets/stylesheets/dbwatcher/core/_variables.scss
195
197
  - app/assets/stylesheets/dbwatcher/vendor/_tabulator_overrides.scss
@@ -212,11 +214,12 @@ files:
212
214
  - app/views/dbwatcher/dashboard/_system_info_content.html.erb
213
215
  - app/views/dbwatcher/dashboard/index.html.erb
214
216
  - app/views/dbwatcher/queries/index.html.erb
215
- - app/views/dbwatcher/sessions/_changes.html.erb
216
217
  - app/views/dbwatcher/sessions/_diagrams.html.erb
217
218
  - app/views/dbwatcher/sessions/_layout.html.erb
218
219
  - app/views/dbwatcher/sessions/_session_header.html.erb
219
220
  - app/views/dbwatcher/sessions/_summary.html.erb
221
+ - app/views/dbwatcher/sessions/_tables.html.erb
222
+ - app/views/dbwatcher/sessions/_timeline.html.erb
220
223
  - app/views/dbwatcher/sessions/index.html.erb
221
224
  - app/views/dbwatcher/sessions/show.html.erb
222
225
  - app/views/dbwatcher/shared/_badge.html.erb
@@ -244,9 +247,9 @@ files:
244
247
  - lib/dbwatcher/services/analyzers/session_data_processor.rb
245
248
  - lib/dbwatcher/services/analyzers/table_summary_builder.rb
246
249
  - lib/dbwatcher/services/api/base_api_service.rb
247
- - lib/dbwatcher/services/api/changes_data_service.rb
248
250
  - lib/dbwatcher/services/api/diagram_data_service.rb
249
251
  - lib/dbwatcher/services/api/summary_data_service.rb
252
+ - lib/dbwatcher/services/api/tables_data_service.rb
250
253
  - lib/dbwatcher/services/base_service.rb
251
254
  - lib/dbwatcher/services/dashboard_data_aggregator.rb
252
255
  - lib/dbwatcher/services/diagram_analyzers/base_analyzer.rb
@@ -263,8 +266,10 @@ files:
263
266
  - lib/dbwatcher/services/diagram_generator.rb
264
267
  - lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb
265
268
  - lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb
269
+ - lib/dbwatcher/services/diagram_strategies/diagram_strategy_helpers.rb
266
270
  - lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb
267
271
  - lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb
272
+ - lib/dbwatcher/services/diagram_strategies/standard_diagram_strategy.rb
268
273
  - lib/dbwatcher/services/diagram_system.rb
269
274
  - lib/dbwatcher/services/diagram_type_registry.rb
270
275
  - lib/dbwatcher/services/mermaid_syntax/base_builder.rb
@@ -281,6 +286,10 @@ files:
281
286
  - lib/dbwatcher/services/system_info/runtime_info_collector.rb
282
287
  - lib/dbwatcher/services/system_info/system_info_collector.rb
283
288
  - lib/dbwatcher/services/table_statistics_collector.rb
289
+ - lib/dbwatcher/services/timeline_data_service.rb
290
+ - lib/dbwatcher/services/timeline_data_service/enhancement_utilities.rb
291
+ - lib/dbwatcher/services/timeline_data_service/entry_builder.rb
292
+ - lib/dbwatcher/services/timeline_data_service/metadata_builder.rb
284
293
  - lib/dbwatcher/sql_logger.rb
285
294
  - lib/dbwatcher/storage.rb
286
295
  - lib/dbwatcher/storage/api/base_api.rb