dbwatcher 1.0.0 → 1.1.1
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 +81 -210
- data/app/assets/config/dbwatcher_manifest.js +15 -0
- data/app/assets/javascripts/dbwatcher/alpine_registrations.js +39 -0
- data/app/assets/javascripts/dbwatcher/auto_init.js +23 -0
- data/app/assets/javascripts/dbwatcher/components/base.js +141 -0
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +1008 -0
- data/app/assets/javascripts/dbwatcher/components/diagrams.js +449 -0
- data/app/assets/javascripts/dbwatcher/components/summary.js +234 -0
- data/app/assets/javascripts/dbwatcher/core/alpine_store.js +138 -0
- data/app/assets/javascripts/dbwatcher/core/api_client.js +162 -0
- data/app/assets/javascripts/dbwatcher/core/component_loader.js +70 -0
- data/app/assets/javascripts/dbwatcher/core/component_registry.js +94 -0
- data/app/assets/javascripts/dbwatcher/dbwatcher.js +120 -0
- data/app/assets/javascripts/dbwatcher/services/mermaid.js +315 -0
- data/app/assets/javascripts/dbwatcher/services/mermaid_service.js +199 -0
- data/app/assets/javascripts/dbwatcher/vendor/date-fns-browser.js +99 -0
- data/app/assets/javascripts/dbwatcher/vendor/lodash.min.js +140 -0
- data/app/assets/javascripts/dbwatcher/vendor/tabulator.min.js +3 -0
- data/app/assets/stylesheets/dbwatcher/application.css +423 -0
- data/app/assets/stylesheets/dbwatcher/application.scss +15 -0
- data/app/assets/stylesheets/dbwatcher/components/_badges.scss +38 -0
- data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +162 -0
- data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +51 -0
- data/app/assets/stylesheets/dbwatcher/components/_forms.scss +27 -0
- data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +55 -0
- data/app/assets/stylesheets/dbwatcher/core/_base.scss +34 -0
- data/app/assets/stylesheets/dbwatcher/core/_variables.scss +47 -0
- data/app/assets/stylesheets/dbwatcher/vendor/tabulator.min.css +2 -0
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +64 -0
- data/app/controllers/dbwatcher/base_controller.rb +8 -2
- data/app/controllers/dbwatcher/dashboard_controller.rb +8 -0
- data/app/controllers/dbwatcher/sessions_controller.rb +25 -10
- data/app/helpers/dbwatcher/component_helper.rb +29 -0
- data/app/helpers/dbwatcher/diagram_helper.rb +110 -0
- data/app/helpers/dbwatcher/session_helper.rb +3 -2
- data/app/views/dbwatcher/sessions/_changes_tab.html.erb +265 -0
- data/app/views/dbwatcher/sessions/_diagrams_tab.html.erb +166 -0
- data/app/views/dbwatcher/sessions/_session_header.html.erb +11 -0
- data/app/views/dbwatcher/sessions/_summary_tab.html.erb +88 -0
- data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +12 -0
- data/app/views/dbwatcher/sessions/changes.html.erb +21 -0
- data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +44 -0
- data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +96 -0
- data/app/views/dbwatcher/sessions/diagrams.html.erb +21 -0
- data/app/views/dbwatcher/sessions/index.html.erb +14 -10
- data/app/views/dbwatcher/sessions/shared/_layout.html.erb +8 -0
- data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +35 -0
- data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +25 -0
- data/app/views/dbwatcher/sessions/show.html.erb +3 -346
- data/app/views/dbwatcher/sessions/summary.html.erb +21 -0
- data/app/views/layouts/dbwatcher/application.html.erb +125 -247
- data/bin/compile_scss +49 -0
- data/config/routes.rb +26 -0
- data/lib/dbwatcher/configuration.rb +102 -8
- data/lib/dbwatcher/engine.rb +17 -7
- data/lib/dbwatcher/services/analyzers/session_data_processor.rb +98 -0
- data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +202 -0
- data/lib/dbwatcher/services/api/base_api_service.rb +100 -0
- data/lib/dbwatcher/services/api/changes_data_service.rb +112 -0
- data/lib/dbwatcher/services/api/diagram_data_service.rb +145 -0
- data/lib/dbwatcher/services/api/summary_data_service.rb +158 -0
- data/lib/dbwatcher/services/base_service.rb +64 -0
- data/lib/dbwatcher/services/diagram_analyzers/base_analyzer.rb +162 -0
- data/lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb +354 -0
- data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +502 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +603 -0
- data/lib/dbwatcher/services/diagram_data/attribute.rb +154 -0
- data/lib/dbwatcher/services/diagram_data/dataset.rb +280 -0
- data/lib/dbwatcher/services/diagram_data/entity.rb +180 -0
- data/lib/dbwatcher/services/diagram_data/relationship.rb +188 -0
- data/lib/dbwatcher/services/diagram_data/relationship_params.rb +55 -0
- data/lib/dbwatcher/services/diagram_data.rb +65 -0
- data/lib/dbwatcher/services/diagram_error_handler.rb +239 -0
- data/lib/dbwatcher/services/diagram_generator.rb +154 -0
- data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +149 -0
- data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +49 -0
- data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +52 -0
- data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +52 -0
- data/lib/dbwatcher/services/diagram_system.rb +69 -0
- data/lib/dbwatcher/services/diagram_type_registry.rb +164 -0
- data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +127 -0
- data/lib/dbwatcher/services/mermaid_syntax/cardinality_mapper.rb +90 -0
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_builder.rb +140 -0
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_helper.rb +48 -0
- data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +116 -0
- data/lib/dbwatcher/services/mermaid_syntax/flowchart_builder.rb +109 -0
- data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +118 -0
- data/lib/dbwatcher/services/mermaid_syntax_builder.rb +155 -0
- data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +15 -128
- data/lib/dbwatcher/storage/api/session_api.rb +47 -0
- data/lib/dbwatcher/storage/base_storage.rb +7 -0
- data/lib/dbwatcher/version.rb +1 -1
- data/lib/dbwatcher.rb +58 -1
- metadata +94 -2
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Services
|
5
|
+
module DiagramStrategies
|
6
|
+
# Abstract base class for diagram generation strategies
|
7
|
+
#
|
8
|
+
# Defines the common interface and shared behavior for all diagram generation
|
9
|
+
# strategies. Subclasses must implement the render_diagram method.
|
10
|
+
class BaseDiagramStrategy
|
11
|
+
attr_reader :syntax_builder, :logger
|
12
|
+
|
13
|
+
# Initialize strategy with dependencies
|
14
|
+
#
|
15
|
+
# @param dependencies [Hash] injected dependencies
|
16
|
+
# @option dependencies [Object] :syntax_builder Mermaid syntax builder
|
17
|
+
# @option dependencies [Logger] :logger logger instance
|
18
|
+
def initialize(dependencies = {})
|
19
|
+
@syntax_builder = dependencies[:syntax_builder] || create_default_syntax_builder
|
20
|
+
@logger = dependencies[:logger] || Rails.logger || Logger.new($stdout)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generate diagram from standardized dataset
|
24
|
+
#
|
25
|
+
# @param dataset [Dataset] standardized dataset
|
26
|
+
# @return [Hash] diagram generation result
|
27
|
+
def generate_from_dataset(dataset)
|
28
|
+
@logger.info("Generating diagram from dataset with #{dataset.entities.size} entities and " \
|
29
|
+
"#{dataset.relationships.size} relationships")
|
30
|
+
start_time = Time.current
|
31
|
+
|
32
|
+
begin
|
33
|
+
# Generate diagram using dataset
|
34
|
+
result = render_diagram(dataset)
|
35
|
+
|
36
|
+
log_operation_completion("diagram generation", Time.current - start_time, {
|
37
|
+
entities_count: dataset.entities.size,
|
38
|
+
relationships_count: dataset.relationships.size
|
39
|
+
})
|
40
|
+
|
41
|
+
result
|
42
|
+
rescue StandardError => e
|
43
|
+
@logger.error("Diagram generation failed: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
|
44
|
+
error_response("Diagram generation failed: #{e.message}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get strategy metadata (abstract method)
|
49
|
+
#
|
50
|
+
# @return [Hash] strategy metadata including name, description
|
51
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
52
|
+
def metadata
|
53
|
+
{
|
54
|
+
name: strategy_name,
|
55
|
+
description: strategy_description,
|
56
|
+
mermaid_type: mermaid_diagram_type
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
# Render diagram from dataset (abstract method)
|
63
|
+
#
|
64
|
+
# @param dataset [Dataset] standardized dataset
|
65
|
+
# @return [Hash] diagram generation result
|
66
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
67
|
+
def render_diagram(dataset)
|
68
|
+
raise NotImplementedError, "Subclasses must implement render_diagram method"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Build empty diagram with message
|
72
|
+
#
|
73
|
+
# @param message [String] message to display in empty diagram
|
74
|
+
# @param diagram_type [String] type of empty diagram to create
|
75
|
+
# @return [Hash] empty diagram result
|
76
|
+
def build_empty_diagram(message, diagram_type = nil)
|
77
|
+
diagram_type ||= mermaid_diagram_type
|
78
|
+
content = @syntax_builder.build_empty_diagram(message, diagram_type)
|
79
|
+
success_response(content, diagram_type)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Create success response
|
83
|
+
#
|
84
|
+
# @param content [String] diagram content
|
85
|
+
# @param type [String] diagram type
|
86
|
+
# @return [Hash] success response
|
87
|
+
def success_response(content, type)
|
88
|
+
{
|
89
|
+
success: true,
|
90
|
+
content: content,
|
91
|
+
type: type,
|
92
|
+
generated_at: Time.current,
|
93
|
+
metadata: {
|
94
|
+
strategy: self.class.name
|
95
|
+
}
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
# Create error response
|
100
|
+
#
|
101
|
+
# @param message [String] error message
|
102
|
+
# @return [Hash] error response
|
103
|
+
def error_response(message)
|
104
|
+
{
|
105
|
+
success: false,
|
106
|
+
error: message,
|
107
|
+
content: nil,
|
108
|
+
type: nil,
|
109
|
+
generated_at: Time.current,
|
110
|
+
metadata: {
|
111
|
+
strategy: self.class.name
|
112
|
+
}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
# Log operation completion
|
117
|
+
#
|
118
|
+
# @param operation [String] operation name
|
119
|
+
# @param duration [Float] operation duration in seconds
|
120
|
+
# @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")
|
124
|
+
end
|
125
|
+
|
126
|
+
# Create default syntax builder
|
127
|
+
#
|
128
|
+
# @return [MermaidSyntaxBuilder] syntax builder instance
|
129
|
+
def create_default_syntax_builder
|
130
|
+
Dbwatcher::Services::MermaidSyntaxBuilder.new
|
131
|
+
end
|
132
|
+
|
133
|
+
# Strategy metadata methods (abstract)
|
134
|
+
|
135
|
+
def strategy_name
|
136
|
+
raise NotImplementedError, "Subclasses must implement strategy_name"
|
137
|
+
end
|
138
|
+
|
139
|
+
def strategy_description
|
140
|
+
raise NotImplementedError, "Subclasses must implement strategy_description"
|
141
|
+
end
|
142
|
+
|
143
|
+
def mermaid_diagram_type
|
144
|
+
raise NotImplementedError, "Subclasses must implement mermaid_diagram_type"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Services
|
5
|
+
module DiagramStrategies
|
6
|
+
# Strategy for generating class diagrams from model associations
|
7
|
+
#
|
8
|
+
# Handles class diagram generation by converting dataset entities and relationships
|
9
|
+
# 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
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Strategy metadata methods
|
34
|
+
|
35
|
+
def strategy_name
|
36
|
+
"Model Associations (Class Diagram)"
|
37
|
+
end
|
38
|
+
|
39
|
+
def strategy_description
|
40
|
+
"Class diagram showing ActiveRecord model relationships and methods"
|
41
|
+
end
|
42
|
+
|
43
|
+
def mermaid_diagram_type
|
44
|
+
"classDiagram"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Services
|
5
|
+
module DiagramStrategies
|
6
|
+
# Strategy for generating Entity Relationship Diagrams (ERD)
|
7
|
+
#
|
8
|
+
# Handles ERD diagram generation by converting dataset entities and relationships
|
9
|
+
# 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
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Strategy metadata methods
|
37
|
+
|
38
|
+
def strategy_name
|
39
|
+
"Database Schema (ERD)"
|
40
|
+
end
|
41
|
+
|
42
|
+
def strategy_description
|
43
|
+
"Entity relationship diagram showing database tables and foreign key relationships"
|
44
|
+
end
|
45
|
+
|
46
|
+
def mermaid_diagram_type
|
47
|
+
"erDiagram"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Services
|
5
|
+
module DiagramStrategies
|
6
|
+
# Strategy for generating flowchart diagrams from model associations
|
7
|
+
#
|
8
|
+
# Handles flowchart diagram generation by converting dataset entities and relationships
|
9
|
+
# 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
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Strategy metadata methods
|
37
|
+
|
38
|
+
def strategy_name
|
39
|
+
"Model Associations"
|
40
|
+
end
|
41
|
+
|
42
|
+
def strategy_description
|
43
|
+
"Flowchart diagram showing ActiveRecord model relationships and associations"
|
44
|
+
end
|
45
|
+
|
46
|
+
def mermaid_diagram_type
|
47
|
+
"flowchart"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Diagram System Components
|
4
|
+
# This file centralizes all diagram-related requires for better organization
|
5
|
+
|
6
|
+
# Core diagram data structures
|
7
|
+
require_relative "diagram_data"
|
8
|
+
|
9
|
+
# Error handling
|
10
|
+
require_relative "diagram_error_handler"
|
11
|
+
|
12
|
+
# Registry and type management
|
13
|
+
require_relative "diagram_type_registry"
|
14
|
+
|
15
|
+
# Mermaid syntax generation
|
16
|
+
require_relative "mermaid_syntax_builder"
|
17
|
+
require_relative "mermaid_syntax/base_builder"
|
18
|
+
require_relative "mermaid_syntax/sanitizer"
|
19
|
+
require_relative "mermaid_syntax/cardinality_mapper"
|
20
|
+
require_relative "mermaid_syntax/erd_builder"
|
21
|
+
require_relative "mermaid_syntax/class_diagram_builder"
|
22
|
+
require_relative "mermaid_syntax/flowchart_builder"
|
23
|
+
|
24
|
+
# Diagram analyzers
|
25
|
+
require_relative "diagram_analyzers/base_analyzer"
|
26
|
+
require_relative "diagram_analyzers/foreign_key_analyzer"
|
27
|
+
require_relative "diagram_analyzers/inferred_relationship_analyzer"
|
28
|
+
require_relative "diagram_analyzers/model_association_analyzer"
|
29
|
+
|
30
|
+
# Diagram strategies
|
31
|
+
require_relative "diagram_strategies/base_diagram_strategy"
|
32
|
+
require_relative "diagram_strategies/erd_diagram_strategy"
|
33
|
+
require_relative "diagram_strategies/class_diagram_strategy"
|
34
|
+
require_relative "diagram_strategies/flowchart_diagram_strategy"
|
35
|
+
|
36
|
+
# Diagram generator (must be loaded after all components)
|
37
|
+
require_relative "diagram_generator"
|
38
|
+
|
39
|
+
module Dbwatcher
|
40
|
+
module Services
|
41
|
+
# Diagram System Module
|
42
|
+
# Provides centralized access to diagram generation capabilities
|
43
|
+
module DiagramSystem
|
44
|
+
# Get available diagram types
|
45
|
+
#
|
46
|
+
# @return [Array<String>] available diagram type names
|
47
|
+
def self.available_types
|
48
|
+
DiagramTypeRegistry.new.available_types
|
49
|
+
end
|
50
|
+
|
51
|
+
# Generate diagram for session
|
52
|
+
#
|
53
|
+
# @param session_id [String] session identifier
|
54
|
+
# @param diagram_type [String] type of diagram to generate
|
55
|
+
# @return [Hash] diagram generation result
|
56
|
+
def self.generate(session_id, diagram_type = "database_tables")
|
57
|
+
DiagramGenerator.new(session_id, diagram_type).call
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check if diagram type is supported
|
61
|
+
#
|
62
|
+
# @param diagram_type [String] diagram type to check
|
63
|
+
# @return [Boolean] true if supported
|
64
|
+
def self.supports?(diagram_type)
|
65
|
+
DiagramTypeRegistry.new.type_exists?(diagram_type)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Services
|
5
|
+
# Registry for managing diagram types and creating strategy instances
|
6
|
+
#
|
7
|
+
# Provides a central registry for all available diagram types with metadata,
|
8
|
+
# and factory methods for creating strategy instances.
|
9
|
+
class DiagramTypeRegistry
|
10
|
+
# Error classes for registry operations
|
11
|
+
class UnknownTypeError < StandardError; end
|
12
|
+
|
13
|
+
# Built-in diagram types with essential metadata
|
14
|
+
DIAGRAM_TYPES = {
|
15
|
+
"database_tables" => {
|
16
|
+
strategy_class: "Dbwatcher::Services::DiagramStrategies::ErdDiagramStrategy",
|
17
|
+
analyzer_class: "Dbwatcher::Services::DiagramAnalyzers::ForeignKeyAnalyzer",
|
18
|
+
display_name: "Database Schema",
|
19
|
+
description: "Entity relationship diagram showing database tables and foreign key relationships",
|
20
|
+
mermaid_type: "erDiagram"
|
21
|
+
},
|
22
|
+
"database_tables_inferred" => {
|
23
|
+
strategy_class: "Dbwatcher::Services::DiagramStrategies::ErdDiagramStrategy",
|
24
|
+
analyzer_class: "Dbwatcher::Services::DiagramAnalyzers::InferredRelationshipAnalyzer",
|
25
|
+
display_name: "Database Schema (Inferred)",
|
26
|
+
description: "Entity relationship diagram with inferred relationships from naming patterns",
|
27
|
+
mermaid_type: "erDiagram"
|
28
|
+
},
|
29
|
+
"model_associations" => {
|
30
|
+
strategy_class: "Dbwatcher::Services::DiagramStrategies::ClassDiagramStrategy",
|
31
|
+
analyzer_class: "Dbwatcher::Services::DiagramAnalyzers::ModelAssociationAnalyzer",
|
32
|
+
display_name: "Model Associations",
|
33
|
+
description: "Class diagram showing ActiveRecord models with attributes and methods",
|
34
|
+
mermaid_type: "classDiagram"
|
35
|
+
},
|
36
|
+
"model_associations_flowchart" => {
|
37
|
+
strategy_class: "Dbwatcher::Services::DiagramStrategies::FlowchartDiagramStrategy",
|
38
|
+
analyzer_class: "Dbwatcher::Services::DiagramAnalyzers::ModelAssociationAnalyzer",
|
39
|
+
display_name: "Model Associations (Flowchart)",
|
40
|
+
description: "Flowchart diagram showing model relationships",
|
41
|
+
mermaid_type: "flowchart"
|
42
|
+
}
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
# Initialize registry
|
46
|
+
def initialize
|
47
|
+
@logger = if defined?(Rails) && Rails.respond_to?(:logger)
|
48
|
+
Rails.logger
|
49
|
+
else
|
50
|
+
require "logger"
|
51
|
+
Logger.new($stdout)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get list of available diagram type keys
|
56
|
+
#
|
57
|
+
# @return [Array<String>] diagram type keys
|
58
|
+
def available_types
|
59
|
+
DIAGRAM_TYPES.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get available types with metadata
|
63
|
+
#
|
64
|
+
# @return [Hash] diagram types with metadata
|
65
|
+
def available_types_with_metadata
|
66
|
+
DIAGRAM_TYPES.transform_values do |type_config|
|
67
|
+
type_config.except(:strategy_class, :analyzer_class)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create strategy instance for given type
|
72
|
+
#
|
73
|
+
# @param type [String] diagram type key
|
74
|
+
# @param dependencies [Hash] optional dependencies to inject
|
75
|
+
# @return [Object] strategy instance
|
76
|
+
# @raise [UnknownTypeError] if type is unknown
|
77
|
+
def create_strategy(type, dependencies = {})
|
78
|
+
type_config = find_type_config(type)
|
79
|
+
strategy_class = resolve_strategy_class(type_config[:strategy_class])
|
80
|
+
|
81
|
+
@logger.debug("Creating strategy for type #{type}: #{strategy_class.name}")
|
82
|
+
strategy_class.new(dependencies)
|
83
|
+
rescue StandardError => e
|
84
|
+
raise UnknownTypeError, "Cannot create strategy for type '#{type}': #{e.message}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Create analyzer instance for given type
|
88
|
+
#
|
89
|
+
# @param type [String] diagram type key
|
90
|
+
# @param session [Object] session object
|
91
|
+
# @return [Object] analyzer instance
|
92
|
+
# @raise [UnknownTypeError] if type is unknown
|
93
|
+
def create_analyzer(type, session)
|
94
|
+
type_config = find_type_config(type)
|
95
|
+
analyzer_class = resolve_analyzer_class(type_config[:analyzer_class])
|
96
|
+
|
97
|
+
@logger.debug("Creating analyzer for type #{type}: #{analyzer_class.name}")
|
98
|
+
analyzer_class.new(session)
|
99
|
+
rescue StandardError => e
|
100
|
+
raise UnknownTypeError, "Cannot create analyzer for type '#{type}': #{e.message}"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if a diagram type exists
|
104
|
+
#
|
105
|
+
# @param type [String] diagram type key
|
106
|
+
# @return [Boolean] true if type exists
|
107
|
+
def type_exists?(type)
|
108
|
+
DIAGRAM_TYPES.key?(type)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Get metadata for a specific diagram type
|
112
|
+
#
|
113
|
+
# @param type [String] diagram type key
|
114
|
+
# @return [Hash] type metadata
|
115
|
+
# @raise [UnknownTypeError] if type is unknown
|
116
|
+
def type_metadata(type)
|
117
|
+
type_config = find_type_config(type)
|
118
|
+
type_config.except(:strategy_class, :analyzer_class)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
# Find type configuration by key
|
124
|
+
#
|
125
|
+
# @param type [String] diagram type key
|
126
|
+
# @return [Hash] type configuration
|
127
|
+
# @raise [UnknownTypeError] if type not found
|
128
|
+
def find_type_config(type)
|
129
|
+
type_config = DIAGRAM_TYPES[type]
|
130
|
+
raise UnknownTypeError, "Unknown diagram type: #{type}" unless type_config
|
131
|
+
|
132
|
+
type_config
|
133
|
+
end
|
134
|
+
|
135
|
+
# Resolve strategy class from string or class
|
136
|
+
#
|
137
|
+
# @param strategy_class [String, Class] strategy class reference
|
138
|
+
# @return [Class] resolved strategy class
|
139
|
+
def resolve_strategy_class(strategy_class)
|
140
|
+
if strategy_class.is_a?(String)
|
141
|
+
strategy_class.constantize
|
142
|
+
else
|
143
|
+
strategy_class
|
144
|
+
end
|
145
|
+
rescue NameError => e
|
146
|
+
raise UnknownTypeError, "Cannot resolve strategy class: #{e.message}"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Resolve analyzer class from string or class
|
150
|
+
#
|
151
|
+
# @param analyzer_class [String, Class] analyzer class reference
|
152
|
+
# @return [Class] resolved analyzer class
|
153
|
+
def resolve_analyzer_class(analyzer_class)
|
154
|
+
if analyzer_class.is_a?(String)
|
155
|
+
analyzer_class.constantize
|
156
|
+
else
|
157
|
+
analyzer_class
|
158
|
+
end
|
159
|
+
rescue NameError => e
|
160
|
+
raise UnknownTypeError, "Cannot resolve analyzer class: #{e.message}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Services
|
5
|
+
module MermaidSyntax
|
6
|
+
# Base class for Mermaid syntax builders
|
7
|
+
#
|
8
|
+
# Provides common functionality and configuration options for all Mermaid
|
9
|
+
# diagram builders. Subclasses should implement the build_from_dataset method.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# class MyDiagramBuilder < BaseBuilder
|
13
|
+
# def build_from_dataset(dataset)
|
14
|
+
# # Implementation
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
class BaseBuilder
|
18
|
+
# Initialize a new builder with configuration
|
19
|
+
#
|
20
|
+
# @param config [Hash] configuration options
|
21
|
+
def initialize(config = {})
|
22
|
+
@config = default_config.merge(config)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Build diagram content from dataset
|
26
|
+
#
|
27
|
+
# @param dataset [DiagramData::Dataset] dataset to render
|
28
|
+
# @return [String] Mermaid diagram content
|
29
|
+
def build_from_dataset(dataset)
|
30
|
+
raise NotImplementedError, "Subclasses must implement build_from_dataset"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Build empty diagram with message
|
34
|
+
#
|
35
|
+
# @param message [String] message to display
|
36
|
+
# @return [String] Mermaid diagram content
|
37
|
+
def build_empty(message)
|
38
|
+
raise NotImplementedError, "Subclasses must implement build_empty"
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
# Check if attributes should be shown
|
44
|
+
#
|
45
|
+
# @return [Boolean] true if attributes should be shown
|
46
|
+
def show_attributes?
|
47
|
+
@config[:show_attributes] == true
|
48
|
+
end
|
49
|
+
|
50
|
+
# Check if methods should be shown
|
51
|
+
#
|
52
|
+
# @return [Boolean] true if methods should be shown
|
53
|
+
def show_methods?
|
54
|
+
@config[:show_methods] == true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check if cardinality should be shown
|
58
|
+
#
|
59
|
+
# @return [Boolean] true if cardinality should be shown
|
60
|
+
def show_cardinality?
|
61
|
+
@config[:show_cardinality] != false
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get maximum number of attributes to show
|
65
|
+
#
|
66
|
+
# @return [Integer] maximum number of attributes
|
67
|
+
def max_attributes
|
68
|
+
@config[:max_attributes] || 10
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get maximum number of methods to show
|
72
|
+
#
|
73
|
+
# @return [Integer] maximum number of methods
|
74
|
+
def max_methods
|
75
|
+
@config[:max_methods] || 5
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get diagram direction
|
79
|
+
#
|
80
|
+
# @return [String] diagram direction (TD, LR, etc.)
|
81
|
+
def diagram_direction
|
82
|
+
@config[:direction] || "LR"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Check if table case should be preserved
|
86
|
+
#
|
87
|
+
# @return [Boolean] true if table case should be preserved
|
88
|
+
def preserve_table_case?
|
89
|
+
@config[:preserve_table_case] != false
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get cardinality format
|
93
|
+
#
|
94
|
+
# @return [Symbol] cardinality format (:standard or :simple)
|
95
|
+
def cardinality_format
|
96
|
+
@config[:cardinality_format] || :simple
|
97
|
+
end
|
98
|
+
|
99
|
+
# Sanitize text for Mermaid
|
100
|
+
#
|
101
|
+
# @param text [String] text to sanitize
|
102
|
+
# @return [String] sanitized text
|
103
|
+
def sanitize_text(text)
|
104
|
+
return "" unless text
|
105
|
+
|
106
|
+
text.to_s.gsub(/["\n\r]/, " ").strip
|
107
|
+
end
|
108
|
+
|
109
|
+
# Default configuration options
|
110
|
+
#
|
111
|
+
# @return [Hash] default configuration
|
112
|
+
def default_config
|
113
|
+
{
|
114
|
+
show_attributes: true,
|
115
|
+
show_methods: false,
|
116
|
+
show_cardinality: true,
|
117
|
+
max_attributes: 10,
|
118
|
+
max_methods: 5,
|
119
|
+
direction: "LR",
|
120
|
+
preserve_table_case: true,
|
121
|
+
cardinality_format: :simple
|
122
|
+
}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|