dbviewer 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -0
  3. data/app/controllers/concerns/dbviewer/database_operations.rb +11 -19
  4. data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +84 -0
  5. data/app/controllers/dbviewer/api/queries_controller.rb +1 -1
  6. data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +5 -6
  7. data/app/controllers/dbviewer/logs_controller.rb +1 -1
  8. data/app/controllers/dbviewer/tables_controller.rb +2 -8
  9. data/app/helpers/dbviewer/application_helper.rb +1 -1
  10. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +217 -100
  11. data/app/views/dbviewer/tables/show.html.erb +278 -404
  12. data/config/routes.rb +7 -0
  13. data/lib/dbviewer/database/cache_manager.rb +78 -0
  14. data/lib/dbviewer/database/dynamic_model_factory.rb +62 -0
  15. data/lib/dbviewer/database/manager.rb +204 -0
  16. data/lib/dbviewer/database/metadata_manager.rb +129 -0
  17. data/lib/dbviewer/datatable/query_operations.rb +330 -0
  18. data/lib/dbviewer/datatable/query_params.rb +41 -0
  19. data/lib/dbviewer/engine.rb +1 -1
  20. data/lib/dbviewer/query/analyzer.rb +250 -0
  21. data/lib/dbviewer/query/collection.rb +39 -0
  22. data/lib/dbviewer/query/executor.rb +93 -0
  23. data/lib/dbviewer/query/logger.rb +108 -0
  24. data/lib/dbviewer/query/parser.rb +56 -0
  25. data/lib/dbviewer/storage/file_storage.rb +0 -3
  26. data/lib/dbviewer/version.rb +1 -1
  27. data/lib/dbviewer.rb +24 -7
  28. metadata +14 -14
  29. data/lib/dbviewer/cache_manager.rb +0 -78
  30. data/lib/dbviewer/database_manager.rb +0 -249
  31. data/lib/dbviewer/dynamic_model_factory.rb +0 -60
  32. data/lib/dbviewer/error_handler.rb +0 -18
  33. data/lib/dbviewer/logger.rb +0 -77
  34. data/lib/dbviewer/query_analyzer.rb +0 -239
  35. data/lib/dbviewer/query_collection.rb +0 -37
  36. data/lib/dbviewer/query_executor.rb +0 -91
  37. data/lib/dbviewer/query_parser.rb +0 -53
  38. data/lib/dbviewer/table_metadata_manager.rb +0 -136
  39. data/lib/dbviewer/table_query_operations.rb +0 -621
  40. data/lib/dbviewer/table_query_params.rb +0 -39
@@ -150,9 +150,48 @@
150
150
 
151
151
  // Generate the ERD diagram
152
152
  const tables = <%= raw @tables.to_json %>;
153
- const relationships = <%= raw @table_relationships.to_json %>;
154
-
155
- console.log(tables, relationships)
153
+
154
+ console.log('Tables:', tables);
155
+
156
+ // Initialize empty relationships - will be loaded asynchronously
157
+ let relationships = [];
158
+ let relationshipsLoaded = false;
159
+
160
+ // Function to fetch relationships asynchronously
161
+ function fetchRelationships() {
162
+ return fetch('<%= dbviewer.relationships_api_entity_relationship_diagrams_path %>', {
163
+ headers: {
164
+ 'Accept': 'application/json',
165
+ 'X-Requested-With': 'XMLHttpRequest'
166
+ }
167
+ })
168
+ .then(response => {
169
+ if (!response.ok) {
170
+ throw new Error(`HTTP error! status: ${response.status}`);
171
+ }
172
+ return response.json();
173
+ })
174
+ .then(data => {
175
+ console.log('Relationships loaded:', data);
176
+ relationships = data.relationships || [];
177
+ relationshipsLoaded = true;
178
+ return relationships;
179
+ })
180
+ .catch(error => {
181
+ console.error('Error fetching relationships:', error);
182
+ relationshipsLoaded = true; // Mark as loaded even on error to prevent infinite loading
183
+ return [];
184
+ });
185
+ }
186
+
187
+ // Function to update loading status
188
+ function updateLoadingStatus(message) {
189
+ const loadingElement = document.getElementById('erd-loading');
190
+ const loadingText = loadingElement.querySelector('p');
191
+ if (loadingText) {
192
+ loadingText.textContent = message;
193
+ }
194
+ }
156
195
 
157
196
  // Create the ER diagram definition in Mermaid syntax
158
197
  let mermaidDefinition = 'erDiagram\n';
@@ -160,7 +199,15 @@
160
199
  // We'll store table column data here as we fetch it
161
200
  const tableColumns = {};
162
201
 
163
- // First pass: add all tables with minimal info
202
+ // Track loading progress
203
+ let columnsLoadedCount = 0;
204
+ const totalTables = tables.length;
205
+
206
+ // Start fetching relationships immediately
207
+ updateLoadingStatus('Loading database relationships...');
208
+ const relationshipsPromise = fetchRelationships();
209
+
210
+ // First pass: add all tables with minimal info and start loading columns
164
211
  tables.forEach(function(table) {
165
212
  const tableName = table.name;
166
213
  mermaidDefinition += ` ${tableName} {\n`;
@@ -178,121 +225,134 @@
178
225
  .then(data => {
179
226
  if (data && data.columns) {
180
227
  tableColumns[tableName] = data.columns;
181
- updateDiagramWithColumns();
228
+ columnsLoadedCount++;
229
+
230
+ // Update loading status
231
+ updateLoadingStatus(`Loading table details... (${columnsLoadedCount}/${totalTables} tables)`);
232
+
233
+ checkIfReadyToUpdate();
182
234
  }
183
235
  })
184
236
  .catch(error => {
185
237
  console.error(`Error fetching columns for table ${tableName}:`, error);
238
+ columnsLoadedCount++;
239
+ checkIfReadyToUpdate();
186
240
  });
187
241
  });
188
242
 
243
+ // Function to check if we're ready to update the diagram with full data
244
+ function checkIfReadyToUpdate() {
245
+ if (columnsLoadedCount === totalTables && relationshipsLoaded) {
246
+ updateDiagramWithFullData();
247
+ }
248
+ }
249
+
250
+ // Wait for relationships to load and check if ready
251
+ relationshipsPromise.finally(() => {
252
+ checkIfReadyToUpdate();
253
+ });
254
+
189
255
  // Track if we're currently updating the diagram
190
256
  let isUpdatingDiagram = false;
191
257
 
192
- // Function to update the diagram once we have columns
193
- function updateDiagramWithColumns() {
258
+ // Function to update the diagram once we have all data
259
+ function updateDiagramWithFullData() {
194
260
  // Prevent multiple simultaneous updates
195
261
  if (isUpdatingDiagram) return;
196
262
 
197
- // Check if we have all the tables loaded
198
- if (Object.keys(tableColumns).length === tables.length) {
199
- isUpdatingDiagram = true;
200
- console.log('Updating diagram with full column data');
201
-
202
- // Regenerate the diagram with complete column data
203
- let updatedDefinition = 'erDiagram\n';
263
+ isUpdatingDiagram = true;
264
+ console.log('Updating diagram with full column and relationship data');
265
+
266
+ updateLoadingStatus('Generating diagram...');
267
+
268
+ // Regenerate the diagram with complete data
269
+ let updatedDefinition = 'erDiagram\n';
270
+
271
+ tables.forEach(function(table) {
272
+ const tableName = table.name;
273
+ updatedDefinition += ` ${tableName} {\n`;
204
274
 
205
- tables.forEach(function(table) {
206
- const tableName = table.name;
207
- updatedDefinition += ` ${tableName} {\n`;
208
-
209
- const columns = tableColumns[tableName] || [];
210
- columns.forEach(column => {
211
- updatedDefinition += ` ${column.type || 'string'} ${column.name}\n`;
212
- });
213
-
214
- updatedDefinition += ' }\n';
275
+ const columns = tableColumns[tableName] || [];
276
+ columns.forEach(column => {
277
+ updatedDefinition += ` ${column.type || 'string'} ${column.name}\n`;
215
278
  });
216
279
 
217
- // Add relationships
218
- if (relationships && relationships.length > 0) {
219
- relationships.forEach(function(rel) {
220
- updatedDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} → ${rel.to_column}"\n`;
221
- });
222
- } else {
223
- updatedDefinition += ' %% No relationships found in the database schema\n';
224
- }
225
-
226
- // Create a new diagram element
227
- const updatedErdDiv = document.createElement('div');
228
- updatedErdDiv.className = 'mermaid';
229
- updatedErdDiv.innerHTML = updatedDefinition;
230
-
231
- // Get the container but don't clear it yet
232
- const container = document.getElementById('erd-container');
233
-
234
- // First, clean up any previous zoom instance
235
- if (panZoomInstance) {
236
- panZoomInstance.destroy();
237
- panZoomInstance = null;
238
- }
239
-
240
- // Create a temporary container
241
- const tempContainer = document.createElement('div');
242
- tempContainer.style.visibility = 'hidden';
243
- tempContainer.style.position = 'absolute';
244
- tempContainer.style.width = '100%';
245
- tempContainer.appendChild(updatedErdDiv);
246
- document.body.appendChild(tempContainer);
280
+ updatedDefinition += ' }\n';
281
+ });
282
+
283
+ // Add relationships
284
+ if (relationships && relationships.length > 0) {
285
+ relationships.forEach(function(rel) {
286
+ updatedDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} ${rel.to_column}"\n`;
287
+ });
288
+ } else {
289
+ updatedDefinition += ' %% No relationships found in the database schema\n';
290
+ }
291
+
292
+ // Create a new diagram element
293
+ const updatedErdDiv = document.createElement('div');
294
+ updatedErdDiv.className = 'mermaid';
295
+ updatedErdDiv.innerHTML = updatedDefinition;
296
+
297
+ // Get the container but don't clear it yet
298
+ const container = document.getElementById('erd-container');
299
+
300
+ // First, clean up any previous zoom instance
301
+ if (panZoomInstance) {
302
+ panZoomInstance.destroy();
303
+ panZoomInstance = null;
304
+ }
305
+
306
+ // Create a temporary container
307
+ const tempContainer = document.createElement('div');
308
+ tempContainer.style.visibility = 'hidden';
309
+ tempContainer.style.position = 'absolute';
310
+ tempContainer.style.width = '100%';
311
+ tempContainer.appendChild(updatedErdDiv);
312
+ document.body.appendChild(tempContainer);
313
+
314
+ // Render in the temporary container first
315
+ mermaid.init(undefined, updatedErdDiv).then(function() {
316
+ console.log('Diagram fully updated with all data');
247
317
 
248
- // Render in the temporary container first
249
- mermaid.init(undefined, updatedErdDiv).then(function() {
250
- console.log('Diagram fully updated with column data');
318
+ // Clear original container and move the rendered content
319
+ try {
320
+ // Remove from temp container without destroying
321
+ tempContainer.removeChild(updatedErdDiv);
251
322
 
252
- // Clear original container and move the rendered content
253
- try {
254
- // Remove from temp container without destroying
255
- tempContainer.removeChild(updatedErdDiv);
256
-
257
- // Clear main container and add the diagram
258
- container.innerHTML = '';
259
- container.appendChild(updatedErdDiv);
260
-
261
- // Remove temp container
262
- document.body.removeChild(tempContainer);
263
-
264
- // Wait a bit for the DOM to stabilize before initializing pan-zoom
265
- setTimeout(() => {
266
- setupZoomControls();
267
- // Mark diagram as ready for download
268
- diagramReady = true;
269
- isUpdatingDiagram = false;
270
- }, 100);
271
- } catch(err) {
272
- console.error('Error moving diagram to container:', err);
273
- isUpdatingDiagram = false;
274
- }
275
- }).catch(function(error) {
276
- console.error('Error rendering updated diagram:', error);
323
+ // Hide loading indicator
324
+ document.getElementById('erd-loading').style.display = 'none';
325
+
326
+ // Clear main container and add the diagram
327
+ container.innerHTML = '';
328
+ container.appendChild(updatedErdDiv);
329
+
330
+ // Remove temp container
277
331
  document.body.removeChild(tempContainer);
332
+
333
+ // Wait a bit for the DOM to stabilize before initializing pan-zoom
334
+ setTimeout(() => {
335
+ setupZoomControls();
336
+ // Mark diagram as ready for download
337
+ diagramReady = true;
338
+ isUpdatingDiagram = false;
339
+ }, 100);
340
+ } catch(err) {
341
+ console.error('Error moving diagram to container:', err);
278
342
  isUpdatingDiagram = false;
279
- showError('Error rendering diagram', 'There was an error updating the diagram with complete data.', error.message);
280
- });
281
- }
282
- }
283
-
284
- // Add relationships
285
- if (relationships && relationships.length > 0) {
286
- relationships.forEach(function(rel) {
287
- // Format: "Customer ||--o{ Order : places"
288
- mermaidDefinition += ` ${rel.from_table} }|--|| ${rel.to_table} : "${rel.from_column} → ${rel.to_column}"\n`;
343
+ }
344
+ }).catch(function(error) {
345
+ console.error('Error rendering updated diagram:', error);
346
+ document.body.removeChild(tempContainer);
347
+ isUpdatingDiagram = false;
348
+ showError('Error rendering diagram', 'There was an error updating the diagram with complete data.', error.message);
289
349
  });
290
- } else {
291
- // Add a note if no relationships are found
292
- mermaidDefinition += ' %% No relationships found in the database schema\n';
293
350
  }
294
351
 
295
- // Create a div for the initial diagram
352
+ // Add initial relationships placeholder (empty)
353
+ mermaidDefinition += ' %% Relationships loading...\n';
354
+
355
+ // Create a div for the initial diagram (shows immediately with table names only)
296
356
  const erdDiv = document.createElement('div');
297
357
  erdDiv.className = 'mermaid';
298
358
  erdDiv.innerHTML = mermaidDefinition;
@@ -308,13 +368,16 @@
308
368
  tempInitContainer.appendChild(erdDiv);
309
369
  document.body.appendChild(tempInitContainer);
310
370
 
371
+ // Update loading status for initial render
372
+ updateLoadingStatus('Rendering initial diagram...');
373
+
311
374
  // Render the initial diagram in the temporary container
312
375
  mermaid.init(undefined, erdDiv).then(function() {
313
376
  try {
314
377
  // Remove from temp container without destroying
315
378
  tempInitContainer.removeChild(erdDiv);
316
379
 
317
- // Hide the loading indicator
380
+ // Hide the loading indicator temporarily (will show updated loading for data fetching)
318
381
  document.getElementById('erd-loading').style.display = 'none';
319
382
 
320
383
  // Add the rendered diagram to the main container
@@ -323,19 +386,57 @@
323
386
  // Remove temp container
324
387
  document.body.removeChild(tempInitContainer);
325
388
 
326
- // Setup zoom controls after diagram is rendered
389
+ // Setup initial zoom controls
327
390
  setTimeout(() => {
328
391
  setupZoomControls();
392
+ // Show a subtle loading indicator that data is still loading
393
+ showDataLoadingIndicator();
329
394
  }, 100);
330
395
  } catch(err) {
331
396
  console.error('Error moving initial diagram to container:', err);
332
397
  }
333
398
  }).catch(function(error) {
334
- console.error('Error rendering diagram:', error);
399
+ console.error('Error rendering initial diagram:', error);
335
400
  document.body.removeChild(tempInitContainer);
336
- showError('Error generating diagram', 'There was an error generating the entity relationship diagram.', error.message);
401
+ showError('Error generating diagram', 'There was an error generating the initial entity relationship diagram.', error.message);
337
402
  });
338
403
 
404
+ // Function to show a subtle loading indicator for data loading
405
+ function showDataLoadingIndicator() {
406
+ // Create a small loading badge in the top-right corner
407
+ const loadingBadge = document.createElement('div');
408
+ loadingBadge.id = 'data-loading-badge';
409
+ loadingBadge.className = 'position-absolute top-0 end-0 m-3';
410
+ loadingBadge.style.zIndex = '1000';
411
+ loadingBadge.innerHTML = `
412
+ <div class="badge bg-info d-flex align-items-center">
413
+ <div class="spinner-border spinner-border-sm me-2" role="status" style="width: 0.8rem; height: 0.8rem;">
414
+ <span class="visually-hidden">Loading...</span>
415
+ </div>
416
+ Loading details...
417
+ </div>
418
+ `;
419
+
420
+ container.style.position = 'relative';
421
+ container.appendChild(loadingBadge);
422
+
423
+ // Remove the badge when data loading is complete
424
+ const checkComplete = () => {
425
+ if (columnsLoadedCount === totalTables && relationshipsLoaded) {
426
+ setTimeout(() => {
427
+ const badge = document.getElementById('data-loading-badge');
428
+ if (badge) {
429
+ badge.remove();
430
+ }
431
+ }, 500); // Small delay to show completion
432
+ } else {
433
+ setTimeout(checkComplete, 500);
434
+ }
435
+ };
436
+
437
+ setTimeout(checkComplete, 1000);
438
+ }
439
+
339
440
  // SVG Pan Zoom instance
340
441
  let panZoomInstance = null;
341
442
 
@@ -677,6 +778,22 @@
677
778
  text-align: center;
678
779
  }
679
780
 
781
+ /* Data loading badge styling */
782
+ #data-loading-badge {
783
+ animation: pulse 2s infinite;
784
+ }
785
+
786
+ @keyframes pulse {
787
+ 0% { opacity: 1; }
788
+ 50% { opacity: 0.7; }
789
+ 100% { opacity: 1; }
790
+ }
791
+
792
+ #data-loading-badge .badge {
793
+ font-size: 0.75rem;
794
+ padding: 0.4rem 0.6rem;
795
+ }
796
+
680
797
  /* Mermaid override for text size */
681
798
  .mermaid .entityLabel div {
682
799
  font-size: 20px !important;