dbdoc_engine 0.1.0
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 +7 -0
- data/README.md +331 -0
- data/Rakefile +8 -0
- data/app/assets/builds/dbdoc_engine/application.css +5 -0
- data/app/assets/images/dbdoc_engine/arrowdown.svg +3 -0
- data/app/assets/images/dbdoc_engine/arrowhorizontal.svg +3 -0
- data/app/assets/images/dbdoc_engine/arrowleft.svg +3 -0
- data/app/assets/images/dbdoc_engine/changelog.svg +3 -0
- data/app/assets/images/dbdoc_engine/column_stats_dbdocs.svg +23 -0
- data/app/assets/images/dbdoc_engine/diagram.svg +3 -0
- data/app/assets/images/dbdoc_engine/double_arrow.svg +4 -0
- data/app/assets/images/dbdoc_engine/group_bu.svg +3 -0
- data/app/assets/images/dbdoc_engine/japan_circle.png +0 -0
- data/app/assets/images/dbdoc_engine/log_in_image.png +0 -0
- data/app/assets/images/dbdoc_engine/logo.svg +12 -0
- data/app/assets/images/dbdoc_engine/orange_changelog.svg +3 -0
- data/app/assets/images/dbdoc_engine/orange_fields.svg +23 -0
- data/app/assets/images/dbdoc_engine/orange_logo.svg +12 -0
- data/app/assets/images/dbdoc_engine/orange_table.svg +21 -0
- data/app/assets/images/dbdoc_engine/orange_updates.svg +43 -0
- data/app/assets/images/dbdoc_engine/orange_wiki.svg +3 -0
- data/app/assets/images/dbdoc_engine/search.svg +3 -0
- data/app/assets/images/dbdoc_engine/setting.svg +3 -0
- data/app/assets/images/dbdoc_engine/table_dbdocs.svg +21 -0
- data/app/assets/images/dbdoc_engine/uk_circle_transparent.png +0 -0
- data/app/assets/images/dbdoc_engine/update_stats_dbdocs.svg +43 -0
- data/app/assets/images/dbdoc_engine/wiki.svg +3 -0
- data/app/assets/stylesheets/dbdoc_engine/admin.css +176 -0
- data/app/assets/stylesheets/dbdoc_engine/admin_header.css +179 -0
- data/app/assets/stylesheets/dbdoc_engine/application.scss +1 -0
- data/app/assets/stylesheets/dbdoc_engine/changelog.css +173 -0
- data/app/assets/stylesheets/dbdoc_engine/dashboard.css +513 -0
- data/app/assets/stylesheets/dbdoc_engine/dbdoc_application.css +117 -0
- data/app/assets/stylesheets/dbdoc_engine/ecommerce.css +253 -0
- data/app/assets/stylesheets/dbdoc_engine/group_details.css +178 -0
- data/app/assets/stylesheets/dbdoc_engine/header.css +212 -0
- data/app/assets/stylesheets/dbdoc_engine/loading_spinner.css +127 -0
- data/app/assets/stylesheets/dbdoc_engine/login.css +213 -0
- data/app/assets/stylesheets/dbdoc_engine/schema_diagram.css +149 -0
- data/app/assets/stylesheets/dbdoc_engine/sidebar.css +296 -0
- data/app/assets/stylesheets/dbdoc_engine/table_details.css +417 -0
- data/app/controllers/dbdoc_engine/admin/base_controller.rb +23 -0
- data/app/controllers/dbdoc_engine/admin/dashboard_controller.rb +16 -0
- data/app/controllers/dbdoc_engine/admin/data_transfer_controller.rb +63 -0
- data/app/controllers/dbdoc_engine/admin/db_design_dynamic_tables_controller.rb +198 -0
- data/app/controllers/dbdoc_engine/admin/db_design_table_groups_controller.rb +107 -0
- data/app/controllers/dbdoc_engine/application_controller.rb +65 -0
- data/app/controllers/dbdoc_engine/concerns/internationalization.rb +57 -0
- data/app/controllers/dbdoc_engine/db_doc_sessions_controller.rb +33 -0
- data/app/controllers/dbdoc_engine/home_controller.rb +79 -0
- data/app/controllers/dbdoc_engine/schema_diagram_controller.rb +293 -0
- data/app/helper/dbdoc_engine/application_helper.rb +35 -0
- data/app/helpers/dbdoc_engine/application_helper.rb +4 -0
- data/app/helpers/dbdoc_engine/changelogs_helper.rb +27 -0
- data/app/helpers/dbdoc_engine/column_helper.rb +30 -0
- data/app/helpers/dbdoc_engine/db_design_dynamic_tables_helper.rb +15 -0
- data/app/helpers/dbdoc_engine/home_helper.rb +75 -0
- data/app/javascript/dbdoc_engine/application.js +12 -0
- data/app/javascript/dbdoc_engine/controllers/application.js +29 -0
- data/app/javascript/dbdoc_engine/controllers/auto_submit_controller.js +17 -0
- data/app/javascript/dbdoc_engine/controllers/chart_controller.js +58 -0
- data/app/javascript/dbdoc_engine/controllers/column-type_controller.js +149 -0
- data/app/javascript/dbdoc_engine/controllers/column_controller.js +362 -0
- data/app/javascript/dbdoc_engine/controllers/column_search_controller.js +42 -0
- data/app/javascript/dbdoc_engine/controllers/dbdoc_accordion_controller.js +42 -0
- data/app/javascript/dbdoc_engine/controllers/ecommerce_controller.js +73 -0
- data/app/javascript/dbdoc_engine/controllers/group_details_controller.js +88 -0
- data/app/javascript/dbdoc_engine/controllers/import_export_controller.js +200 -0
- data/app/javascript/dbdoc_engine/controllers/index.js +9 -0
- data/app/javascript/dbdoc_engine/controllers/language_controller.js +100 -0
- data/app/javascript/dbdoc_engine/controllers/loading_spinner_controller.js +48 -0
- data/app/javascript/dbdoc_engine/controllers/login_controller.js +75 -0
- data/app/javascript/dbdoc_engine/controllers/notification_controller.js +15 -0
- data/app/javascript/dbdoc_engine/controllers/schema_diagram_controller.js +1129 -0
- data/app/javascript/dbdoc_engine/controllers/select2_controller.js +67 -0
- data/app/javascript/dbdoc_engine/controllers/sidebar_controller.js +943 -0
- data/app/javascript/dbdoc_engine/controllers/table_details_controller.js +245 -0
- data/app/javascript/dbdoc_engine/controllers/table_group_validation_controller.js +148 -0
- data/app/javascript/dbdoc_engine/controllers/table_validation_controller.js +423 -0
- data/app/jobs/dbdoc_engine/application_job.rb +4 -0
- data/app/mailers/dbdoc_engine/application_mailer.rb +6 -0
- data/app/models/dbdoc_engine/application_record.rb +6 -0
- data/app/models/dbdoc_engine/concerns/soft_deletable.rb +30 -0
- data/app/models/dbdoc_engine/db_design_changelog.rb +44 -0
- data/app/models/dbdoc_engine/db_design_dynamic_column.rb +211 -0
- data/app/models/dbdoc_engine/db_design_dynamic_table.rb +124 -0
- data/app/models/dbdoc_engine/db_design_table_group.rb +88 -0
- data/app/models/dbdoc_engine/user.rb +21 -0
- data/app/queries/dbdoc_engine/admin_dashboard_queries.rb +71 -0
- data/app/queries/dbdoc_engine/db_design_changelog_queries.rb +68 -0
- data/app/queries/dbdoc_engine/db_design_dynamic_column_queries.rb +37 -0
- data/app/queries/dbdoc_engine/db_design_dynamic_table_commands.rb +106 -0
- data/app/queries/dbdoc_engine/db_design_dynamic_table_queries.rb +194 -0
- data/app/queries/dbdoc_engine/db_design_table_group_queries.rb +154 -0
- data/app/services/dbdoc_engine/db_design_dynamic_table_export_service.rb +38 -0
- data/app/services/dbdoc_engine/db_design_dynamic_table_handler_service.rb +49 -0
- data/app/services/dbdoc_engine/db_design_dynamic_tables_service.rb +21 -0
- data/app/services/dbdoc_engine/error_handler_service.rb +43 -0
- data/app/services/dbdoc_engine/schema_rb_import_service.rb +194 -0
- data/app/services/dbdoc_engine/schema_rb_parser_service.rb +339 -0
- data/app/services/dbdoc_engine/table_filter_service.rb +35 -0
- data/app/services/dbdoc_engine/table_groups_service.rb +199 -0
- data/app/services/dbdoc_engine/table_management_service.rb +192 -0
- data/app/views/dbdoc_engine/admin/dashboard/_action_badge.html.erb +11 -0
- data/app/views/dbdoc_engine/admin/dashboard/_changelog_rows.html.erb +22 -0
- data/app/views/dbdoc_engine/admin/dashboard/_changelog_table_headers.html.erb +8 -0
- data/app/views/dbdoc_engine/admin/dashboard/_filter_fields.html.erb +43 -0
- data/app/views/dbdoc_engine/admin/dashboard/index.html.erb +159 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_column_fields.html.erb +225 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_deleted_table_index.html.erb +110 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_foreign_key_fields.html.erb +51 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_form.html.erb +75 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_recent_activity.html.erb +39 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_columns.html.erb +127 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_index.html.erb +109 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_information.html.erb +99 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/deleted_tables.html.erb +95 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/edit.html.erb +23 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_all_to_excel.xlsx.axlsx +240 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_to_excel.xlsx.axlsx +135 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/index.html.erb +109 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/new.html.erb +25 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/show_table_info.html.erb +125 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/_deleted_table_groups_list.html.erb +75 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/_form.html.erb +88 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/_table_groups_list.html.erb +82 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/deleted_groups.html.erb +60 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/edit.html.erb +25 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/index.html.erb +85 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/new.html.erb +26 -0
- data/app/views/dbdoc_engine/db_doc_sessions/new.html.erb +59 -0
- data/app/views/dbdoc_engine/home/changelog_details.html.erb +80 -0
- data/app/views/dbdoc_engine/home/changelogs.html.erb +20 -0
- data/app/views/dbdoc_engine/home/group_details.html.erb +94 -0
- data/app/views/dbdoc_engine/home/index.html.erb +11 -0
- data/app/views/dbdoc_engine/home/partials/_action_badge.html.erb +11 -0
- data/app/views/dbdoc_engine/home/partials/_breadcrumb_navigation.html.erb +30 -0
- data/app/views/dbdoc_engine/home/partials/_changelog_rows.html.erb +35 -0
- data/app/views/dbdoc_engine/home/partials/_changelog_table_headers.html.erb +16 -0
- data/app/views/dbdoc_engine/home/partials/_column_headers.html.erb +23 -0
- data/app/views/dbdoc_engine/home/partials/_column_row.html.erb +157 -0
- data/app/views/dbdoc_engine/home/partials/_filter_form.html.erb +47 -0
- data/app/views/dbdoc_engine/home/partials/_group_section.html.erb +84 -0
- data/app/views/dbdoc_engine/home/partials/_pagination.html.erb +5 -0
- data/app/views/dbdoc_engine/home/partials/_stats_container.html.erb +46 -0
- data/app/views/dbdoc_engine/home/partials/_table_groups.html.erb +7 -0
- data/app/views/dbdoc_engine/home/partials/_table_information_section.html.erb +50 -0
- data/app/views/dbdoc_engine/home/partials/_table_section.html.erb +48 -0
- data/app/views/dbdoc_engine/home/table_details.html.erb +9 -0
- data/app/views/dbdoc_engine/schema_diagram/index.html.erb +102 -0
- data/app/views/dbdoc_engine/shared/_admin_header.html.erb +78 -0
- data/app/views/dbdoc_engine/shared/_header.html.erb +94 -0
- data/app/views/dbdoc_engine/shared/_js_translations.html.erb +3 -0
- data/app/views/dbdoc_engine/shared/_language_button.html.erb +14 -0
- data/app/views/dbdoc_engine/shared/_sidebar.html.erb +128 -0
- data/app/views/kaminari/dbdoc_engine/_first_page.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_gap.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_last_page.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_next_page.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_page.html.erb +9 -0
- data/app/views/kaminari/dbdoc_engine/_paginator.html.erb +17 -0
- data/app/views/kaminari/dbdoc_engine/_prev_page.html.erb +3 -0
- data/app/views/layouts/dbdoc_engine/application.html.erb +107 -0
- data/app/views/layouts/dbdoc_engine/header.html.erb +108 -0
- data/config/importmap.rb +11 -0
- data/config/locales/en.yml +307 -0
- data/config/locales/ja.yml +306 -0
- data/config/routes.rb +73 -0
- data/db/migrate/rails7/20250227060610_create_db_design_table_groups.rb +15 -0
- data/db/migrate/rails7/20250227094626_create_db_design_dynamic_tables.rb +19 -0
- data/db/migrate/rails7/20250228022732_create_db_design_dynamic_columns.rb +34 -0
- data/db/migrate/rails7/20250401051453_create_db_design_changelogs.rb +26 -0
- data/db/migrate/rails7/20250411040822_create_users.rb +14 -0
- data/db/migrate/rails7/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
- data/db/migrate/rails8/20250227060610_create_db_design_table_groups.rb +15 -0
- data/db/migrate/rails8/20250227094626_create_db_design_dynamic_tables.rb +19 -0
- data/db/migrate/rails8/20250228022732_create_db_design_dynamic_columns.rb +34 -0
- data/db/migrate/rails8/20250401051453_create_db_design_changelogs.rb +26 -0
- data/db/migrate/rails8/20250411040822_create_users.rb +14 -0
- data/db/migrate/rails8/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
- data/db/seeds.rb +28 -0
- data/lib/dbdoc_engine/engine.rb +57 -0
- data/lib/dbdoc_engine/version.rb +3 -0
- data/lib/dbdoc_engine.rb +9 -0
- data/lib/generators/dbdoc_engine/install/install_generator.rb +245 -0
- data/lib/generators/dbdoc_engine/uninstall/uninstall_generator.rb +196 -0
- data/lib/tasks/dbdoc_engine_tasks.rake +44 -0
- data/public/dbdoc_engine_assets/images/camel_chess_head.png +0 -0
- data/public/dbdoc_engine_assets/images/dblogo.svg +4 -0
- data/public/dbdoc_engine_assets/images/japan_circle.png +0 -0
- data/public/dbdoc_engine_assets/images/king_chess_head.png +0 -0
- data/public/dbdoc_engine_assets/images/login-bg.svg +44 -0
- data/public/dbdoc_engine_assets/images/logo.png +0 -0
- data/public/dbdoc_engine_assets/images/logo.svg +12 -0
- data/public/dbdoc_engine_assets/images/queen_chess_head.png +0 -0
- data/public/dbdoc_engine_assets/images/soldier_chess_headd.png +0 -0
- data/public/dbdoc_engine_assets/images/uk_circle_transparent.png +0 -0
- metadata +415 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# app/controllers/dbdoc_engine/schema_diagram_controller.rb
|
|
2
|
+
module DbdocEngine
|
|
3
|
+
class SchemaDiagramController < ApplicationController
|
|
4
|
+
layout "dbdoc_engine/header"
|
|
5
|
+
|
|
6
|
+
# Skip CSRF protection for the data endpoint to allow AJAX calls
|
|
7
|
+
skip_before_action :verify_authenticity_token, only: [ :data ]
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@groups = DbdocEngine::DbDesignTableGroupQueries.groups_with_tables_having_columns
|
|
11
|
+
|
|
12
|
+
@foreign_keys = DbdocEngine::DbDesignDynamicColumnQueries.all_foreign_keys_with_tables
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def data
|
|
16
|
+
@tables = DbDesignDynamicTableQueries.active_tables_with_columns
|
|
17
|
+
.includes(:db_design_dynamic_columns, :db_design_table_group)
|
|
18
|
+
|
|
19
|
+
@foreign_keys = DbdocEngine::DbDesignDynamicColumnQueries.all_foreign_keys_with_tables
|
|
20
|
+
.includes(db_design_dynamic_table: :db_design_dynamic_columns)
|
|
21
|
+
@existing_table_names = DbDesignDynamicTable.where(deleted_at: nil).pluck(:table_name).to_set
|
|
22
|
+
|
|
23
|
+
dot_code = generate_graphviz_dot(@tables)
|
|
24
|
+
svg = render_graphviz_svg(dot_code)
|
|
25
|
+
|
|
26
|
+
selected_group_id = params[:group_id].presence if params[:group_id] != "all"
|
|
27
|
+
@group_colors = {}
|
|
28
|
+
|
|
29
|
+
@tables.each do |table|
|
|
30
|
+
group_color = table.db_design_table_group&.group_color || "#cccccc"
|
|
31
|
+
if selected_group_id.nil? || table.db_design_table_group_id.to_s == selected_group_id
|
|
32
|
+
@group_colors[table.table_name] = group_color
|
|
33
|
+
else
|
|
34
|
+
@group_colors[table.table_name] = "#808080"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@column_data = generate_column_data(@tables)
|
|
39
|
+
@relationship_data = generate_relationship_data
|
|
40
|
+
|
|
41
|
+
respond_to do |format|
|
|
42
|
+
format.json do
|
|
43
|
+
render json: {
|
|
44
|
+
diagram_svg: svg,
|
|
45
|
+
group_colors: @group_colors,
|
|
46
|
+
column_data: @column_data,
|
|
47
|
+
relationship_data: @relationship_data
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def render_graphviz_svg(dot_code)
|
|
54
|
+
require "open3"
|
|
55
|
+
svg = nil
|
|
56
|
+
Open3.popen3("dot -Tsvg") do |stdin, stdout, stderr, wait_thr|
|
|
57
|
+
stdin.puts dot_code
|
|
58
|
+
stdin.close
|
|
59
|
+
svg = stdout.read
|
|
60
|
+
raise "Graphviz error: #{stderr.read}" if wait_thr.value != 0
|
|
61
|
+
end
|
|
62
|
+
svg
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def lighten_color(hex, amount = 0.7)
|
|
66
|
+
r = hex[1..2].to_i(16)
|
|
67
|
+
g = hex[3..4].to_i(16)
|
|
68
|
+
b = hex[5..6].to_i(16)
|
|
69
|
+
r = [ (r + (255 - r) * amount), 255 ].min.round
|
|
70
|
+
g = [ (g + (255 - g) * amount), 255 ].min.round
|
|
71
|
+
b = [ (b + (255 - b) * amount), 255 ].min.round
|
|
72
|
+
"#%02X%02X%02X" % [ r, g, b ]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def generate_graphviz_dot(tables)
|
|
78
|
+
selected_group_id = params[:group_id].presence if params[:group_id] != "all"
|
|
79
|
+
|
|
80
|
+
dot = <<~DOT
|
|
81
|
+
digraph G {
|
|
82
|
+
graph [overlap=false rankdir=LR ratio=auto splines=true fontname="Arial" fontsize=10 compound=true]
|
|
83
|
+
node [fontname="Arial" fontsize=10 shape=plain margin=0]
|
|
84
|
+
edge [fontname="Arial" fontsize=8]
|
|
85
|
+
DOT
|
|
86
|
+
|
|
87
|
+
table_colors = {}
|
|
88
|
+
|
|
89
|
+
# Generate table nodes
|
|
90
|
+
tables.each do |table|
|
|
91
|
+
group = table.db_design_table_group
|
|
92
|
+
color = if selected_group_id.nil? || table.db_design_table_group_id.to_s == selected_group_id
|
|
93
|
+
group&.group_color || "#cccccc"
|
|
94
|
+
else
|
|
95
|
+
"#808080"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
table_colors[table.table_name] = color
|
|
99
|
+
|
|
100
|
+
dot << generate_table_node(table, color)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Generate edges with better labeling and grouping
|
|
104
|
+
dot << generate_relationship_edges(tables, table_colors)
|
|
105
|
+
|
|
106
|
+
dot << "}\n"
|
|
107
|
+
dot
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def generate_table_node(table, color)
|
|
111
|
+
node_content = <<~TABLE
|
|
112
|
+
"#{escape_dot_string(table.table_name)}" [id="#{escape_dot_string(table.table_name)}" label=<
|
|
113
|
+
<table border="1" cellborder="0" cellspacing="0" cellpadding="8" COLOR="#{color}">
|
|
114
|
+
<tr>
|
|
115
|
+
<td bgcolor="#{color}" port="header" colspan="2" align="center">
|
|
116
|
+
<font point-size="12" color="white"><b>#{escape_html(table.table_name)}</b></font>
|
|
117
|
+
</td>
|
|
118
|
+
</tr>
|
|
119
|
+
TABLE
|
|
120
|
+
|
|
121
|
+
table.db_design_dynamic_columns.each_with_index do |column, i|
|
|
122
|
+
port_name = "port_#{i}"
|
|
123
|
+
attributes = []
|
|
124
|
+
attributes << "🔑" if column.is_primary_key
|
|
125
|
+
attributes << "🔗" if column.is_foreign_key
|
|
126
|
+
attributes << "NN" if column.not_null
|
|
127
|
+
|
|
128
|
+
node_content << <<~COLUMN
|
|
129
|
+
<tr>
|
|
130
|
+
<td port="#{port_name}" align="left">
|
|
131
|
+
#{escape_html(column.column_name)} #{attributes.join(' ')}
|
|
132
|
+
</td>
|
|
133
|
+
<td align="right">
|
|
134
|
+
<i>#{escape_html(column.data_type)}</i>
|
|
135
|
+
</td>
|
|
136
|
+
</tr>
|
|
137
|
+
COLUMN
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
node_content << "</table> >];\n"
|
|
141
|
+
node_content
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def generate_relationship_edges(tables, table_colors)
|
|
145
|
+
edges_content = ""
|
|
146
|
+
|
|
147
|
+
@foreign_keys.each do |fk|
|
|
148
|
+
next unless @existing_table_names.include?(fk.foreign_table_name)
|
|
149
|
+
|
|
150
|
+
source_table = escape_dot_string(fk.foreign_table_name)
|
|
151
|
+
target_table = escape_dot_string(fk.db_design_dynamic_table.table_name)
|
|
152
|
+
target_column_index = fk.db_design_dynamic_table.db_design_dynamic_columns
|
|
153
|
+
.find_index { |c| c.column_name == fk.column_name }
|
|
154
|
+
next unless target_column_index
|
|
155
|
+
|
|
156
|
+
target_port = "port_#{target_column_index}"
|
|
157
|
+
source_port = "header"
|
|
158
|
+
color = table_colors[fk.db_design_dynamic_table.table_name] || "#cccccc"
|
|
159
|
+
edge_attributes = get_edge_attributes(fk.relationship_type)
|
|
160
|
+
edge_id = "edge_#{fk.foreign_table_name}_#{fk.db_design_dynamic_table.table_name}_#{fk.column_name}"
|
|
161
|
+
edge_title = "#{fk.foreign_table_name}:#{fk.foreign_column_name || 'id'}->#{fk.db_design_dynamic_table.table_name}:#{fk.column_name}"
|
|
162
|
+
relationship_label = create_relationship_label(fk)
|
|
163
|
+
|
|
164
|
+
edges_content << <<~EDGE
|
|
165
|
+
"#{source_table}":#{source_port} -> "#{target_table}":#{target_port} [
|
|
166
|
+
id="#{escape_dot_string(edge_id)}",
|
|
167
|
+
#{edge_attributes},
|
|
168
|
+
color="#{color}",
|
|
169
|
+
penwidth=1.5,
|
|
170
|
+
label="#{relationship_label}",
|
|
171
|
+
labeltooltip="#{escape_dot_string(edge_title)}",
|
|
172
|
+
tooltip="#{escape_dot_string(edge_title)}"
|
|
173
|
+
];
|
|
174
|
+
EDGE
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
edges_content
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def get_edge_attributes(relationship_type)
|
|
181
|
+
case relationship_type&.to_s
|
|
182
|
+
when "one_to_one"
|
|
183
|
+
"arrowhead=tee, arrowtail=tee, dir=both, style=solid"
|
|
184
|
+
when "one_to_many"
|
|
185
|
+
"arrowhead=crow, arrowtail=tee, dir=both, style=solid"
|
|
186
|
+
when "many_to_one"
|
|
187
|
+
"arrowhead=tee, arrowtail=crow, dir=both, style=solid"
|
|
188
|
+
when "many_to_many"
|
|
189
|
+
"arrowhead=crow, arrowtail=crow, dir=both, style=solid"
|
|
190
|
+
else
|
|
191
|
+
"arrowhead=normal, arrowtail=none, dir=forward, style=solid"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def create_relationship_label(fk)
|
|
196
|
+
# Create a concise label for the relationship
|
|
197
|
+
case fk.relationship_type&.to_s
|
|
198
|
+
when "one_to_one"
|
|
199
|
+
"1:1"
|
|
200
|
+
when "one_to_many"
|
|
201
|
+
"1:N"
|
|
202
|
+
when "many_to_one"
|
|
203
|
+
"N:1"
|
|
204
|
+
when "many_to_many"
|
|
205
|
+
"N:N"
|
|
206
|
+
else
|
|
207
|
+
fk.foreign_column_name || "ref"
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def escape_dot_string(str)
|
|
212
|
+
return "" if str.nil?
|
|
213
|
+
str.to_s.gsub(/["\\]/, '\\\\\\&')
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def escape_html(str)
|
|
217
|
+
return "" if str.nil?
|
|
218
|
+
str.to_s.gsub(/[&<>"']/, {
|
|
219
|
+
"&" => "&",
|
|
220
|
+
"<" => "<",
|
|
221
|
+
">" => ">",
|
|
222
|
+
'"' => """,
|
|
223
|
+
"'" => "'"
|
|
224
|
+
})
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def generate_column_data(tables)
|
|
228
|
+
column_data = {}
|
|
229
|
+
|
|
230
|
+
tables.each do |table|
|
|
231
|
+
column_data[table.table_name] = {}
|
|
232
|
+
|
|
233
|
+
table.db_design_dynamic_columns.each do |column|
|
|
234
|
+
column_data[table.table_name][column.column_name] = {
|
|
235
|
+
name: column.column_name,
|
|
236
|
+
data_type: column.data_type.to_s,
|
|
237
|
+
description: column.description || "No description available",
|
|
238
|
+
is_primary_key: column.is_primary_key,
|
|
239
|
+
is_foreign_key: column.is_foreign_key,
|
|
240
|
+
not_null: column.not_null,
|
|
241
|
+
is_unique_key: column.is_unique_key,
|
|
242
|
+
is_candidate_key: column.is_candidate_key,
|
|
243
|
+
is_indexed: column.is_indexed,
|
|
244
|
+
foreign_table_name: column.foreign_table_name,
|
|
245
|
+
foreign_column_name: column.foreign_column_name,
|
|
246
|
+
relationship_type: column.relationship_type
|
|
247
|
+
}
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
column_data
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def generate_relationship_data
|
|
255
|
+
relationships = {}
|
|
256
|
+
|
|
257
|
+
@foreign_keys.each do |fk|
|
|
258
|
+
next unless @existing_table_names.include?(fk.foreign_table_name)
|
|
259
|
+
|
|
260
|
+
edge_id = "edge_#{fk.foreign_table_name}_#{fk.db_design_dynamic_table.table_name}_#{fk.column_name}"
|
|
261
|
+
|
|
262
|
+
relationships[edge_id] = {
|
|
263
|
+
id: edge_id,
|
|
264
|
+
source_table: fk.foreign_table_name,
|
|
265
|
+
source_column: fk.foreign_column_name || "id",
|
|
266
|
+
target_table: fk.db_design_dynamic_table.table_name,
|
|
267
|
+
target_column: fk.column_name,
|
|
268
|
+
relationship_type: fk.relationship_type || "many_to_one",
|
|
269
|
+
description: generate_relationship_description(fk)
|
|
270
|
+
}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
relationships
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def generate_relationship_description(fk)
|
|
277
|
+
type_description = case fk.relationship_type&.to_s
|
|
278
|
+
when "one_to_one"
|
|
279
|
+
"One-to-One"
|
|
280
|
+
when "one_to_many"
|
|
281
|
+
"One-to-Many"
|
|
282
|
+
when "many_to_one"
|
|
283
|
+
"Many-to-One"
|
|
284
|
+
when "many_to_many"
|
|
285
|
+
"Many-to-Many"
|
|
286
|
+
else
|
|
287
|
+
"Foreign Key"
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
"#{type_description} relationship between #{fk.foreign_table_name} and #{fk.db_design_dynamic_table.table_name}"
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# This module provides helper methods for views across the application.
|
|
2
|
+
# It includes methods for handling JavaScript translations for client-side validation.
|
|
3
|
+
module DbdocEngine
|
|
4
|
+
module ApplicationHelper
|
|
5
|
+
# Returns a JSON object containing localized error messages for JavaScript validation
|
|
6
|
+
def js_translations
|
|
7
|
+
{
|
|
8
|
+
group_name_required: I18n.t("dbdoc.group_name_required"),
|
|
9
|
+
group_name_unique: I18n.t("dbdoc.group_name_unique"),
|
|
10
|
+
group_color_required: I18n.t("dbdoc.group_color_required"),
|
|
11
|
+
table_name_required: I18n.t("dbdoc.table_name_required"),
|
|
12
|
+
physical_table_name_required: I18n.t("dbdoc.physical_table_name_required"),
|
|
13
|
+
table_group_must_be_selected: I18n.t("dbdoc.table_group_must_be_selected"),
|
|
14
|
+
created_by_field_required: I18n.t("dbdoc.created_by_field_required"),
|
|
15
|
+
column_name_required: I18n.t("dbdoc.column_name_required"),
|
|
16
|
+
physical_column_name_required: I18n.t("dbdoc.physical_column_name_required"),
|
|
17
|
+
data_type_must_be_selected: I18n.t("dbdoc.data_type_must_be_selected"),
|
|
18
|
+
length_is_required_for_this_data_type: I18n.t("dbdoc.length_is_required_for_this_data_type"),
|
|
19
|
+
referenced_table_is_required_for_foreign_key: I18n.t("dbdoc.referenced_table_is_required_for_foreign_key"),
|
|
20
|
+
referenced_column_is_required_for_foreign_key: I18n.t("dbdoc.referenced_column_is_required_for_foreign_key"),
|
|
21
|
+
relationship_type_is_required_for_foreign_key: I18n.t("dbdoc.relationship_type_is_required_for_foreign_key"),
|
|
22
|
+
created_by_required: I18n.t("dbdoc.created_by_required"),
|
|
23
|
+
duplicate_columns: I18n.t("dbdoc.duplicate_columns"),
|
|
24
|
+
table_name_already_exists: I18n.t("dbdoc.table_name_already_exists"),
|
|
25
|
+
updated_by_required: I18n.t("dbdoc.updated_by_required"),
|
|
26
|
+
username_required: I18n.t("dbdoc.username_required"),
|
|
27
|
+
password_required: I18n.t("dbdoc.password_required")
|
|
28
|
+
}.to_json.html_safe
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def admin_nav_active?(controller_name)
|
|
32
|
+
controller_path == "dbdoc_engine/admin/#{controller_name}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# app/helpers/changelogs_helper.rb
|
|
2
|
+
# Provides helper methods for changelog-related views and forms
|
|
3
|
+
module DbdocEngine
|
|
4
|
+
module ChangelogsHelper
|
|
5
|
+
# Returns an array of options for the entity type select dropdown
|
|
6
|
+
# Used for filtering changelogs by entity type
|
|
7
|
+
def entity_type_options
|
|
8
|
+
[
|
|
9
|
+
['All Types', ''], # Option to show all entity types
|
|
10
|
+
['Table Group', 'table_group'],
|
|
11
|
+
['Table', 'table'],
|
|
12
|
+
['Column', 'column']
|
|
13
|
+
]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns an array of options for the action type select dropdown
|
|
17
|
+
# Used for filtering changelogs by action type
|
|
18
|
+
def action_type_options
|
|
19
|
+
[
|
|
20
|
+
['All Actions', ''], # Option to show all action types
|
|
21
|
+
['Create', 'create'],
|
|
22
|
+
['Update', 'update'],
|
|
23
|
+
['Delete', 'delete']
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# app/helpers/column_helper.rb
|
|
2
|
+
module DbdocEngine
|
|
3
|
+
module ColumnHelper
|
|
4
|
+
def data_type_select(form, selected_value = nil)
|
|
5
|
+
# Define the available PostgreSQL data types
|
|
6
|
+
data_types = [
|
|
7
|
+
"integer", "smallint", "bigint", "numeric", "real", "double_precision",
|
|
8
|
+
"varchar", "character", "text", "char", "boolean", "date", "timestamp",
|
|
9
|
+
"timestamptz", "time", "timetz", "interval", "uuid", "json", "jsonb"
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
# Generate the select field with select2 controller
|
|
13
|
+
content_tag(:div, data: {
|
|
14
|
+
controller: "select2",
|
|
15
|
+
select2_placeholder_value: t("dbdoc.select_data_type")
|
|
16
|
+
}) do
|
|
17
|
+
form.select :data_type,
|
|
18
|
+
options_for_select(data_types, selected_value),
|
|
19
|
+
{ prompt: t("dbdoc.select_data_type") },
|
|
20
|
+
class: "form-select #{form.object.errors[:data_type].any? ? 'is-invalid' : ''}",
|
|
21
|
+
data: {
|
|
22
|
+
column_type_target: "dataType",
|
|
23
|
+
select2_target: "select",
|
|
24
|
+
action: "change->column-type#updateLength change->column#updateAccordionTitle",
|
|
25
|
+
error_target: "dataType"
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# app/helpers/db_design_dynamic_tables_helper.rb
|
|
2
|
+
module DbdocEngine
|
|
3
|
+
module DbDesignDynamicTablesHelper
|
|
4
|
+
# Returns a list of options for group filter dropdown
|
|
5
|
+
# @param groups [Array<DbDesignTableGroup>] Collection of table groups
|
|
6
|
+
# @param selected [String, Integer] The currently selected group ID
|
|
7
|
+
# @return [Array] Options array for select dropdown
|
|
8
|
+
def group_filter_options(groups, selected = nil)
|
|
9
|
+
options_for_select(
|
|
10
|
+
[ [ I18n.t("dbdoc.all_groups"), "" ] ] + groups.map { |g| [ g.group_name, g.id ] },
|
|
11
|
+
selected: selected
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# This module provides helper methods for the home view.
|
|
2
|
+
# It includes methods for formatting action badges, entity types, table row styles, and array counts.
|
|
3
|
+
module DbdocEngine
|
|
4
|
+
module HomeHelper
|
|
5
|
+
# Returns a badge element with appropriate styling based on the action type.
|
|
6
|
+
def format_action_badge(action_type)
|
|
7
|
+
badge_classes = 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium'
|
|
8
|
+
|
|
9
|
+
case action_type
|
|
10
|
+
when 'create'
|
|
11
|
+
content_tag(:span, 'Create', class: "#{badge_classes} bg-green-100 text-green-800")
|
|
12
|
+
when 'update'
|
|
13
|
+
content_tag(:span, 'Update', class: "#{badge_classes} bg-blue-100 text-blue-800")
|
|
14
|
+
when 'delete'
|
|
15
|
+
content_tag(:span, 'Delete', class: "#{badge_classes} bg-red-100 text-red-800")
|
|
16
|
+
else
|
|
17
|
+
content_tag(:span, action_type.capitalize, class: "#{badge_classes} bg-gray-100 text-gray-800")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Formats the entity type into a human-readable format.
|
|
22
|
+
def format_entity_type(entity_type)
|
|
23
|
+
case entity_type
|
|
24
|
+
when 'table_group'
|
|
25
|
+
'Table Group'
|
|
26
|
+
when 'table'
|
|
27
|
+
'Table'
|
|
28
|
+
when 'column'
|
|
29
|
+
'Column'
|
|
30
|
+
else
|
|
31
|
+
entity_type.capitalize
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Uses .size to leverage eager-loaded associations when available
|
|
36
|
+
def column_count(table)
|
|
37
|
+
table.db_design_dynamic_columns.size
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Converts a hex color (e.g., "#829ED4") to an RGBA string with transparency
|
|
41
|
+
def hex_to_rgba(hex_color, alpha = 0.3)
|
|
42
|
+
rgb_values = hex_color.delete('#').scan(/../).map { |component| component.to_i(16) }
|
|
43
|
+
"rgba(#{rgb_values.join(', ')}, #{alpha})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the total count of dynamic columns
|
|
47
|
+
def dynamic_column_count
|
|
48
|
+
AdminDashboardQueries.columns_count
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the total count of dynamic tables
|
|
52
|
+
def dynamic_table_count
|
|
53
|
+
AdminDashboardQueries.tables_count
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the total count of changelog entries
|
|
57
|
+
def changelog_count
|
|
58
|
+
DbdocEngine::DbDesignChangelogQueries.filtered_changelogs.count
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Fetches all table groups with their associated dynamic tables
|
|
62
|
+
def fetch_table_groups
|
|
63
|
+
DbdocEngine::DbDesignTableGroupQueries.all_groups_with_tables_and_columns
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def group_badge_style(group)
|
|
67
|
+
rgba_color = hex_to_rgba(group.group_color)
|
|
68
|
+
"background-color: #{rgba_color}; color: #{group.group_color};"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def table_column_count(table)
|
|
72
|
+
table.db_design_dynamic_columns.size
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// app/javascript/dbdoc_engine/application.js
|
|
2
|
+
|
|
3
|
+
import "@hotwired/turbo-rails";
|
|
4
|
+
import { Application } from "@hotwired/stimulus";
|
|
5
|
+
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Initialize Stimulus
|
|
9
|
+
const dbdoc_engine = Application.start();
|
|
10
|
+
eagerLoadControllersFrom("dbdoc_engine/controllers", dbdoc_engine); // Load gem controllers
|
|
11
|
+
|
|
12
|
+
window.Stimulus = dbdoc_engine;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
const dbdoc_engine = dbdoc_engine.start();
|
|
4
|
+
|
|
5
|
+
// Configure Stimulus development experience
|
|
6
|
+
dbdoc_engine.debug = false;
|
|
7
|
+
|
|
8
|
+
export { dbdoc_engine };
|
|
9
|
+
export default class extends Controller {
|
|
10
|
+
connect() {
|
|
11
|
+
document.addEventListener('turbo:load', this.updateLanguageDisplay);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
disconnect() {
|
|
15
|
+
document.removeEventListener('turbo:load', this.updateLanguageDisplay);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
updateLanguageDisplay = () => {
|
|
19
|
+
// Refresh language display after Turbo navigation
|
|
20
|
+
const langController = window.Stimulus.getControllerForElementAndIdentifier(
|
|
21
|
+
document.querySelector('[data-controller="language"]'),
|
|
22
|
+
'language'
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (langController) {
|
|
26
|
+
langController.updateButtonDisplay();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["form"]
|
|
5
|
+
|
|
6
|
+
connect() {
|
|
7
|
+
this.timeout = null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
submit() {
|
|
11
|
+
clearTimeout(this.timeout)
|
|
12
|
+
|
|
13
|
+
this.timeout = setTimeout(() => {
|
|
14
|
+
this.element.requestSubmit()
|
|
15
|
+
}, 300) // 300ms delay to avoid too many requests while typing
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static values = { data: Object }
|
|
5
|
+
|
|
6
|
+
connect() {
|
|
7
|
+
this.initializeChart()
|
|
8
|
+
}
|
|
9
|
+
initializeChart() {
|
|
10
|
+
const ctx = this.element.getContext("2d")
|
|
11
|
+
if (this.chart) this.chart.destroy()
|
|
12
|
+
|
|
13
|
+
// Check if showing all actions (multiple datasets)
|
|
14
|
+
const isAllActions = this.dataValue.datasets.length > 1
|
|
15
|
+
|
|
16
|
+
this.chart = new window.Chart(ctx, {
|
|
17
|
+
type: "bar",
|
|
18
|
+
data: this.dataValue,
|
|
19
|
+
options: {
|
|
20
|
+
responsive: true,
|
|
21
|
+
maintainAspectRatio: false,
|
|
22
|
+
plugins: {
|
|
23
|
+
legend: {
|
|
24
|
+
display: isAllActions,
|
|
25
|
+
position: 'top'
|
|
26
|
+
},
|
|
27
|
+
tooltip: {
|
|
28
|
+
callbacks: {
|
|
29
|
+
label: (context) => `${context.dataset.label}: ${context.parsed.y}`
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
scales: {
|
|
34
|
+
x: {
|
|
35
|
+
stacked: isAllActions,
|
|
36
|
+
grid: { display: false },
|
|
37
|
+
title: {
|
|
38
|
+
display: true,
|
|
39
|
+
text: 'Entity Types'
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
y: {
|
|
43
|
+
stacked: isAllActions,
|
|
44
|
+
beginAtZero: true,
|
|
45
|
+
ticks: {
|
|
46
|
+
stepSize: 1,
|
|
47
|
+
precision: 0
|
|
48
|
+
},
|
|
49
|
+
title: {
|
|
50
|
+
display: true,
|
|
51
|
+
text: 'Number of Actions'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|