pg_insights 0.3.1 → 0.4.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 +4 -4
- data/app/assets/javascripts/pg_insights/application.js +91 -24
- 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 -2
- 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 +8 -1
- 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/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: 92152260b0987ce757b707833297038fee48d0dbebd8ea727012052eaa3ee6f2
|
4
|
+
data.tar.gz: 971daab4babe4ef099c544a3cf2bd0a7c64b332c6785a7b33dc9e90463f31b25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d562ca875f72512bd5f422a3eb87b835496c808b09282f7f6014ed39deaeeba5afc116f179a08e75a402e49014fff4c1872aabf52a2a693b3b7175eff09d86c7
|
7
|
+
data.tar.gz: d404b95da5502eb7b9a966f0b6eb43e59fb35c9e637f4b77d71d6a6cc06149430a7ce3d73cadc9c9375902bdec785191007cb5e95a9dd41641901d7dcc49484e
|
@@ -1,7 +1,3 @@
|
|
1
|
-
//= require chartkick
|
2
|
-
//= require Chart.bundle
|
3
|
-
//= require_tree .
|
4
|
-
|
5
1
|
|
6
2
|
// PG Insights JavaScript
|
7
3
|
document.addEventListener('DOMContentLoaded', function() {
|
@@ -26,6 +22,45 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
26
22
|
this.validateInitialQuery();
|
27
23
|
this.setupQueryExamples();
|
28
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
|
+
}
|
29
64
|
},
|
30
65
|
|
31
66
|
// Load queries from data attribute
|
@@ -46,7 +81,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
46
81
|
// Keep this for backward compatibility if needed elsewhere
|
47
82
|
},
|
48
83
|
|
49
|
-
|
84
|
+
|
50
85
|
copyCurrentQuery() {
|
51
86
|
const textarea = document.querySelector('.sql-editor');
|
52
87
|
const btn = document.querySelector('.btn-icon.btn-copy');
|
@@ -64,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
64
99
|
});
|
65
100
|
},
|
66
101
|
|
67
|
-
|
102
|
+
|
68
103
|
saveCurrentQuery() {
|
69
104
|
const textarea = document.querySelector('.sql-editor');
|
70
105
|
const sql = textarea?.value.trim();
|
@@ -79,7 +114,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
79
114
|
name = prompt("Enter a name for this new saved query:");
|
80
115
|
}
|
81
116
|
|
82
|
-
if (!name) return;
|
117
|
+
if (!name) return;
|
83
118
|
|
84
119
|
const method = isUpdate ? 'PATCH' : 'POST';
|
85
120
|
const url = isUpdate ? `/pg_insights/queries/${this.currentQueryState.id}` : '/pg_insights/queries';
|
@@ -133,7 +168,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
133
168
|
});
|
134
169
|
},
|
135
170
|
|
136
|
-
|
171
|
+
|
137
172
|
validateQuery(sql) {
|
138
173
|
if (!sql || !sql.trim()) {
|
139
174
|
return { valid: false, message: "Please enter a SQL query" };
|
@@ -209,7 +244,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
209
244
|
}
|
210
245
|
},
|
211
246
|
|
212
|
-
|
247
|
+
|
213
248
|
clearQuery() {
|
214
249
|
const textarea = document.querySelector('.sql-editor');
|
215
250
|
if (textarea) {
|
@@ -255,7 +290,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
255
290
|
});
|
256
291
|
},
|
257
292
|
|
258
|
-
|
293
|
+
|
259
294
|
previewTable(tableName) {
|
260
295
|
if (!tableName) return;
|
261
296
|
|
@@ -265,12 +300,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
265
300
|
if (textarea) {
|
266
301
|
textarea.value = sql;
|
267
302
|
|
268
|
-
// Validate the query
|
269
303
|
this.validateAndUpdateUI(sql);
|
270
|
-
|
271
|
-
// Auto-resize textarea
|
272
|
-
textarea.style.height = 'auto';
|
273
|
-
textarea.style.height = Math.max(160, textarea.scrollHeight) + 'px';
|
304
|
+
this.resizeTextarea(textarea);
|
274
305
|
|
275
306
|
// Auto-execute the query
|
276
307
|
const executeBtn = document.getElementById('execute-btn');
|
@@ -286,6 +317,35 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
286
317
|
}
|
287
318
|
},
|
288
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
|
+
|
289
349
|
loadQueryById(queryId) {
|
290
350
|
const query = this.config.queries.find(q => q.id.toString() === queryId.toString());
|
291
351
|
|
@@ -361,7 +421,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
361
421
|
});
|
362
422
|
},
|
363
423
|
|
364
|
-
|
424
|
+
|
365
425
|
bindEvents() {
|
366
426
|
const textarea = document.querySelector('.sql-editor');
|
367
427
|
const executeBtn = document.getElementById('execute-btn');
|
@@ -369,24 +429,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
369
429
|
// Real-time validation on input
|
370
430
|
if (textarea) {
|
371
431
|
textarea.addEventListener('input', () => {
|
372
|
-
|
373
|
-
textarea.style.height = 'auto';
|
374
|
-
textarea.style.height = Math.max(160, textarea.scrollHeight) + 'px';
|
375
|
-
|
376
|
-
// Instant validation
|
432
|
+
this.resizeTextarea(textarea);
|
377
433
|
this.validateAndUpdateUI(textarea.value);
|
378
434
|
});
|
379
435
|
|
380
|
-
|
436
|
+
|
381
437
|
textarea.addEventListener('paste', () => {
|
382
|
-
// Small delay to let paste complete
|
383
438
|
setTimeout(() => {
|
384
439
|
this.validateAndUpdateUI(textarea.value);
|
440
|
+
this.resizeTextarea(textarea);
|
385
441
|
}, 10);
|
386
442
|
});
|
387
443
|
}
|
388
444
|
|
389
|
-
|
445
|
+
|
390
446
|
if (executeBtn) {
|
391
447
|
executeBtn.addEventListener('click', (event) => {
|
392
448
|
if (executeBtn.disabled) {
|
@@ -403,6 +459,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
403
459
|
window.clearQuery = () => this.clearQuery();
|
404
460
|
window.copyCurrentQuery = () => this.copyCurrentQuery();
|
405
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
|
+
}
|
406
473
|
|
407
474
|
// Table preview dropdown
|
408
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
|
+
});
|