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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "base"
4
+
3
5
  module Dbwatcher
4
6
  module Services
5
7
  module DiagramData
@@ -18,7 +20,7 @@ module Dbwatcher
18
20
  # )
19
21
  # attribute.valid? # => true
20
22
  # attribute.to_h # => { name: "email", type: "string", ... }
21
- class Attribute
23
+ class Attribute < Base
22
24
  attr_accessor :name, :type, :nullable, :default, :metadata
23
25
 
24
26
  # Initialize attribute
@@ -29,6 +31,7 @@ module Dbwatcher
29
31
  # @param default [Object] default value
30
32
  # @param metadata [Hash] additional type-specific information
31
33
  def initialize(name:, type: nil, nullable: true, default: nil, metadata: {})
34
+ super() # Initialize parent class
32
35
  @name = name.to_s
33
36
  @type = type.to_s
34
37
  @nullable = nullable == true
@@ -36,16 +39,23 @@ module Dbwatcher
36
39
  @metadata = metadata.is_a?(Hash) ? metadata : {}
37
40
  end
38
41
 
39
- # Check if attribute is valid
40
- #
41
- # @return [Boolean] true if attribute has required fields
42
- def valid?
43
- validation_errors.empty?
42
+ # Implementation for Base class
43
+ def comparable_attributes
44
+ [name, type, nullable, default, metadata]
44
45
  end
45
46
 
46
- # Get validation errors
47
- #
48
- # @return [Array<String>] array of validation error messages
47
+ # Implementation for Base class
48
+ def serializable_attributes
49
+ {
50
+ name: name,
51
+ type: type,
52
+ nullable: nullable,
53
+ default: default,
54
+ metadata: metadata
55
+ }
56
+ end
57
+
58
+ # Implementation for Base class
49
59
  def validation_errors
50
60
  errors = []
51
61
  errors << "Name cannot be blank" if name.nil? || name.to_s.strip.empty?
@@ -67,86 +77,15 @@ module Dbwatcher
67
77
  metadata[:foreign_key] == true || name.to_s.end_with?("_id")
68
78
  end
69
79
 
70
- # Serialize attribute to hash
71
- #
72
- # @return [Hash] serialized attribute data
73
- def to_h
80
+ # Override base class method to handle nullable default
81
+ def self.extract_constructor_args(hash)
74
82
  {
75
- name: name,
76
- type: type,
77
- nullable: nullable,
78
- default: default,
79
- metadata: metadata
80
- }
81
- end
82
-
83
- # Serialize attribute to JSON
84
- #
85
- # @return [String] JSON representation
86
- def to_json(*args)
87
- to_h.to_json(*args)
88
- end
89
-
90
- # Create attribute from hash
91
- #
92
- # @param hash [Hash] attribute data
93
- # @return [Attribute] new attribute instance
94
- def self.from_h(hash)
95
- # Convert string keys to symbols for consistent access
96
- hash = hash.transform_keys(&:to_sym) if hash.keys.first.is_a?(String)
97
-
98
- # Use fetch with default values to handle missing fields
99
- new(
100
83
  name: hash[:name],
101
84
  type: hash[:type],
102
85
  nullable: hash.key?(:nullable) ? hash[:nullable] : true,
103
86
  default: hash[:default],
104
87
  metadata: hash[:metadata] || {}
105
- )
106
- end
107
-
108
- # Create attribute from JSON
109
- #
110
- # @param json [String] JSON string
111
- # @return [Attribute] new attribute instance
112
- def self.from_json(json)
113
- from_h(JSON.parse(json))
114
- end
115
-
116
- # Check equality with another attribute
117
- #
118
- # @param other [Attribute] other attribute to compare
119
- # @return [Boolean] true if attributes are equal
120
- def ==(other)
121
- return false unless other.is_a?(Attribute)
122
-
123
- name == other.name &&
124
- type == other.type &&
125
- nullable == other.nullable &&
126
- default == other.default &&
127
- metadata == other.metadata
128
- end
129
-
130
- # Generate hash code for attribute
131
- #
132
- # @return [Integer] hash code
133
- def hash
134
- [name, type, nullable, default, metadata].hash
135
- end
136
-
137
- # String representation of attribute
138
- #
139
- # @return [String] string representation
140
- def to_s
141
- "#{self.class.name}(name: #{name}, type: #{type}, nullable: #{nullable})"
142
- end
143
-
144
- # Detailed string representation
145
- #
146
- # @return [String] detailed string representation
147
- def inspect
148
- "#{self.class.name}(name: #{name.inspect}, type: #{type.inspect}, " \
149
- "nullable: #{nullable.inspect}, default: #{default.inspect}, metadata: #{metadata.inspect})"
88
+ }
150
89
  end
151
90
  end
152
91
  end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Dbwatcher
6
+ module Services
7
+ module DiagramData
8
+ # Base class for diagram data objects
9
+ #
10
+ # Provides common functionality for serialization, validation, and comparison
11
+ # that is shared across Attribute, Entity, and Relationship classes.
12
+ #
13
+ # Subclasses must implement:
14
+ # - comparable_attributes: Array of values used for equality comparison
15
+ # - serializable_attributes: Hash of attributes for serialization
16
+ # - validation_errors: Array of validation error strings (optional)
17
+ #
18
+ # @example
19
+ # class MyClass < Base
20
+ # def comparable_attributes
21
+ # [name, type, value]
22
+ # end
23
+ #
24
+ # def serializable_attributes
25
+ # { name: name, type: type, value: value }
26
+ # end
27
+ # end
28
+ class Base
29
+ # Check if object is valid
30
+ #
31
+ # @return [Boolean] true if object has no validation errors
32
+ def valid?
33
+ validation_errors.empty?
34
+ end
35
+
36
+ # Check equality with another object of the same class
37
+ #
38
+ # @param other [Object] object to compare with
39
+ # @return [Boolean] true if objects are equal
40
+ def ==(other)
41
+ return false unless other.is_a?(self.class)
42
+
43
+ comparable_attributes == other.comparable_attributes
44
+ end
45
+
46
+ # Generate hash code for object
47
+ #
48
+ # @return [Integer] hash code
49
+ def hash
50
+ comparable_attributes.hash
51
+ end
52
+
53
+ # Serialize object to hash
54
+ #
55
+ # @return [Hash] serialized object data
56
+ def to_h
57
+ serializable_attributes
58
+ end
59
+
60
+ # Serialize object to JSON
61
+ #
62
+ # @return [String] JSON representation
63
+ def to_json(*args)
64
+ to_h.to_json(*args)
65
+ end
66
+
67
+ # Create object from hash
68
+ #
69
+ # @param hash [Hash] object data
70
+ # @return [Object] new object instance
71
+ def self.from_h(hash)
72
+ # Convert string keys to symbols for consistent access
73
+ hash = hash.transform_keys(&:to_sym) if hash.respond_to?(:transform_keys) && hash.keys.first.is_a?(String)
74
+
75
+ new(**extract_constructor_args(hash))
76
+ end
77
+
78
+ # Create object from JSON
79
+ #
80
+ # @param json [String] JSON string
81
+ # @return [Object] new object instance
82
+ def self.from_json(json)
83
+ from_h(JSON.parse(json))
84
+ end
85
+
86
+ # String representation of object
87
+ #
88
+ # @return [String] string representation
89
+ def to_s
90
+ attrs = serializable_attributes.map { |k, v| "#{k}: #{v}" }.join(", ")
91
+ "#{self.class.name}(#{attrs})"
92
+ end
93
+
94
+ # Detailed string representation
95
+ #
96
+ # @return [String] detailed string representation
97
+ def inspect
98
+ attrs = serializable_attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
99
+ "#{self.class.name}(#{attrs})"
100
+ end
101
+
102
+ # Default implementation - subclasses should override
103
+ def comparable_attributes
104
+ raise NotImplementedError, "#{self.class} must implement #comparable_attributes"
105
+ end
106
+
107
+ # Default implementation - subclasses should override
108
+ def serializable_attributes
109
+ raise NotImplementedError, "#{self.class} must implement #serializable_attributes"
110
+ end
111
+
112
+ # Default implementation - subclasses should override if validation needed
113
+ def validation_errors
114
+ []
115
+ end
116
+
117
+ # Extract constructor arguments from hash
118
+ # Subclasses can override this for custom initialization logic
119
+ #
120
+ # @param hash [Hash] object data
121
+ # @return [Hash] constructor arguments
122
+ def self.extract_constructor_args(hash)
123
+ hash
124
+ end
125
+ private_class_method :extract_constructor_args
126
+ end
127
+ end
128
+ end
129
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "base"
4
+
3
5
  module Dbwatcher
4
6
  module Services
5
7
  module DiagramData
@@ -20,7 +22,7 @@ module Dbwatcher
20
22
  # )
21
23
  # entity.valid? # => true
22
24
  # entity.to_h # => { id: "users", name: "User", ... }
23
- class Entity
25
+ class Entity < Base
24
26
  attr_accessor :id, :name, :type, :attributes, :metadata
25
27
 
26
28
  # Initialize entity
@@ -31,6 +33,7 @@ module Dbwatcher
31
33
  # @param attributes [Array<Attribute>] entity attributes/properties
32
34
  # @param metadata [Hash] additional type-specific information
33
35
  def initialize(id:, name:, type: "default", attributes: [], metadata: {})
36
+ super() # Initialize parent class
34
37
  @id = id.to_s
35
38
  @name = name.to_s
36
39
  @type = type.to_s
@@ -38,16 +41,23 @@ module Dbwatcher
38
41
  @metadata = metadata.is_a?(Hash) ? metadata : {}
39
42
  end
40
43
 
41
- # Check if entity is valid
42
- #
43
- # @return [Boolean] true if entity has required fields
44
- def valid?
45
- validation_errors.empty?
44
+ # Implementation for Base class
45
+ def comparable_attributes
46
+ [id, name, type, attributes, metadata]
46
47
  end
47
48
 
48
- # Get validation errors
49
- #
50
- # @return [Array<String>] array of validation error messages
49
+ # Implementation for Base class
50
+ def serializable_attributes
51
+ {
52
+ id: id,
53
+ name: name,
54
+ type: type,
55
+ attributes: attributes.map(&:to_h),
56
+ metadata: metadata
57
+ }
58
+ end
59
+
60
+ # Implementation for Base class
51
61
  def validation_errors
52
62
  errors = []
53
63
  errors << "ID cannot be blank" if id.nil? || id.to_s.strip.empty?
@@ -91,80 +101,21 @@ module Dbwatcher
91
101
  attributes.select(&:foreign_key?)
92
102
  end
93
103
 
94
- # Serialize entity to hash
95
- #
96
- # @return [Hash] serialized entity data
97
- def to_h
98
- {
99
- id: id,
100
- name: name,
101
- type: type,
102
- attributes: attributes.map(&:to_h),
103
- metadata: metadata
104
- }
105
- end
106
-
107
- # Serialize entity to JSON
108
- #
109
- # @return [String] JSON representation
110
- def to_json(*args)
111
- to_h.to_json(*args)
112
- end
113
-
114
- # Create entity from hash
115
- #
116
- # @param hash [Hash] entity data
117
- # @return [Entity] new entity instance
118
- def self.from_h(hash)
104
+ # Override base class method to handle attributes array
105
+ def self.extract_constructor_args(hash)
119
106
  attrs = []
120
107
  if hash[:attributes] || hash["attributes"]
121
108
  attr_data = hash[:attributes] || hash["attributes"]
122
109
  attrs = attr_data.map { |attr| Attribute.from_h(attr) }
123
110
  end
124
111
 
125
- new(
112
+ {
126
113
  id: hash[:id] || hash["id"],
127
114
  name: hash[:name] || hash["name"],
128
115
  type: hash[:type] || hash["type"] || "default",
129
116
  attributes: attrs,
130
117
  metadata: hash[:metadata] || hash["metadata"] || {}
131
- )
132
- end
133
-
134
- # Create entity from JSON
135
- #
136
- # @param json [String] JSON string
137
- # @return [Entity] new entity instance
138
- def self.from_json(json)
139
- from_h(JSON.parse(json))
140
- end
141
-
142
- # Check equality with another entity
143
- #
144
- # @param other [Entity] other entity to compare
145
- # @return [Boolean] true if entities are equal
146
- def ==(other)
147
- return false unless other.is_a?(Entity)
148
-
149
- id == other.id &&
150
- name == other.name &&
151
- type == other.type &&
152
- attributes == other.attributes &&
153
- metadata == other.metadata
154
- end
155
-
156
- # Generate hash code for entity
157
- #
158
- # @return [Integer] hash code
159
- def hash
160
- [id, name, type, attributes, metadata].hash
161
- end
162
-
163
- # String representation of entity
164
- #
165
- # @return [String] string representation
166
- def to_s
167
- "#{self.class.name}(id: #{id}, name: #{name}, type: #{type})"
118
+ }
168
119
  end
169
120
 
170
121
  # Detailed string representation
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "base"
3
4
  require_relative "relationship_params"
4
5
 
5
6
  module Dbwatcher
@@ -21,7 +22,7 @@ module Dbwatcher
21
22
  # )
22
23
  # relationship.valid? # => true
23
24
  # relationship.to_h # => { source_id: "users", target_id: "orders", ... }
24
- class Relationship
25
+ class Relationship < Base
25
26
  attr_accessor :source_id, :target_id, :type, :label, :cardinality, :metadata
26
27
 
27
28
  # Valid cardinality types
@@ -57,6 +58,7 @@ module Dbwatcher
57
58
  # @param params [RelationshipParams, Hash] relationship parameters
58
59
  # @return [Relationship] new relationship instance
59
60
  def initialize(params)
61
+ super() # Initialize parent class
60
62
  params = RelationshipParams.new(params) if params.is_a?(Hash)
61
63
 
62
64
  @source_id = params.source_id.to_s
@@ -108,10 +110,18 @@ module Dbwatcher
108
110
  ERD_NOTATIONS[infer_cardinality] || DEFAULT_ERD_NOTATION
109
111
  end
110
112
 
111
- # Serialize relationship to hash
112
- #
113
- # @return [Hash] serialized relationship data
114
- def to_h
113
+ # Override base class method to handle simple hash initialization
114
+ def self.extract_constructor_args(hash)
115
+ hash
116
+ end
117
+
118
+ # Implementation for Base class
119
+ def comparable_attributes
120
+ [source_id, target_id, type, label, cardinality, metadata]
121
+ end
122
+
123
+ # Implementation for Base class
124
+ def serializable_attributes
115
125
  {
116
126
  source_id: source_id,
117
127
  target_id: target_id,
@@ -121,67 +131,6 @@ module Dbwatcher
121
131
  metadata: metadata
122
132
  }
123
133
  end
124
-
125
- # Serialize relationship to JSON
126
- #
127
- # @return [String] JSON representation
128
- def to_json(*args)
129
- to_h.to_json(*args)
130
- end
131
-
132
- # Create relationship from hash
133
- #
134
- # @param hash [Hash] relationship data
135
- # @return [Relationship] new relationship instance
136
- def self.from_h(hash)
137
- hash = hash.transform_keys(&:to_sym) if hash.keys.first.is_a?(String)
138
- new(hash)
139
- end
140
-
141
- # Create relationship from JSON
142
- #
143
- # @param json [String] JSON string
144
- # @return [Relationship] new relationship instance
145
- def self.from_json(json)
146
- from_h(JSON.parse(json))
147
- end
148
-
149
- # Check equality with another relationship
150
- #
151
- # @param other [Relationship] other relationship to compare
152
- # @return [Boolean] true if relationships are equal
153
- def ==(other)
154
- return false unless other.is_a?(Relationship)
155
-
156
- source_id == other.source_id &&
157
- target_id == other.target_id &&
158
- type == other.type &&
159
- label == other.label &&
160
- cardinality == other.cardinality &&
161
- metadata == other.metadata
162
- end
163
-
164
- # Generate hash code for relationship
165
- #
166
- # @return [Integer] hash code
167
- def hash
168
- [source_id, target_id, type, label, cardinality, metadata].hash
169
- end
170
-
171
- # String representation of relationship
172
- #
173
- # @return [String] string representation
174
- def to_s
175
- "#{self.class.name}(source: #{source_id}, target: #{target_id}, type: #{type})"
176
- end
177
-
178
- # Detailed string representation
179
- #
180
- # @return [String] detailed string representation
181
- def inspect
182
- "#{self.class.name}(source: #{source_id.inspect}, target: #{target_id.inspect}, " \
183
- "type: #{type.inspect}, label: #{label.inspect}, cardinality: #{cardinality.inspect})"
184
- end
185
134
  end
186
135
  end
187
136
  end
@@ -5,81 +5,58 @@ require_relative "diagram_type_registry"
5
5
 
6
6
  module Dbwatcher
7
7
  module Services
8
- # Orchestrator for diagram generation using strategy pattern
8
+ # Service for generating diagrams from session data
9
9
  #
10
- # This service coordinates diagram generation by delegating to appropriate
11
- # analyzer and strategy classes with clean error handling.
10
+ # Coordinates the process of generating diagrams by:
11
+ # 1. Loading session data
12
+ # 2. Using appropriate analyzers to extract relationships
13
+ # 3. Applying diagram generation strategies
12
14
  #
13
15
  # @example
14
- # generator = DiagramGenerator.new(session_id, 'database_tables')
16
+ # generator = DiagramGenerator.new(session_id: "abc123", diagram_type: "database_tables")
15
17
  # result = generator.call
16
- # # => { content: "erDiagram\n USERS ||--o{ ORDERS : user_id", type: 'erDiagram' }
17
- class DiagramGenerator < BaseService
18
- attr_reader :session_id, :diagram_type, :registry, :error_handler, :logger
18
+ # # => { success: true, content: "erDiagram\n...", type: "erDiagram" }
19
+ class DiagramGenerator
20
+ include Dbwatcher::Logging
19
21
 
20
- # Initialize with session id and diagram type
22
+ # Initialize generator with options
21
23
  #
22
- # @param session_id [String] session identifier
24
+ # @param session_id [String] session ID to analyze
23
25
  # @param diagram_type [String] type of diagram to generate
24
- # @param dependencies [Hash] optional dependency injection
25
- # @option dependencies [DiagramTypeRegistry] :registry type registry
26
- # @option dependencies [DiagramErrorHandler] :error_handler error handler
27
- # @option dependencies [Logger] :logger logger instance
28
- def initialize(session_id, diagram_type = "database_tables", dependencies = {})
26
+ # @param options [Hash] additional options
27
+ def initialize(session_id:, diagram_type:, options: {})
29
28
  @session_id = session_id
30
29
  @diagram_type = diagram_type
31
- @registry = dependencies[:registry] || DiagramTypeRegistry.new
32
- @error_handler = dependencies[:error_handler] || DiagramErrorHandler.new
33
- @logger = dependencies[:logger] || default_logger
34
- super()
30
+ @options = options
31
+ @registry = options[:registry] || DiagramTypeRegistry.new
32
+ @logger = options[:logger] || Rails.logger
35
33
  end
36
34
 
37
- # Generate diagram for session
35
+ # Generate diagram
38
36
  #
39
- # @return [Hash] diagram data with content and type
37
+ # @return [Hash] diagram generation result
40
38
  def call
41
- @logger.info("Generating diagram for session #{@session_id} with type #{@diagram_type}")
42
- start_time = Time.now
39
+ log_info("Generating diagram of type #{@diagram_type} for session #{@session_id}")
43
40
 
44
- begin
45
- result = generate_diagram
46
- log_completion(start_time, result)
47
- result
48
- rescue StandardError => e
49
- @error_handler.handle_generation_error(e, error_context)
50
- end
51
- end
41
+ start_time = Time.now
42
+ result = generate_diagram
52
43
 
53
- # Get available diagram types with metadata
54
- #
55
- # @return [Hash] diagram types with metadata
56
- def available_types
57
- @registry.available_types_with_metadata
58
- end
44
+ duration_ms = ((Time.now - start_time) * 1000).round(2)
45
+ log_info("Diagram generation completed in #{duration_ms}ms", {
46
+ session_id: @session_id,
47
+ diagram_type: @diagram_type,
48
+ success: result[:success]
49
+ })
59
50
 
60
- # Get available diagram types (class method for backward compatibility)
61
- #
62
- # @return [Hash] diagram types with metadata
63
- def self.available_types
64
- DiagramTypeRegistry.new.available_types_with_metadata
51
+ result
52
+ rescue StandardError => e
53
+ log_error("Diagram generation failed: #{e.message}", error_context)
54
+ error_result("Diagram generation failed: #{e.message}")
65
55
  end
66
56
 
67
57
  private
68
58
 
69
- # Default logger when no logger is provided
70
- #
71
- # @return [Logger] default logger instance
72
- def default_logger
73
- # Use Rails logger if available, otherwise create a simple logger
74
- if defined?(Rails) && Rails.respond_to?(:logger)
75
- Rails.logger
76
- else
77
- require "logger"
78
- Logger.new($stdout)
79
- end
80
- end
81
-
82
- # Generate diagram using standardized analyzer-to-strategy flow
59
+ # Generate diagram based on configuration
83
60
  #
84
61
  # @return [Hash] diagram generation result
85
62
  def generate_diagram
@@ -96,8 +73,8 @@ module Dbwatcher
96
73
  analyzer = @registry.create_analyzer(@diagram_type, session)
97
74
  dataset = analyzer.call
98
75
 
99
- @logger.debug("Generated dataset with #{dataset.entities.size} entities and " \
100
- "#{dataset.relationships.size} relationships")
76
+ log_debug("Generated dataset with #{dataset.entities.size} entities and " \
77
+ "#{dataset.relationships.size} relationships")
101
78
 
102
79
  # Create strategy and generate diagram from dataset
103
80
  strategy = @registry.create_strategy(@diagram_type)
@@ -110,7 +87,7 @@ module Dbwatcher
110
87
  def load_session
111
88
  Dbwatcher::Storage.sessions.find(@session_id)
112
89
  rescue StandardError => e
113
- @logger.warn("Could not load session #{@session_id}: #{e.message}")
90
+ log_warn("Could not load session #{@session_id}: #{e.message}")
114
91
  nil
115
92
  end
116
93
 
@@ -138,17 +115,6 @@ module Dbwatcher
138
115
  generated_at: Time.now.iso8601
139
116
  }
140
117
  end
141
-
142
- # Log generation completion
143
- #
144
- # @param start_time [Time] operation start time
145
- # @param result [Hash] generation result
146
- def log_completion(start_time, result)
147
- duration = Time.now - start_time
148
- success = result[:success] || false
149
- @logger.info("Diagram generation completed for session #{@session_id} type #{@diagram_type} " \
150
- "in #{(duration * 1000).round(2)}ms - Success: #{success}")
151
- end
152
118
  end
153
119
  end
154
120
  end