dbwatcher 1.1.3 → 1.1.5
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/README.md +79 -26
- data/app/assets/images/dbwatcher/apple-touch-icon.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher-social-preview.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher-tranparent_512x512.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher_512x512.png +0 -0
- data/app/assets/images/dbwatcher/favicon-96x96.png +0 -0
- data/app/assets/images/dbwatcher/favicon.ico +0 -0
- data/app/assets/images/dbwatcher/favicon.svg +3 -0
- data/app/assets/images/dbwatcher/site.webmanifest +21 -0
- data/app/assets/images/dbwatcher/web-app-manifest-192x192.png +0 -0
- data/app/assets/images/dbwatcher/web-app-manifest-512x512.png +0 -0
- data/app/assets/stylesheets/dbwatcher/application.css +38 -4
- data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +57 -13
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +14 -18
- data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +1 -1
- data/app/controllers/dbwatcher/dashboard_controller.rb +1 -1
- data/app/views/dbwatcher/dashboard/_overview.html.erb +8 -7
- data/app/views/dbwatcher/sessions/index.html.erb +42 -59
- data/app/views/layouts/dbwatcher/application.html.erb +22 -6
- data/lib/dbwatcher/configuration.rb +51 -74
- data/lib/dbwatcher/logging.rb +23 -1
- data/lib/dbwatcher/services/diagram_analyzers/concerns/activerecord_introspection.rb +60 -0
- data/lib/dbwatcher/services/diagram_analyzers/concerns/association_scope_filtering.rb +60 -0
- data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +62 -36
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/association_extractor.rb +224 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/dataset_builder.rb +226 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/model_discovery.rb +161 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +27 -514
- data/lib/dbwatcher/services/diagram_data/attribute.rb +22 -83
- data/lib/dbwatcher/services/diagram_data/base.rb +129 -0
- data/lib/dbwatcher/services/diagram_data/entity.rb +23 -72
- data/lib/dbwatcher/services/diagram_data/relationship.rb +15 -66
- 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/erd_builder.rb +2 -2
- data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +4 -14
- data/lib/dbwatcher/services/mermaid_syntax_builder.rb +10 -8
- data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +7 -7
- data/lib/dbwatcher/services/system_info/system_info_collector.rb +3 -3
- data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +23 -1
- data/lib/dbwatcher/storage/session_storage.rb +2 -2
- data/lib/dbwatcher/storage.rb +1 -1
- data/lib/dbwatcher/version.rb +1 -1
- metadata +20 -2
@@ -8,6 +8,8 @@ module Dbwatcher
|
|
8
8
|
# Defines the common interface and shared behavior for all diagram generation
|
9
9
|
# strategies. Subclasses must implement the render_diagram method.
|
10
10
|
class BaseDiagramStrategy
|
11
|
+
include Dbwatcher::Logging
|
12
|
+
|
11
13
|
attr_reader :syntax_builder, :logger
|
12
14
|
|
13
15
|
# Initialize strategy with dependencies
|
@@ -25,8 +27,8 @@ module Dbwatcher
|
|
25
27
|
# @param dataset [Dataset] standardized dataset
|
26
28
|
# @return [Hash] diagram generation result
|
27
29
|
def generate_from_dataset(dataset)
|
28
|
-
|
29
|
-
|
30
|
+
log_info("Generating diagram from dataset with #{dataset.entities.size} entities and " \
|
31
|
+
"#{dataset.relationships.size} relationships")
|
30
32
|
start_time = Time.current
|
31
33
|
|
32
34
|
begin
|
@@ -40,7 +42,7 @@ module Dbwatcher
|
|
40
42
|
|
41
43
|
result
|
42
44
|
rescue StandardError => e
|
43
|
-
|
45
|
+
log_error("Diagram generation failed: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
|
44
46
|
error_response("Diagram generation failed: #{e.message}")
|
45
47
|
end
|
46
48
|
end
|
@@ -59,13 +61,25 @@ module Dbwatcher
|
|
59
61
|
|
60
62
|
protected
|
61
63
|
|
62
|
-
# Render diagram from dataset (
|
64
|
+
# Render diagram from dataset (template method)
|
63
65
|
#
|
64
66
|
# @param dataset [Dataset] standardized dataset
|
65
67
|
# @return [Hash] diagram generation result
|
66
|
-
# @raise [NotImplementedError] if not implemented by subclass
|
67
68
|
def render_diagram(dataset)
|
68
|
-
|
69
|
+
log_debug("Rendering #{mermaid_diagram_type} diagram from dataset with " \
|
70
|
+
"#{dataset.entities.size} entities and #{dataset.relationships.size} relationships")
|
71
|
+
|
72
|
+
# Generate diagram content directly from dataset
|
73
|
+
content = generate_diagram_content(dataset)
|
74
|
+
success_response(content, mermaid_diagram_type)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Generate diagram content based on dataset (to be implemented by subclasses)
|
78
|
+
#
|
79
|
+
# @param dataset [Dataset] standardized dataset
|
80
|
+
# @return [String] diagram content
|
81
|
+
def generate_diagram_content(dataset)
|
82
|
+
raise NotImplementedError, "Subclasses must implement generate_diagram_content method"
|
69
83
|
end
|
70
84
|
|
71
85
|
# Build empty diagram with message
|
@@ -118,9 +132,9 @@ module Dbwatcher
|
|
118
132
|
# @param operation [String] operation name
|
119
133
|
# @param duration [Float] operation duration in seconds
|
120
134
|
# @param context [Hash] additional context
|
121
|
-
def log_operation_completion(operation, duration,
|
122
|
-
|
123
|
-
|
135
|
+
def log_operation_completion(operation, duration, context = {})
|
136
|
+
log_info("Strategy operation completed: #{operation} by #{self.class.name} " \
|
137
|
+
"in #{(duration * 1000).round(2)}ms", context)
|
124
138
|
end
|
125
139
|
|
126
140
|
# Create default syntax builder
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "standard_diagram_strategy"
|
4
|
+
|
3
5
|
module Dbwatcher
|
4
6
|
module Services
|
5
7
|
module DiagramStrategies
|
@@ -7,31 +9,10 @@ module Dbwatcher
|
|
7
9
|
#
|
8
10
|
# Handles class diagram generation by converting dataset entities and relationships
|
9
11
|
# to Mermaid class diagram syntax.
|
10
|
-
class ClassDiagramStrategy <
|
11
|
-
protected
|
12
|
-
|
13
|
-
# Render class diagram from standardized dataset
|
14
|
-
#
|
15
|
-
# @param dataset [Dataset] standardized dataset
|
16
|
-
# @return [Hash] diagram generation result
|
17
|
-
def render_diagram(dataset)
|
18
|
-
@logger.debug "Rendering class diagram from dataset with #{dataset.entities.size} entities and " \
|
19
|
-
"#{dataset.relationships.size} relationships"
|
20
|
-
|
21
|
-
# Generate diagram content directly from dataset
|
22
|
-
content = if dataset.relationships.empty? && dataset.entities.empty?
|
23
|
-
@syntax_builder.build_empty_class_diagram("No model associations or entities found")
|
24
|
-
else
|
25
|
-
@syntax_builder.build_class_diagram_from_dataset(dataset)
|
26
|
-
end
|
27
|
-
|
28
|
-
success_response(content, "classDiagram")
|
29
|
-
end
|
30
|
-
|
12
|
+
class ClassDiagramStrategy < StandardDiagramStrategy
|
31
13
|
private
|
32
14
|
|
33
15
|
# Strategy metadata methods
|
34
|
-
|
35
16
|
def strategy_name
|
36
17
|
"Model Associations (Class Diagram)"
|
37
18
|
end
|
@@ -43,6 +24,19 @@ module Dbwatcher
|
|
43
24
|
def mermaid_diagram_type
|
44
25
|
"classDiagram"
|
45
26
|
end
|
27
|
+
|
28
|
+
# Diagram generation configuration
|
29
|
+
def empty_diagram_method
|
30
|
+
:build_empty_class_diagram
|
31
|
+
end
|
32
|
+
|
33
|
+
def empty_diagram_message
|
34
|
+
"No model associations or entities found"
|
35
|
+
end
|
36
|
+
|
37
|
+
def full_diagram_method
|
38
|
+
:build_class_diagram_from_dataset
|
39
|
+
end
|
46
40
|
end
|
47
41
|
end
|
48
42
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Services
|
5
|
+
module DiagramStrategies
|
6
|
+
# Helper module for diagram strategies
|
7
|
+
#
|
8
|
+
# Provides common utility methods for diagram strategies to reduce code duplication.
|
9
|
+
# These methods handle common patterns in diagram generation.
|
10
|
+
module DiagramStrategyHelpers
|
11
|
+
# Generate diagram content with empty state handling
|
12
|
+
#
|
13
|
+
# @param dataset [Dataset] standardized dataset
|
14
|
+
# @param options [Hash] options for diagram generation
|
15
|
+
# @option options [Symbol] :empty_method method to call for empty diagram
|
16
|
+
# @option options [String] :empty_message message for empty diagram
|
17
|
+
# @option options [Symbol] :empty_entities_method method to call for diagram with only entities
|
18
|
+
# @option options [Symbol] :full_diagram_method method to call for complete diagram
|
19
|
+
# @return [String] diagram content
|
20
|
+
def generate_standard_diagram_content(dataset, options)
|
21
|
+
if dataset.relationships.empty? && dataset.entities.empty?
|
22
|
+
@syntax_builder.send(options[:empty_method], options[:empty_message])
|
23
|
+
elsif dataset.relationships.empty? && options[:empty_entities_method]
|
24
|
+
# Show isolated entities if no relationships but entities exist
|
25
|
+
@syntax_builder.send(options[:empty_entities_method], dataset.entities.values)
|
26
|
+
else
|
27
|
+
@syntax_builder.send(options[:full_diagram_method], dataset)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "standard_diagram_strategy"
|
4
|
+
|
3
5
|
module Dbwatcher
|
4
6
|
module Services
|
5
7
|
module DiagramStrategies
|
@@ -7,34 +9,10 @@ module Dbwatcher
|
|
7
9
|
#
|
8
10
|
# Handles ERD diagram generation by converting dataset entities and relationships
|
9
11
|
# to Mermaid ERD syntax.
|
10
|
-
class ErdDiagramStrategy <
|
11
|
-
protected
|
12
|
-
|
13
|
-
# Render ERD diagram from standardized dataset
|
14
|
-
#
|
15
|
-
# @param dataset [Dataset] standardized dataset
|
16
|
-
# @return [Hash] diagram generation result
|
17
|
-
def render_diagram(dataset)
|
18
|
-
@logger.debug "Rendering ERD diagram from dataset with #{dataset.entities.size} entities and " \
|
19
|
-
"#{dataset.relationships.size} relationships"
|
20
|
-
|
21
|
-
# Generate diagram content directly from dataset
|
22
|
-
content = if dataset.relationships.empty? && dataset.entities.empty?
|
23
|
-
@syntax_builder.build_empty_erd("No database relationships or tables found")
|
24
|
-
elsif dataset.relationships.empty?
|
25
|
-
# Show isolated tables if no relationships but entities exist
|
26
|
-
@syntax_builder.build_erd_diagram_with_tables(dataset.entities.values)
|
27
|
-
else
|
28
|
-
@syntax_builder.build_erd_diagram_from_dataset(dataset)
|
29
|
-
end
|
30
|
-
|
31
|
-
success_response(content, "erDiagram")
|
32
|
-
end
|
33
|
-
|
12
|
+
class ErdDiagramStrategy < StandardDiagramStrategy
|
34
13
|
private
|
35
14
|
|
36
15
|
# Strategy metadata methods
|
37
|
-
|
38
16
|
def strategy_name
|
39
17
|
"Database Schema (ERD)"
|
40
18
|
end
|
@@ -46,6 +24,23 @@ module Dbwatcher
|
|
46
24
|
def mermaid_diagram_type
|
47
25
|
"erDiagram"
|
48
26
|
end
|
27
|
+
|
28
|
+
# Diagram generation configuration
|
29
|
+
def empty_diagram_method
|
30
|
+
:build_empty_erd
|
31
|
+
end
|
32
|
+
|
33
|
+
def empty_diagram_message
|
34
|
+
"No database relationships or tables found"
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty_entities_method
|
38
|
+
:build_erd_diagram_with_tables
|
39
|
+
end
|
40
|
+
|
41
|
+
def full_diagram_method
|
42
|
+
:build_erd_diagram_from_dataset
|
43
|
+
end
|
49
44
|
end
|
50
45
|
end
|
51
46
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "standard_diagram_strategy"
|
4
|
+
|
3
5
|
module Dbwatcher
|
4
6
|
module Services
|
5
7
|
module DiagramStrategies
|
@@ -7,34 +9,10 @@ module Dbwatcher
|
|
7
9
|
#
|
8
10
|
# Handles flowchart diagram generation by converting dataset entities and relationships
|
9
11
|
# to Mermaid flowchart syntax.
|
10
|
-
class FlowchartDiagramStrategy <
|
11
|
-
protected
|
12
|
-
|
13
|
-
# Render flowchart diagram from standardized dataset
|
14
|
-
#
|
15
|
-
# @param dataset [Dataset] standardized dataset
|
16
|
-
# @return [Hash] diagram generation result
|
17
|
-
def render_diagram(dataset)
|
18
|
-
@logger.debug "Rendering flowchart diagram from dataset with #{dataset.entities.size} entities and " \
|
19
|
-
"#{dataset.relationships.size} relationships"
|
20
|
-
|
21
|
-
# Generate diagram content directly from dataset
|
22
|
-
content = if dataset.relationships.empty? && dataset.entities.empty?
|
23
|
-
@syntax_builder.build_empty_flowchart("No model associations or entities found")
|
24
|
-
elsif dataset.relationships.empty?
|
25
|
-
# Show isolated nodes if no relationships but entities exist
|
26
|
-
@syntax_builder.build_flowchart_with_nodes(dataset.entities.values)
|
27
|
-
else
|
28
|
-
@syntax_builder.build_flowchart_diagram_from_dataset(dataset)
|
29
|
-
end
|
30
|
-
|
31
|
-
success_response(content, "flowchart")
|
32
|
-
end
|
33
|
-
|
12
|
+
class FlowchartDiagramStrategy < StandardDiagramStrategy
|
34
13
|
private
|
35
14
|
|
36
15
|
# Strategy metadata methods
|
37
|
-
|
38
16
|
def strategy_name
|
39
17
|
"Model Associations"
|
40
18
|
end
|
@@ -46,6 +24,23 @@ module Dbwatcher
|
|
46
24
|
def mermaid_diagram_type
|
47
25
|
"flowchart"
|
48
26
|
end
|
27
|
+
|
28
|
+
# Diagram generation configuration
|
29
|
+
def empty_diagram_method
|
30
|
+
:build_empty_flowchart
|
31
|
+
end
|
32
|
+
|
33
|
+
def empty_diagram_message
|
34
|
+
"No model associations or entities found"
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty_entities_method
|
38
|
+
:build_flowchart_with_nodes
|
39
|
+
end
|
40
|
+
|
41
|
+
def full_diagram_method
|
42
|
+
:build_flowchart_diagram_from_dataset
|
43
|
+
end
|
49
44
|
end
|
50
45
|
end
|
51
46
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_diagram_strategy"
|
4
|
+
require_relative "diagram_strategy_helpers"
|
5
|
+
|
6
|
+
module Dbwatcher
|
7
|
+
module Services
|
8
|
+
module DiagramStrategies
|
9
|
+
# Standard diagram strategy implementation
|
10
|
+
#
|
11
|
+
# Provides a common implementation for diagram strategies that follow
|
12
|
+
# the standard pattern of generating diagrams from datasets.
|
13
|
+
# Specific strategies can inherit from this class and provide only
|
14
|
+
# the necessary configuration.
|
15
|
+
class StandardDiagramStrategy < BaseDiagramStrategy
|
16
|
+
include DiagramStrategyHelpers
|
17
|
+
|
18
|
+
# Initialize with configuration options
|
19
|
+
#
|
20
|
+
# @param dependencies [Hash] injected dependencies
|
21
|
+
# @option dependencies [Object] :syntax_builder Mermaid syntax builder
|
22
|
+
# @option dependencies [Logger] :logger logger instance
|
23
|
+
def initialize(dependencies = {})
|
24
|
+
super
|
25
|
+
@diagram_options = diagram_options
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
# Generate diagram content from dataset using standard pattern
|
31
|
+
#
|
32
|
+
# @param dataset [Dataset] standardized dataset
|
33
|
+
# @return [String] diagram content
|
34
|
+
def generate_diagram_content(dataset)
|
35
|
+
generate_standard_diagram_content(dataset, @diagram_options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get diagram generation options
|
39
|
+
#
|
40
|
+
# @return [Hash] diagram options
|
41
|
+
def diagram_options
|
42
|
+
{
|
43
|
+
empty_method: empty_diagram_method,
|
44
|
+
empty_message: empty_diagram_message,
|
45
|
+
empty_entities_method: empty_entities_method,
|
46
|
+
full_diagram_method: full_diagram_method
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get method name for generating empty diagram
|
51
|
+
#
|
52
|
+
# @return [Symbol] method name
|
53
|
+
def empty_diagram_method
|
54
|
+
raise NotImplementedError, "Subclasses must implement empty_diagram_method"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get message for empty diagram
|
58
|
+
#
|
59
|
+
# @return [String] empty diagram message
|
60
|
+
def empty_diagram_message
|
61
|
+
"No data available for diagram"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get method name for generating diagram with only entities
|
65
|
+
#
|
66
|
+
# @return [Symbol] method name
|
67
|
+
def empty_entities_method
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get method name for generating full diagram
|
72
|
+
#
|
73
|
+
# @return [Symbol] method name
|
74
|
+
def full_diagram_method
|
75
|
+
raise NotImplementedError, "Subclasses must implement full_diagram_method"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -29,6 +29,8 @@ require_relative "diagram_analyzers/model_association_analyzer"
|
|
29
29
|
|
30
30
|
# Diagram strategies
|
31
31
|
require_relative "diagram_strategies/base_diagram_strategy"
|
32
|
+
require_relative "diagram_strategies/diagram_strategy_helpers"
|
33
|
+
require_relative "diagram_strategies/standard_diagram_strategy"
|
32
34
|
require_relative "diagram_strategies/erd_diagram_strategy"
|
33
35
|
require_relative "diagram_strategies/class_diagram_strategy"
|
34
36
|
require_relative "diagram_strategies/flowchart_diagram_strategy"
|
@@ -41,6 +43,13 @@ module Dbwatcher
|
|
41
43
|
# Diagram System Module
|
42
44
|
# Provides centralized access to diagram generation capabilities
|
43
45
|
module DiagramSystem
|
46
|
+
extend Dbwatcher::Logging
|
47
|
+
|
48
|
+
# Explicitly set the component name for logging
|
49
|
+
def self.component_name
|
50
|
+
"DiagramSystem"
|
51
|
+
end
|
52
|
+
|
44
53
|
# Get available diagram types
|
45
54
|
#
|
46
55
|
# @return [Array<String>] available diagram type names
|
@@ -54,7 +63,11 @@ module Dbwatcher
|
|
54
63
|
# @param diagram_type [String] type of diagram to generate
|
55
64
|
# @return [Hash] diagram generation result
|
56
65
|
def self.generate(session_id, diagram_type = "database_tables")
|
57
|
-
|
66
|
+
log_debug("Generating diagram of type #{diagram_type} for session #{session_id}")
|
67
|
+
generator = DiagramGenerator.new(session_id: session_id, diagram_type: diagram_type)
|
68
|
+
result = generator.call
|
69
|
+
log_debug("Diagram generation completed with success=#{result[:success]}")
|
70
|
+
result
|
58
71
|
end
|
59
72
|
|
60
73
|
# Check if diagram type is supported
|
@@ -70,7 +70,7 @@ module Dbwatcher
|
|
70
70
|
# @param entity [DiagramData::Entity] entity to render
|
71
71
|
# @return [Array<String>] entity definition lines
|
72
72
|
def build_erd_entity(entity)
|
73
|
-
table_name = Sanitizer.table_name(entity.name
|
73
|
+
table_name = Sanitizer.table_name(entity.name)
|
74
74
|
lines = [" #{table_name} {"]
|
75
75
|
|
76
76
|
# Add attributes if enabled and available
|
@@ -92,7 +92,7 @@ module Dbwatcher
|
|
92
92
|
# @return [String] formatted entity name
|
93
93
|
def format_entity_name(entity_id, dataset)
|
94
94
|
entity_name = dataset.get_entity(entity_id)&.name || entity_id
|
95
|
-
Sanitizer.table_name(entity_name
|
95
|
+
Sanitizer.table_name(entity_name)
|
96
96
|
end
|
97
97
|
|
98
98
|
# Build relationship definition
|
@@ -48,22 +48,12 @@ module Dbwatcher
|
|
48
48
|
# Sanitize table name for Mermaid ERD
|
49
49
|
#
|
50
50
|
# @param name [String] raw table name
|
51
|
-
# @
|
52
|
-
|
53
|
-
def table_name(name, preserve_case = nil)
|
51
|
+
# @return [String] sanitized table name (preserves original case)
|
52
|
+
def table_name(name)
|
54
53
|
return "UNKNOWN_TABLE" unless name.is_a?(String) && !name.empty?
|
55
54
|
|
56
|
-
preserve
|
57
|
-
|
58
|
-
else
|
59
|
-
preserve_case
|
60
|
-
end
|
61
|
-
|
62
|
-
if preserve
|
63
|
-
name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
|
64
|
-
else
|
65
|
-
name.to_s.upcase.gsub(/[^A-Z0-9_]/, "_")
|
66
|
-
end
|
55
|
+
# Always preserve original case
|
56
|
+
name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
|
67
57
|
end
|
68
58
|
|
69
59
|
# Sanitize method name for Mermaid class diagrams
|
@@ -15,6 +15,8 @@ module Dbwatcher
|
|
15
15
|
# content = builder.build_erd_diagram_from_dataset(dataset)
|
16
16
|
# # => "erDiagram\n USERS ||--o{ ORDERS : user_id"
|
17
17
|
class MermaidSyntaxBuilder
|
18
|
+
include Dbwatcher::Logging
|
19
|
+
|
18
20
|
# Custom error classes
|
19
21
|
class SyntaxValidationError < StandardError; end
|
20
22
|
class UnsupportedDiagramTypeError < StandardError; end
|
@@ -40,8 +42,8 @@ module Dbwatcher
|
|
40
42
|
# @param options [Hash] generation options
|
41
43
|
# @return [String] Mermaid ERD syntax
|
42
44
|
def build_erd_diagram_from_dataset(dataset, options = {})
|
43
|
-
|
44
|
-
|
45
|
+
log_debug("Building ERD diagram from dataset with #{dataset.entities.size} entities and " \
|
46
|
+
"#{dataset.relationships.size} relationships")
|
45
47
|
|
46
48
|
builder = MermaidSyntax::ErdBuilder.new(@config.merge(options))
|
47
49
|
builder.build_from_dataset(dataset)
|
@@ -53,8 +55,8 @@ module Dbwatcher
|
|
53
55
|
# @param options [Hash] generation options
|
54
56
|
# @return [String] Mermaid class diagram syntax
|
55
57
|
def build_class_diagram_from_dataset(dataset, options = {})
|
56
|
-
|
57
|
-
|
58
|
+
log_debug("Building class diagram from dataset with #{dataset.entities.size} entities and " \
|
59
|
+
"#{dataset.relationships.size} relationships")
|
58
60
|
|
59
61
|
builder = MermaidSyntax::ClassDiagramBuilder.new(@config.merge(options))
|
60
62
|
builder.build_from_dataset(dataset)
|
@@ -66,8 +68,8 @@ module Dbwatcher
|
|
66
68
|
# @param options [Hash] generation options
|
67
69
|
# @return [String] Mermaid flowchart syntax
|
68
70
|
def build_flowchart_diagram_from_dataset(dataset, options = {})
|
69
|
-
|
70
|
-
|
71
|
+
log_debug("Building flowchart diagram from dataset with #{dataset.entities.size} entities and " \
|
72
|
+
"#{dataset.relationships.size} relationships")
|
71
73
|
|
72
74
|
builder = MermaidSyntax::FlowchartBuilder.new(@config.merge(options))
|
73
75
|
builder.build_from_dataset(dataset)
|
@@ -125,7 +127,7 @@ module Dbwatcher
|
|
125
127
|
# @param options [Hash] generation options
|
126
128
|
# @return [String] Mermaid ERD syntax
|
127
129
|
def build_erd_diagram_with_tables(entities, options = {})
|
128
|
-
|
130
|
+
log_debug("Building ERD diagram with #{entities.size} isolated tables")
|
129
131
|
|
130
132
|
dataset = Dbwatcher::Services::DiagramData::Dataset.new
|
131
133
|
entities.each { |entity| dataset.add_entity(entity) }
|
@@ -139,7 +141,7 @@ module Dbwatcher
|
|
139
141
|
# @param options [Hash] generation options
|
140
142
|
# @return [String] Mermaid flowchart syntax
|
141
143
|
def build_flowchart_with_nodes(entities, options = {})
|
142
|
-
|
144
|
+
log_debug("Building flowchart diagram with #{entities.size} isolated nodes")
|
143
145
|
|
144
146
|
dataset = Dbwatcher::Services::DiagramData::Dataset.new
|
145
147
|
entities.each { |entity| dataset.add_entity(entity) }
|
@@ -136,7 +136,7 @@ module Dbwatcher
|
|
136
136
|
#
|
137
137
|
# @return [Hash] loaded gems with versions
|
138
138
|
def collect_loaded_gems
|
139
|
-
return {} unless Dbwatcher.configuration.system_info_include_performance_metrics
|
139
|
+
return {} unless Dbwatcher.configuration.system_info_include_performance_metrics?
|
140
140
|
|
141
141
|
gems = {}
|
142
142
|
Gem.loaded_specs.each do |name, spec|
|
@@ -154,7 +154,7 @@ module Dbwatcher
|
|
154
154
|
def collect_load_path_info
|
155
155
|
{
|
156
156
|
size: $LOAD_PATH.size,
|
157
|
-
paths: Dbwatcher.configuration.system_info_include_performance_metrics ? $LOAD_PATH.first(10) : []
|
157
|
+
paths: Dbwatcher.configuration.system_info_include_performance_metrics? ? $LOAD_PATH.first(10) : []
|
158
158
|
}
|
159
159
|
rescue StandardError => e
|
160
160
|
log_error "Failed to get load path info: #{e.message}"
|
@@ -166,7 +166,7 @@ module Dbwatcher
|
|
166
166
|
# @return [Hash] filtered environment variables
|
167
167
|
# rubocop:disable Metrics/MethodLength
|
168
168
|
def collect_environment_variables
|
169
|
-
return {} unless Dbwatcher.configuration.collect_sensitive_env_vars
|
169
|
+
return {} unless Dbwatcher.configuration.collect_sensitive_env_vars?
|
170
170
|
|
171
171
|
env_vars = {}
|
172
172
|
|
@@ -280,14 +280,14 @@ module Dbwatcher
|
|
280
280
|
300
|
281
281
|
end,
|
282
282
|
collect_sensitive_env_vars:
|
283
|
-
if config.respond_to?(:collect_sensitive_env_vars)
|
284
|
-
config.collect_sensitive_env_vars
|
283
|
+
if config.respond_to?(:collect_sensitive_env_vars?)
|
284
|
+
config.collect_sensitive_env_vars?
|
285
285
|
else
|
286
286
|
false
|
287
287
|
end,
|
288
288
|
system_info_include_performance_metrics:
|
289
|
-
if config.respond_to?(:system_info_include_performance_metrics)
|
290
|
-
config.system_info_include_performance_metrics
|
289
|
+
if config.respond_to?(:system_info_include_performance_metrics?)
|
290
|
+
config.system_info_include_performance_metrics?
|
291
291
|
else
|
292
292
|
true
|
293
293
|
end
|
@@ -77,7 +77,7 @@ module Dbwatcher
|
|
77
77
|
#
|
78
78
|
# @return [Hash] machine information or empty hash on error
|
79
79
|
def collect_machine_info
|
80
|
-
return {} unless Dbwatcher.configuration.
|
80
|
+
return {} unless Dbwatcher.configuration.system_info
|
81
81
|
|
82
82
|
MachineInfoCollector.call
|
83
83
|
rescue StandardError => e
|
@@ -89,7 +89,7 @@ module Dbwatcher
|
|
89
89
|
#
|
90
90
|
# @return [Hash] database information or empty hash on error
|
91
91
|
def collect_database_info
|
92
|
-
return {} unless Dbwatcher.configuration.
|
92
|
+
return {} unless Dbwatcher.configuration.system_info
|
93
93
|
|
94
94
|
DatabaseInfoCollector.call
|
95
95
|
rescue StandardError => e
|
@@ -101,7 +101,7 @@ module Dbwatcher
|
|
101
101
|
#
|
102
102
|
# @return [Hash] runtime information or empty hash on error
|
103
103
|
def collect_runtime_info
|
104
|
-
return {} unless Dbwatcher.configuration.
|
104
|
+
return {} unless Dbwatcher.configuration.system_info
|
105
105
|
|
106
106
|
RuntimeInfoCollector.call
|
107
107
|
rescue StandardError => e
|
@@ -66,7 +66,29 @@ module Dbwatcher
|
|
66
66
|
# @param change [Hash] change data
|
67
67
|
# @return [String, nil] record ID if available
|
68
68
|
def extract_record_id(change)
|
69
|
-
change[:record_id] || change[:id] || change
|
69
|
+
change[:record_id] || change[:id] || extract_id_from_changes(change[:changes])
|
70
|
+
end
|
71
|
+
|
72
|
+
# Extract ID from changes data (hash or array format)
|
73
|
+
#
|
74
|
+
# @param changes [Hash, Array, nil] changes data
|
75
|
+
# @return [String, nil] extracted ID
|
76
|
+
def extract_id_from_changes(changes)
|
77
|
+
return nil unless changes
|
78
|
+
|
79
|
+
case changes
|
80
|
+
when Hash then changes[:id]
|
81
|
+
when Array then extract_id_from_array_changes(changes)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Extract ID from array of column changes
|
86
|
+
#
|
87
|
+
# @param changes [Array] array of column changes
|
88
|
+
# @return [String, nil] extracted ID value
|
89
|
+
def extract_id_from_array_changes(changes)
|
90
|
+
id_change = changes.find { |c| c.is_a?(Hash) && c[:column] == "id" }
|
91
|
+
id_change&.dig(:new_value) || id_change&.dig(:old_value)
|
70
92
|
end
|
71
93
|
|
72
94
|
# Format changes for timeline display
|
@@ -282,14 +282,14 @@ module Dbwatcher
|
|
282
282
|
#
|
283
283
|
# @return [Boolean] true if cleanup is enabled
|
284
284
|
def cleanup_enabled?
|
285
|
-
Dbwatcher.configuration.
|
285
|
+
Dbwatcher.configuration.auto_clean_days&.positive?
|
286
286
|
end
|
287
287
|
|
288
288
|
# Calculates cleanup cutoff date
|
289
289
|
#
|
290
290
|
# @return [Time] cutoff date for cleanup
|
291
291
|
def calculate_cleanup_cutoff
|
292
|
-
days = Dbwatcher.configuration.
|
292
|
+
days = Dbwatcher.configuration.auto_clean_days
|
293
293
|
current_time - (days * 24 * 60 * 60)
|
294
294
|
end
|
295
295
|
|
data/lib/dbwatcher/storage.rb
CHANGED
@@ -105,7 +105,7 @@ module Dbwatcher
|
|
105
105
|
# retention period. This helps manage storage space usage.
|
106
106
|
#
|
107
107
|
# @return [void]
|
108
|
-
# @see Configuration#
|
108
|
+
# @see Configuration#auto_clean_days
|
109
109
|
def cleanup_old_sessions
|
110
110
|
session_storage.cleanup_old_sessions
|
111
111
|
end
|
data/lib/dbwatcher/version.rb
CHANGED