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.
- checksums.yaml +4 -4
- data/app/assets/config/dbwatcher_manifest.js +1 -0
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +184 -97
- data/app/assets/javascripts/dbwatcher/components/timeline.js +211 -0
- data/app/assets/javascripts/dbwatcher/dbwatcher.js +5 -0
- data/app/assets/stylesheets/dbwatcher/application.css +298 -1
- data/app/assets/stylesheets/dbwatcher/application.scss +1 -0
- data/app/assets/stylesheets/dbwatcher/components/_timeline.scss +326 -0
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +27 -17
- data/app/controllers/dbwatcher/sessions_controller.rb +1 -1
- data/app/views/dbwatcher/sessions/_layout.html.erb +5 -2
- data/app/views/dbwatcher/sessions/_summary.html.erb +1 -1
- data/app/views/dbwatcher/sessions/{_changes.html.erb → _tables.html.erb} +84 -5
- data/app/views/dbwatcher/sessions/_timeline.html.erb +260 -0
- data/app/views/dbwatcher/sessions/show.html.erb +3 -1
- data/app/views/layouts/dbwatcher/application.html.erb +1 -0
- data/config/routes.rb +2 -1
- data/lib/dbwatcher/configuration.rb +11 -0
- data/lib/dbwatcher/logging.rb +23 -1
- data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +102 -1
- data/lib/dbwatcher/services/api/{changes_data_service.rb → tables_data_service.rb} +6 -6
- data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +62 -36
- data/lib/dbwatcher/services/diagram_generator.rb +35 -69
- data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +23 -9
- data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +16 -22
- data/lib/dbwatcher/services/diagram_strategies/diagram_strategy_helpers.rb +33 -0
- data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +20 -25
- data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +20 -25
- data/lib/dbwatcher/services/diagram_strategies/standard_diagram_strategy.rb +80 -0
- data/lib/dbwatcher/services/diagram_system.rb +14 -1
- data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +2 -0
- data/lib/dbwatcher/services/mermaid_syntax_builder.rb +10 -8
- data/lib/dbwatcher/services/timeline_data_service/enhancement_utilities.rb +100 -0
- data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +125 -0
- data/lib/dbwatcher/services/timeline_data_service/metadata_builder.rb +93 -0
- data/lib/dbwatcher/services/timeline_data_service.rb +130 -0
- data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +1 -1
- data/lib/dbwatcher/version.rb +1 -1
- data/lib/dbwatcher.rb +1 -1
- 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.
|
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)
|
data/lib/dbwatcher/version.rb
CHANGED
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/
|
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.
|
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-
|
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
|