dbwatcher 1.1.3 → 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/controllers/dbwatcher/api/v1/sessions_controller.rb +14 -18
- data/lib/dbwatcher/configuration.rb +11 -0
- data/lib/dbwatcher/logging.rb +23 -1
- 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/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b14e37821dd10efdc21bfa6e9a68ff8ffea9096d616616893944aca66dd1c58
|
4
|
+
data.tar.gz: 983a92a4065533bdb7f15a0adeeefc3fd0a97109cf5b08a9c4adaef11bd829f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86071979a9e26aa6e0e5f289b4079804615e4477357dc894305d8eab047c328c9a1d97651e37889c913f073fc8f156abd410582adbe8581f68443f3d195fc5f4
|
7
|
+
data.tar.gz: '09bacda0cc050854b53d8567b46c791cfc440825598bb3c06300e2f4f572dd1f3cde1df0b127a332edd9a3995077ef7beae9bafc47cb7629478d2671a0d2691a'
|
@@ -8,32 +8,23 @@ module Dbwatcher
|
|
8
8
|
|
9
9
|
def tables_data
|
10
10
|
Rails.logger.info "API::V1::SessionsController#tables_data: Getting tables for session #{@session.id}"
|
11
|
-
|
12
|
-
# Paginated, filtered tables data
|
13
|
-
# Convert ActionController::Parameters to a hash before passing to service
|
14
|
-
service = Dbwatcher::Services::Api::TablesDataService.new(@session, filter_params.to_h)
|
11
|
+
service = Dbwatcher::Services::Api::TablesDataService.new(@session, tables_data_params)
|
15
12
|
render json: service.call
|
16
13
|
end
|
17
14
|
|
18
15
|
def summary_data
|
19
16
|
Rails.logger.info "API::V1::SessionsController#summary_data: Getting summary for session #{@session.id}"
|
20
|
-
|
21
|
-
# Aggregated summary statistics
|
22
17
|
service = Dbwatcher::Services::Api::SummaryDataService.new(@session)
|
23
18
|
render json: service.call
|
24
19
|
end
|
25
20
|
|
26
21
|
def diagram_data
|
27
22
|
Rails.logger.info "API::V1::SessionsController#diagram_data: Getting diagram for session #{@session.id}"
|
28
|
-
|
29
|
-
# Generated diagram content with caching
|
30
|
-
# Convert ActionController::Parameters to a hash before passing to service
|
31
|
-
diagram_params = params.to_unsafe_h
|
32
23
|
service = Dbwatcher::Services::Api::DiagramDataService.new(@session, params[:type], diagram_params)
|
33
24
|
result = service.call
|
34
25
|
|
35
26
|
if result[:error]
|
36
|
-
|
27
|
+
render_error(result[:error])
|
37
28
|
else
|
38
29
|
render json: result
|
39
30
|
end
|
@@ -41,13 +32,11 @@ module Dbwatcher
|
|
41
32
|
|
42
33
|
def timeline_data
|
43
34
|
Rails.logger.info "API::V1::SessionsController#timeline_data: Getting timeline for session #{@session.id}"
|
44
|
-
|
45
|
-
# Timeline data processed from session changes
|
46
35
|
service = Dbwatcher::Services::TimelineDataService.new(@session)
|
47
36
|
result = service.call
|
48
37
|
|
49
38
|
if result[:errors].any?
|
50
|
-
|
39
|
+
render_error(result[:errors].first[:message])
|
51
40
|
else
|
52
41
|
render json: result
|
53
42
|
end
|
@@ -55,7 +44,6 @@ module Dbwatcher
|
|
55
44
|
|
56
45
|
def diagram_types
|
57
46
|
Rails.logger.info "API::V1::SessionsController#diagram_types: Getting available diagram types"
|
58
|
-
|
59
47
|
render json: {
|
60
48
|
types: Dbwatcher::Services::Api::DiagramDataService.available_types_with_metadata,
|
61
49
|
default_type: "database_tables"
|
@@ -66,11 +54,19 @@ module Dbwatcher
|
|
66
54
|
|
67
55
|
def find_session
|
68
56
|
@session = Storage.sessions.find(params[:id])
|
69
|
-
|
57
|
+
render_error("Session not found", :not_found) unless @session
|
58
|
+
end
|
59
|
+
|
60
|
+
def tables_data_params
|
61
|
+
params.permit(:id, :table, :operation, :page, :per_page, session: {}).to_h
|
62
|
+
end
|
63
|
+
|
64
|
+
def diagram_params
|
65
|
+
params.permit(:type, :format, :include_columns, :show_relationships, session: {}).to_h
|
70
66
|
end
|
71
67
|
|
72
|
-
def
|
73
|
-
|
68
|
+
def render_error(message, status = :unprocessable_entity)
|
69
|
+
render json: { error: message }, status: status
|
74
70
|
end
|
75
71
|
end
|
76
72
|
end
|
@@ -26,6 +26,9 @@ module Dbwatcher
|
|
26
26
|
:collect_sensitive_env_vars, :system_info_cache_duration,
|
27
27
|
:system_info_include_performance_metrics
|
28
28
|
|
29
|
+
# Logging configuration
|
30
|
+
attr_accessor :debug_logging
|
31
|
+
|
29
32
|
# Initialize with default values
|
30
33
|
def initialize
|
31
34
|
# Storage configuration defaults
|
@@ -47,6 +50,9 @@ module Dbwatcher
|
|
47
50
|
|
48
51
|
# Initialize system information configuration with defaults
|
49
52
|
initialize_system_info_config
|
53
|
+
|
54
|
+
# Initialize logging configuration with defaults
|
55
|
+
initialize_logging_config
|
50
56
|
end
|
51
57
|
|
52
58
|
# Initialize diagram configuration with default values
|
@@ -73,6 +79,11 @@ module Dbwatcher
|
|
73
79
|
@system_info_include_performance_metrics = true
|
74
80
|
end
|
75
81
|
|
82
|
+
# Initialize logging configuration with default values
|
83
|
+
def initialize_logging_config
|
84
|
+
@debug_logging = false
|
85
|
+
end
|
86
|
+
|
76
87
|
# Validate configuration
|
77
88
|
#
|
78
89
|
# @return [Boolean] true if configuration is valid
|
data/lib/dbwatcher/logging.rb
CHANGED
@@ -14,9 +14,12 @@ module Dbwatcher
|
|
14
14
|
end
|
15
15
|
|
16
16
|
# Log a debug message with optional context
|
17
|
+
# Only logs if debug mode is enabled
|
17
18
|
# @param message [String] the log message
|
18
19
|
# @param context [Hash] additional context data
|
19
20
|
def log_debug(message, context = {})
|
21
|
+
return unless debug_enabled?
|
22
|
+
|
20
23
|
log_with_level(:debug, message, context)
|
21
24
|
end
|
22
25
|
|
@@ -34,6 +37,16 @@ module Dbwatcher
|
|
34
37
|
log_with_level(:error, message, context)
|
35
38
|
end
|
36
39
|
|
40
|
+
# Check if debug logging is enabled
|
41
|
+
# @return [Boolean] true if debug logging is enabled
|
42
|
+
def debug_enabled?
|
43
|
+
return Dbwatcher.configuration.debug_logging if defined?(Dbwatcher.configuration) &&
|
44
|
+
Dbwatcher.configuration.respond_to?(:debug_logging)
|
45
|
+
return Rails.env.development? if defined?(Rails)
|
46
|
+
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
37
50
|
private
|
38
51
|
|
39
52
|
def log_with_level(level, message, context)
|
@@ -51,7 +64,16 @@ module Dbwatcher
|
|
51
64
|
end
|
52
65
|
|
53
66
|
def component_name
|
54
|
-
|
67
|
+
if is_a?(Module) && !is_a?(Class)
|
68
|
+
# For modules
|
69
|
+
name.to_s.split("::").last
|
70
|
+
elsif self.class.name
|
71
|
+
# For classes
|
72
|
+
self.class.name.split("::").last
|
73
|
+
else
|
74
|
+
# Fallback
|
75
|
+
"Logger"
|
76
|
+
end
|
55
77
|
end
|
56
78
|
|
57
79
|
def rails_logger
|
@@ -232,6 +232,37 @@ module Dbwatcher
|
|
232
232
|
# @param primary_key [String, nil] optional primary key for testing
|
233
233
|
# @return [Boolean] true if likely self-referential
|
234
234
|
def self_referential_column?(column_name, table_name, primary_key = nil)
|
235
|
+
# Get the singular form of the table name
|
236
|
+
base_name = singularize(table_name)
|
237
|
+
|
238
|
+
# Special case for post_id in posts table - not a self-reference
|
239
|
+
return false if column_name == "#{base_name}_id" && table_name == "posts" && base_name == "post"
|
240
|
+
|
241
|
+
# Check primary key if this is a table-specific reference
|
242
|
+
if column_name == "#{base_name}_id"
|
243
|
+
if primary_key.nil?
|
244
|
+
begin
|
245
|
+
primary_key = connection.primary_key(table_name)
|
246
|
+
rescue StandardError
|
247
|
+
return false
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
return column_name != primary_key
|
252
|
+
end
|
253
|
+
|
254
|
+
# Use pattern matching to check various self-referential patterns
|
255
|
+
common_pattern?(column_name) ||
|
256
|
+
hierarchy_pattern?(column_name, base_name) ||
|
257
|
+
relationship_pattern?(column_name) ||
|
258
|
+
directional_pattern?(column_name)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Check if column matches common self-referential patterns
|
262
|
+
#
|
263
|
+
# @param column_name [String] column name to check
|
264
|
+
# @return [Boolean] true if matches common patterns
|
265
|
+
def common_pattern?(column_name)
|
235
266
|
# Common self-referential patterns
|
236
267
|
self_ref_patterns = %w[
|
237
268
|
parent_id
|
@@ -257,52 +288,47 @@ module Dbwatcher
|
|
257
288
|
replied_to_id
|
258
289
|
]
|
259
290
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
# Get the singular form of the table name
|
264
|
-
base_name = singularize(table_name)
|
265
|
-
|
266
|
-
# Special case for post_id in posts table - not a self-reference
|
267
|
-
return false if column_name == "#{base_name}_id" && table_name == "posts" && base_name == "post"
|
268
|
-
|
269
|
-
# Check for table-specific self-references (e.g., comment_id in comments table)
|
270
|
-
if column_name == "#{base_name}_id"
|
271
|
-
# Check if this is not the primary key column
|
272
|
-
if primary_key.nil?
|
273
|
-
begin
|
274
|
-
primary_key = connection.primary_key(table_name)
|
275
|
-
rescue StandardError
|
276
|
-
return false
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
return column_name != primary_key
|
281
|
-
end
|
291
|
+
self_ref_patterns.include?(column_name)
|
292
|
+
end
|
282
293
|
|
283
|
-
|
294
|
+
# Check for hierarchy patterns with table name
|
295
|
+
#
|
296
|
+
# @param column_name [String] column name to check
|
297
|
+
# @param base_name [String] singular form of table name
|
298
|
+
# @return [Boolean] true if matches hierarchy patterns
|
299
|
+
def hierarchy_pattern?(column_name, base_name)
|
284
300
|
hierarchy_prefixes = %w[parent child ancestor descendant superior subordinate manager supervisor]
|
285
|
-
hierarchy_prefixes.each do |prefix|
|
286
|
-
# Check for patterns like parent_comment_id in comments table
|
287
|
-
return true if column_name.start_with?("#{prefix}_#{base_name}_id")
|
288
301
|
|
289
|
-
|
290
|
-
|
302
|
+
hierarchy_prefixes.any? do |prefix|
|
303
|
+
# Check for patterns like parent_comment_id in comments table
|
304
|
+
column_name.start_with?("#{prefix}_#{base_name}_id") ||
|
305
|
+
# Check for patterns like parent_of_id in any table
|
306
|
+
column_name.start_with?("#{prefix}_of_id")
|
291
307
|
end
|
308
|
+
end
|
292
309
|
|
293
|
-
|
310
|
+
# Check for relationship patterns
|
311
|
+
#
|
312
|
+
# @param column_name [String] column name to check
|
313
|
+
# @return [Boolean] true if matches relationship patterns
|
314
|
+
def relationship_pattern?(column_name)
|
294
315
|
relationship_patterns = %w[related linked connected associated referenced]
|
295
|
-
|
296
|
-
|
316
|
+
|
317
|
+
relationship_patterns.any? do |pattern|
|
318
|
+
column_name.start_with?("#{pattern}_")
|
297
319
|
end
|
320
|
+
end
|
298
321
|
|
299
|
-
|
322
|
+
# Check for directional patterns
|
323
|
+
#
|
324
|
+
# @param column_name [String] column name to check
|
325
|
+
# @return [Boolean] true if matches directional patterns
|
326
|
+
def directional_pattern?(column_name)
|
300
327
|
directional_patterns = %w[previous next original copy source target]
|
301
|
-
directional_patterns.each do |pattern|
|
302
|
-
return true if column_name.start_with?("#{pattern}_")
|
303
|
-
end
|
304
328
|
|
305
|
-
|
329
|
+
directional_patterns.any? do |pattern|
|
330
|
+
column_name.start_with?("#{pattern}_")
|
331
|
+
end
|
306
332
|
end
|
307
333
|
|
308
334
|
# Analyze junction tables (many-to-many relationships)
|
@@ -5,81 +5,58 @@ require_relative "diagram_type_registry"
|
|
5
5
|
|
6
6
|
module Dbwatcher
|
7
7
|
module Services
|
8
|
-
#
|
8
|
+
# Service for generating diagrams from session data
|
9
9
|
#
|
10
|
-
#
|
11
|
-
#
|
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,
|
16
|
+
# generator = DiagramGenerator.new(session_id: "abc123", diagram_type: "database_tables")
|
15
17
|
# result = generator.call
|
16
|
-
# # => { content: "erDiagram\n
|
17
|
-
class DiagramGenerator
|
18
|
-
|
18
|
+
# # => { success: true, content: "erDiagram\n...", type: "erDiagram" }
|
19
|
+
class DiagramGenerator
|
20
|
+
include Dbwatcher::Logging
|
19
21
|
|
20
|
-
# Initialize with
|
22
|
+
# Initialize generator with options
|
21
23
|
#
|
22
|
-
# @param session_id [String] session
|
24
|
+
# @param session_id [String] session ID to analyze
|
23
25
|
# @param diagram_type [String] type of diagram to generate
|
24
|
-
# @param
|
25
|
-
|
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
|
-
@
|
32
|
-
@
|
33
|
-
@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
|
35
|
+
# Generate diagram
|
38
36
|
#
|
39
|
-
# @return [Hash] diagram
|
37
|
+
# @return [Hash] diagram generation result
|
40
38
|
def call
|
41
|
-
|
42
|
-
start_time = Time.now
|
39
|
+
log_info("Generating diagram of type #{@diagram_type} for session #{@session_id}")
|
43
40
|
|
44
|
-
|
45
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
#
|
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
|
-
|
100
|
-
|
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
|
-
|
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
|
@@ -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
|
@@ -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) }
|
data/lib/dbwatcher/version.rb
CHANGED
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
|
@@ -266,8 +266,10 @@ files:
|
|
266
266
|
- lib/dbwatcher/services/diagram_generator.rb
|
267
267
|
- lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb
|
268
268
|
- lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb
|
269
|
+
- lib/dbwatcher/services/diagram_strategies/diagram_strategy_helpers.rb
|
269
270
|
- lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb
|
270
271
|
- lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb
|
272
|
+
- lib/dbwatcher/services/diagram_strategies/standard_diagram_strategy.rb
|
271
273
|
- lib/dbwatcher/services/diagram_system.rb
|
272
274
|
- lib/dbwatcher/services/diagram_type_registry.rb
|
273
275
|
- lib/dbwatcher/services/mermaid_syntax/base_builder.rb
|