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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -26
  3. data/app/assets/images/dbwatcher/apple-touch-icon.png +0 -0
  4. data/app/assets/images/dbwatcher/dbwatcher-social-preview.png +0 -0
  5. data/app/assets/images/dbwatcher/dbwatcher-tranparent_512x512.png +0 -0
  6. data/app/assets/images/dbwatcher/dbwatcher_512x512.png +0 -0
  7. data/app/assets/images/dbwatcher/favicon-96x96.png +0 -0
  8. data/app/assets/images/dbwatcher/favicon.ico +0 -0
  9. data/app/assets/images/dbwatcher/favicon.svg +3 -0
  10. data/app/assets/images/dbwatcher/site.webmanifest +21 -0
  11. data/app/assets/images/dbwatcher/web-app-manifest-192x192.png +0 -0
  12. data/app/assets/images/dbwatcher/web-app-manifest-512x512.png +0 -0
  13. data/app/assets/stylesheets/dbwatcher/application.css +38 -4
  14. data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +57 -13
  15. data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +14 -18
  16. data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +1 -1
  17. data/app/controllers/dbwatcher/dashboard_controller.rb +1 -1
  18. data/app/views/dbwatcher/dashboard/_overview.html.erb +8 -7
  19. data/app/views/dbwatcher/sessions/index.html.erb +42 -59
  20. data/app/views/layouts/dbwatcher/application.html.erb +22 -6
  21. data/lib/dbwatcher/configuration.rb +51 -74
  22. data/lib/dbwatcher/logging.rb +23 -1
  23. data/lib/dbwatcher/services/diagram_analyzers/concerns/activerecord_introspection.rb +60 -0
  24. data/lib/dbwatcher/services/diagram_analyzers/concerns/association_scope_filtering.rb +60 -0
  25. data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +62 -36
  26. data/lib/dbwatcher/services/diagram_analyzers/model_analysis/association_extractor.rb +224 -0
  27. data/lib/dbwatcher/services/diagram_analyzers/model_analysis/dataset_builder.rb +226 -0
  28. data/lib/dbwatcher/services/diagram_analyzers/model_analysis/model_discovery.rb +161 -0
  29. data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +27 -514
  30. data/lib/dbwatcher/services/diagram_data/attribute.rb +22 -83
  31. data/lib/dbwatcher/services/diagram_data/base.rb +129 -0
  32. data/lib/dbwatcher/services/diagram_data/entity.rb +23 -72
  33. data/lib/dbwatcher/services/diagram_data/relationship.rb +15 -66
  34. data/lib/dbwatcher/services/diagram_generator.rb +35 -69
  35. data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +23 -9
  36. data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +16 -22
  37. data/lib/dbwatcher/services/diagram_strategies/diagram_strategy_helpers.rb +33 -0
  38. data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +20 -25
  39. data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +20 -25
  40. data/lib/dbwatcher/services/diagram_strategies/standard_diagram_strategy.rb +80 -0
  41. data/lib/dbwatcher/services/diagram_system.rb +14 -1
  42. data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +2 -0
  43. data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +2 -2
  44. data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +4 -14
  45. data/lib/dbwatcher/services/mermaid_syntax_builder.rb +10 -8
  46. data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +7 -7
  47. data/lib/dbwatcher/services/system_info/system_info_collector.rb +3 -3
  48. data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +23 -1
  49. data/lib/dbwatcher/storage/session_storage.rb +2 -2
  50. data/lib/dbwatcher/storage.rb +1 -1
  51. data/lib/dbwatcher/version.rb +1 -1
  52. 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
- @logger.info("Generating diagram from dataset with #{dataset.entities.size} entities and " \
29
- "#{dataset.relationships.size} relationships")
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
- @logger.error("Diagram generation failed: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
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 (abstract method)
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
- raise NotImplementedError, "Subclasses must implement render_diagram method"
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, _context = {})
122
- @logger.info("Strategy operation completed: #{operation} by #{self.class.name} " \
123
- "in #{(duration * 1000).round(2)}ms")
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 < BaseDiagramStrategy
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 < BaseDiagramStrategy
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 < BaseDiagramStrategy
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
- DiagramGenerator.new(session_id, diagram_type).call
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
@@ -15,6 +15,8 @@ module Dbwatcher
15
15
  # end
16
16
  # end
17
17
  class BaseBuilder
18
+ include Dbwatcher::Logging
19
+
18
20
  # Initialize a new builder with configuration
19
21
  #
20
22
  # @param config [Hash] configuration options
@@ -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, preserve_table_case?)
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, preserve_table_case?)
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
- # @param preserve_case [Boolean] whether to preserve original case
52
- # @return [String] sanitized table name
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 = if preserve_case.nil?
57
- Dbwatcher.configuration.diagram_preserve_table_case
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
- @logger.debug "Building ERD diagram from dataset with #{dataset.entities.size} entities and " \
44
- "#{dataset.relationships.size} relationships"
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
- @logger.debug "Building class diagram from dataset with #{dataset.entities.size} entities and " \
57
- "#{dataset.relationships.size} relationships"
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
- @logger.debug "Building flowchart diagram from dataset with #{dataset.entities.size} entities and " \
70
- "#{dataset.relationships.size} relationships"
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
- @logger.debug "Building ERD diagram with #{entities.size} isolated tables"
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
- @logger.debug "Building flowchart diagram with #{entities.size} isolated nodes"
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.collect_system_info
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.collect_system_info
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.collect_system_info
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.dig(:changes, :id)
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.auto_clean_after_days&.positive?
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.auto_clean_after_days
292
+ days = Dbwatcher.configuration.auto_clean_days
293
293
  current_time - (days * 24 * 60 * 60)
294
294
  end
295
295
 
@@ -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#auto_clean_after_days
108
+ # @see Configuration#auto_clean_days
109
109
  def cleanup_old_sessions
110
110
  session_storage.cleanup_old_sessions
111
111
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dbwatcher
4
- VERSION = "1.1.3"
4
+ VERSION = "1.1.5"
5
5
  end