pg_insights 0.3.2 → 0.4.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/app/assets/javascripts/pg_insights/application.js +91 -21
- data/app/assets/javascripts/pg_insights/plan_performance.js +53 -0
- data/app/assets/javascripts/pg_insights/query_comparison.js +1129 -0
- data/app/assets/javascripts/pg_insights/results/view_toggles.js +26 -5
- data/app/assets/javascripts/pg_insights/results.js +231 -1
- data/app/assets/stylesheets/pg_insights/analysis.css +2628 -0
- data/app/assets/stylesheets/pg_insights/application.css +51 -1
- data/app/assets/stylesheets/pg_insights/results.css +12 -1
- data/app/controllers/pg_insights/insights_controller.rb +486 -9
- data/app/helpers/pg_insights/application_helper.rb +339 -0
- data/app/helpers/pg_insights/insights_helper.rb +567 -0
- data/app/jobs/pg_insights/query_analysis_job.rb +142 -0
- data/app/models/pg_insights/query_execution.rb +198 -0
- data/app/services/pg_insights/query_analysis_service.rb +269 -0
- data/app/views/layouts/pg_insights/application.html.erb +2 -0
- data/app/views/pg_insights/insights/_compare_view.html.erb +264 -0
- data/app/views/pg_insights/insights/_empty_state.html.erb +9 -0
- data/app/views/pg_insights/insights/_execution_table_view.html.erb +86 -0
- data/app/views/pg_insights/insights/_history_bar.html.erb +33 -0
- data/app/views/pg_insights/insights/_perf_view.html.erb +244 -0
- data/app/views/pg_insights/insights/_plan_nodes.html.erb +12 -0
- data/app/views/pg_insights/insights/_plan_tree.html.erb +30 -0
- data/app/views/pg_insights/insights/_plan_tree_modern.html.erb +12 -0
- data/app/views/pg_insights/insights/_plan_view.html.erb +159 -0
- data/app/views/pg_insights/insights/_query_panel.html.erb +3 -2
- data/app/views/pg_insights/insights/_result.html.erb +19 -4
- data/app/views/pg_insights/insights/_results_info.html.erb +33 -9
- data/app/views/pg_insights/insights/_results_info_empty.html.erb +10 -0
- data/app/views/pg_insights/insights/_results_panel.html.erb +7 -9
- data/app/views/pg_insights/insights/_results_table.html.erb +0 -5
- data/app/views/pg_insights/insights/_visual_view.html.erb +212 -0
- data/app/views/pg_insights/insights/index.html.erb +4 -1
- data/app/views/pg_insights/timeline/compare.html.erb +3 -3
- data/config/routes.rb +6 -0
- data/lib/generators/pg_insights/install_generator.rb +20 -14
- data/lib/generators/pg_insights/templates/db/migrate/create_pg_insights_query_executions.rb +45 -0
- data/lib/pg_insights/engine.rb +8 -0
- data/lib/pg_insights/version.rb +1 -1
- data/lib/pg_insights.rb +30 -2
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddf26a0084294d0b53c353cdab1af7754064251af9a395f57364bda181aee532
|
4
|
+
data.tar.gz: 8e30fe547411d3a2eb5b67d03dd96637bfb2bee59df96b1a4946a892f0de4e13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2bce9427d180b8d043536aad436ef7ab73067f389684f4fbbfb18a2cc6ad410dcbf89a074ebb1e7240d73263078d590986593f8728b03373bd66880cd62f72b
|
7
|
+
data.tar.gz: 18a82e1a0dc340f3bbeb8c74830f08614bea54bbe351f553274e192562e616d4dc2021f4b6e8d851ab072c55525f9be5367bb430e9aeb33eb69e3d9dd3c69e95
|
@@ -1,5 +1,4 @@
|
|
1
1
|
|
2
|
-
|
3
2
|
// PG Insights JavaScript
|
4
3
|
document.addEventListener('DOMContentLoaded', function() {
|
5
4
|
const InsightsApp = {
|
@@ -23,6 +22,45 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
23
22
|
this.validateInitialQuery();
|
24
23
|
this.setupQueryExamples();
|
25
24
|
this.loadTableNames();
|
25
|
+
this.initializeAnalysisViews();
|
26
|
+
},
|
27
|
+
|
28
|
+
// Initialize analysis views if present
|
29
|
+
initializeAnalysisViews() {
|
30
|
+
const planView = document.getElementById('plan-view');
|
31
|
+
const perfView = document.getElementById('perf-view');
|
32
|
+
const planTab = document.querySelector('[data-view="plan"]');
|
33
|
+
|
34
|
+
if (planView && planTab && planView.style.display !== 'none') {
|
35
|
+
// Plan view is visible, make sure the tab is properly activated
|
36
|
+
this.activateAnalysisTab('plan');
|
37
|
+
}
|
38
|
+
},
|
39
|
+
|
40
|
+
// Activate analysis tab
|
41
|
+
activateAnalysisTab(viewType) {
|
42
|
+
const allTabs = document.querySelectorAll('.toggle-btn');
|
43
|
+
const targetTab = document.querySelector(`[data-view="${viewType}"]`);
|
44
|
+
|
45
|
+
if (targetTab) {
|
46
|
+
allTabs.forEach(tab => tab.classList.remove('active'));
|
47
|
+
targetTab.classList.add('active');
|
48
|
+
|
49
|
+
const allViews = document.querySelectorAll('.view-content');
|
50
|
+
allViews.forEach(view => view.style.display = 'none');
|
51
|
+
|
52
|
+
const targetView = document.getElementById(`${viewType}-view`);
|
53
|
+
if (targetView) {
|
54
|
+
targetView.style.display = 'block';
|
55
|
+
|
56
|
+
// Initialize PEV2 if switching to visual view
|
57
|
+
if (viewType === 'visual' && typeof window.initPEV2 !== 'undefined') {
|
58
|
+
setTimeout(() => {
|
59
|
+
window.initPEV2();
|
60
|
+
}, 100);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
26
64
|
},
|
27
65
|
|
28
66
|
// Load queries from data attribute
|
@@ -43,7 +81,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
43
81
|
// Keep this for backward compatibility if needed elsewhere
|
44
82
|
},
|
45
83
|
|
46
|
-
|
84
|
+
|
47
85
|
copyCurrentQuery() {
|
48
86
|
const textarea = document.querySelector('.sql-editor');
|
49
87
|
const btn = document.querySelector('.btn-icon.btn-copy');
|
@@ -61,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
61
99
|
});
|
62
100
|
},
|
63
101
|
|
64
|
-
|
102
|
+
|
65
103
|
saveCurrentQuery() {
|
66
104
|
const textarea = document.querySelector('.sql-editor');
|
67
105
|
const sql = textarea?.value.trim();
|
@@ -76,7 +114,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
76
114
|
name = prompt("Enter a name for this new saved query:");
|
77
115
|
}
|
78
116
|
|
79
|
-
if (!name) return;
|
117
|
+
if (!name) return;
|
80
118
|
|
81
119
|
const method = isUpdate ? 'PATCH' : 'POST';
|
82
120
|
const url = isUpdate ? `/pg_insights/queries/${this.currentQueryState.id}` : '/pg_insights/queries';
|
@@ -130,7 +168,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
130
168
|
});
|
131
169
|
},
|
132
170
|
|
133
|
-
|
171
|
+
|
134
172
|
validateQuery(sql) {
|
135
173
|
if (!sql || !sql.trim()) {
|
136
174
|
return { valid: false, message: "Please enter a SQL query" };
|
@@ -206,7 +244,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
206
244
|
}
|
207
245
|
},
|
208
246
|
|
209
|
-
|
247
|
+
|
210
248
|
clearQuery() {
|
211
249
|
const textarea = document.querySelector('.sql-editor');
|
212
250
|
if (textarea) {
|
@@ -252,7 +290,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
252
290
|
});
|
253
291
|
},
|
254
292
|
|
255
|
-
|
293
|
+
|
256
294
|
previewTable(tableName) {
|
257
295
|
if (!tableName) return;
|
258
296
|
|
@@ -262,12 +300,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
262
300
|
if (textarea) {
|
263
301
|
textarea.value = sql;
|
264
302
|
|
265
|
-
// Validate the query
|
266
303
|
this.validateAndUpdateUI(sql);
|
267
|
-
|
268
|
-
// Auto-resize textarea
|
269
|
-
textarea.style.height = 'auto';
|
270
|
-
textarea.style.height = Math.max(160, textarea.scrollHeight) + 'px';
|
304
|
+
this.resizeTextarea(textarea);
|
271
305
|
|
272
306
|
// Auto-execute the query
|
273
307
|
const executeBtn = document.getElementById('execute-btn');
|
@@ -283,6 +317,35 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
283
317
|
}
|
284
318
|
},
|
285
319
|
|
320
|
+
resizeTextarea(textarea) {
|
321
|
+
if (!textarea) return;
|
322
|
+
|
323
|
+
const formContent = textarea.closest('.form-content');
|
324
|
+
if (!formContent) {
|
325
|
+
textarea.style.height = 'auto';
|
326
|
+
textarea.style.height = Math.max(160, Math.min(400, textarea.scrollHeight)) + 'px';
|
327
|
+
return;
|
328
|
+
}
|
329
|
+
|
330
|
+
const formContentStyle = window.getComputedStyle(formContent);
|
331
|
+
const formContentHeight = parseInt(formContentStyle.height);
|
332
|
+
const formContentPadding = parseInt(formContentStyle.paddingTop) + parseInt(formContentStyle.paddingBottom);
|
333
|
+
const availableHeight = formContentHeight - formContentPadding;
|
334
|
+
|
335
|
+
const minHeight = 160;
|
336
|
+
const maxHeight = Math.min(400, Math.max(minHeight, availableHeight - 20));
|
337
|
+
|
338
|
+
textarea.style.height = 'auto';
|
339
|
+
const idealHeight = Math.max(minHeight, Math.min(maxHeight, textarea.scrollHeight));
|
340
|
+
textarea.style.height = idealHeight + 'px';
|
341
|
+
|
342
|
+
if (textarea.scrollHeight > maxHeight) {
|
343
|
+
textarea.style.overflowY = 'auto';
|
344
|
+
} else {
|
345
|
+
textarea.style.overflowY = 'hidden';
|
346
|
+
}
|
347
|
+
},
|
348
|
+
|
286
349
|
loadQueryById(queryId) {
|
287
350
|
const query = this.config.queries.find(q => q.id.toString() === queryId.toString());
|
288
351
|
|
@@ -358,7 +421,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
358
421
|
});
|
359
422
|
},
|
360
423
|
|
361
|
-
|
424
|
+
|
362
425
|
bindEvents() {
|
363
426
|
const textarea = document.querySelector('.sql-editor');
|
364
427
|
const executeBtn = document.getElementById('execute-btn');
|
@@ -366,24 +429,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
366
429
|
// Real-time validation on input
|
367
430
|
if (textarea) {
|
368
431
|
textarea.addEventListener('input', () => {
|
369
|
-
|
370
|
-
textarea.style.height = 'auto';
|
371
|
-
textarea.style.height = Math.max(160, textarea.scrollHeight) + 'px';
|
372
|
-
|
373
|
-
// Instant validation
|
432
|
+
this.resizeTextarea(textarea);
|
374
433
|
this.validateAndUpdateUI(textarea.value);
|
375
434
|
});
|
376
435
|
|
377
|
-
|
436
|
+
|
378
437
|
textarea.addEventListener('paste', () => {
|
379
|
-
// Small delay to let paste complete
|
380
438
|
setTimeout(() => {
|
381
439
|
this.validateAndUpdateUI(textarea.value);
|
440
|
+
this.resizeTextarea(textarea);
|
382
441
|
}, 10);
|
383
442
|
});
|
384
443
|
}
|
385
444
|
|
386
|
-
|
445
|
+
|
387
446
|
if (executeBtn) {
|
388
447
|
executeBtn.addEventListener('click', (event) => {
|
389
448
|
if (executeBtn.disabled) {
|
@@ -400,6 +459,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
400
459
|
window.clearQuery = () => this.clearQuery();
|
401
460
|
window.copyCurrentQuery = () => this.copyCurrentQuery();
|
402
461
|
window.saveCurrentQuery = () => this.saveCurrentQuery();
|
462
|
+
|
463
|
+
// Listen for form submissions and trigger history refresh
|
464
|
+
const form = document.querySelector('.insights-container form');
|
465
|
+
if (form) {
|
466
|
+
form.addEventListener('submit', () => {
|
467
|
+
// Delay to allow the analysis to complete
|
468
|
+
setTimeout(() => {
|
469
|
+
document.dispatchEvent(new CustomEvent('analysisCompleted'));
|
470
|
+
}, 2000);
|
471
|
+
});
|
472
|
+
}
|
403
473
|
|
404
474
|
// Table preview dropdown
|
405
475
|
const tableSelect = document.getElementById('table-preview-select');
|
@@ -0,0 +1,53 @@
|
|
1
|
+
// Enhanced Plan and Performance Views JavaScript - Simplified
|
2
|
+
document.addEventListener('DOMContentLoaded', function() {
|
3
|
+
const PlanPerformanceEnhancer = {
|
4
|
+
init() {
|
5
|
+
// Apply score-based CSS classes to any remaining elements
|
6
|
+
this.applyScoreClasses();
|
7
|
+
},
|
8
|
+
|
9
|
+
applyScoreClasses() {
|
10
|
+
// Find elements that contain score text and apply appropriate classes
|
11
|
+
const scoreElements = document.querySelectorAll('.stat-value, .metric-score, .kpi-value');
|
12
|
+
|
13
|
+
scoreElements.forEach(element => {
|
14
|
+
const text = element.textContent.toLowerCase().trim();
|
15
|
+
let cssClass = '';
|
16
|
+
|
17
|
+
switch(text) {
|
18
|
+
case 'excellent':
|
19
|
+
cssClass = 'score-excellent';
|
20
|
+
break;
|
21
|
+
case 'good':
|
22
|
+
cssClass = 'score-good';
|
23
|
+
break;
|
24
|
+
case 'fair':
|
25
|
+
cssClass = 'score-fair';
|
26
|
+
break;
|
27
|
+
case 'poor':
|
28
|
+
cssClass = 'score-poor';
|
29
|
+
break;
|
30
|
+
case 'none':
|
31
|
+
cssClass = 'score-none';
|
32
|
+
break;
|
33
|
+
}
|
34
|
+
|
35
|
+
if (cssClass) {
|
36
|
+
element.classList.add(cssClass);
|
37
|
+
}
|
38
|
+
});
|
39
|
+
}
|
40
|
+
};
|
41
|
+
|
42
|
+
// Initialize enhanced views
|
43
|
+
PlanPerformanceEnhancer.init();
|
44
|
+
|
45
|
+
// Re-initialize when views are switched
|
46
|
+
document.addEventListener('click', function(e) {
|
47
|
+
if (e.target.matches('.toggle-btn[data-view="plan"]')) {
|
48
|
+
setTimeout(() => PlanPerformanceEnhancer.applyScoreClasses(), 100);
|
49
|
+
} else if (e.target.matches('.toggle-btn[data-view="perf"]')) {
|
50
|
+
setTimeout(() => PlanPerformanceEnhancer.applyScoreClasses(), 100);
|
51
|
+
}
|
52
|
+
});
|
53
|
+
});
|