pg_reports 0.5.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05027e6e3278a707d98301a3d4a44dbce9930d26e963359d1afb01b57626df0c
4
- data.tar.gz: 0c03b76ef459a04fa4ea732e062fd73020d814c5f11bcc6e3f0a771d17e66976
3
+ metadata.gz: 0e9a2129fba68b259fc72598215a7cc02ff8785c93a786603505d9b9987d5df1
4
+ data.tar.gz: 7910113c5cc18115f59463067ab201f1942ec8b968570557af8aefda645098c7
5
5
  SHA512:
6
- metadata.gz: a5e8a8eae451b10265dcbd3a9839f21d7c647d0876e7a41b7ac8e2a2219b87bbfbd45ec621f6ac552aba1c1cb8284005a114ca8758c3bd61e9cf3440ca6838d6
7
- data.tar.gz: cf37e2c3458b8f24ca6bc548f7d58dea29af6547c14bf1c6db1468962fa1ec6e2095a9df8923045e5b8e86c6cf8c00e054e90e21cd803c7137d10db96e3f12de
6
+ metadata.gz: 7e5573dd2cca17cc74cdfe104e6eb09249069d59d50b033effd15e9c46bcb63040da50d998d91c8a2c7e31b8dc4fd3f86c2692f5b60d83050db766942d9a5d46
7
+ data.tar.gz: 025a5a37c86817e22265aff1227e332a1f5dd09cf0954a48321fe1ca5dc8c8f402cd13671c4b7b4c1b69e7ec739ef7f8ca47280b11894718d1bcaa946d7983b7
data/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.3] - 2026-02-11
11
+
12
+ ### Fixed
13
+
14
+ - **Live Query Monitor** - fixed filtering that was too aggressive:
15
+ - Removed `dashboard_controller.rb` from query filtering to allow monitoring user application queries
16
+ - Now correctly shows queries from user's application even when dashboard page is active
17
+ - Only internal pg_reports module queries are filtered (as intended)
18
+
19
+ ### Added
20
+
21
+ - **IDE Integration for Live Query Monitor**:
22
+ - Clickable source file links in query monitor now open files in IDE
23
+ - Support for multiple IDEs: VS Code, RubyMine, IntelliJ IDEA, Cursor (with WSL variants)
24
+ - Smart IDE selection: shows menu or opens directly if favorite IDE is set
25
+ - IDE settings button (⚙️) in dashboard header for choosing default IDE
26
+ - Settings persist in localStorage across all dashboard pages
27
+
28
+ ## [0.5.2] - 2026-02-09
29
+
30
+ ### Fixed
31
+
32
+ - **pg_stat_statements detection no longer requires `pg_read_all_settings` role**:
33
+ - Changed `pg_stat_statements_preloaded?` to query pg_stat_statements directly instead of checking `shared_preload_libraries`
34
+ - Fixes permission denied errors in environments like CloudnativePG where regular users lack access to `shared_preload_libraries` setting
35
+ - Works seamlessly with Kubernetes PostgreSQL operators and managed databases with restricted permissions
36
+ - Improved error messages in `enable_pg_stat_statements!` method
37
+
10
38
  ## [0.5.1] - 2026-02-09
11
39
 
12
40
  ### Added
@@ -15,23 +43,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
43
  - New config option `allow_raw_query_execution` (default: `false`)
16
44
  - Environment variable support: `PG_REPORTS_ALLOW_RAW_QUERY_EXECUTION`
17
45
  - Security documentation in README with examples and best practices
18
- - Frontend UI: disabled buttons with tooltips when feature is off
19
- - JavaScript validation in `executeQuery()` and `executeExplainAnalyze()` functions
20
46
  - Configuration tests for new security setting
47
+ - **Hash-based Query Execution System** - prevents SQL injection and query tampering:
48
+ - Backend generates SHA256 hash for each query and stores in Rails.cache (1-hour TTL)
49
+ - Frontend sends only hash (not query text) to execution endpoints
50
+ - Backend retrieves and validates original query by hash
51
+ - Strict validation: only SELECT queries, no dangerous keywords, no multiple statements
52
+ - Cache failure handling with clear error messages
53
+ - Protection against nested SQL injection attempts
54
+ - **Enhanced Error Handling** - improved user feedback:
55
+ - Active warning messages instead of disabled buttons when feature is off
56
+ - Toast notifications with configuration instructions
57
+ - Detailed error messages in modal with code examples
58
+ - Clear messaging when Redis/cache backend is unavailable
21
59
 
22
60
  ### Changed
23
61
 
24
62
  - **BREAKING CHANGE**: `execute_query` and `explain_analyze` endpoints now require explicit opt-in
25
63
  - Both endpoints return 403 Forbidden when `allow_raw_query_execution` is `false` (default)
26
- - Dashboard "Execute Query" and "EXPLAIN ANALYZE" buttons disabled by default
27
64
  - To restore previous behavior, add to initializer: `config.allow_raw_query_execution = true`
28
65
  - **Migration path**: Users must explicitly enable this feature if they were using Query Analyzer
66
+ - **UI/UX Improvements**:
67
+ - Query Analyzer modal size increased: width 600px→900px, height 80vh→90vh for better query visibility
68
+ - "EXPLAIN ANALYZE", "Execute Query", and "Create Migration" buttons now show active warnings when clicked (instead of being disabled)
69
+ - Warning messages include configuration instructions with code examples
70
+ - Better visual feedback for disabled features
71
+ - **Query Execution Flow**:
72
+ - `execute_query` and `explain_analyze` endpoints now accept `query_hash` parameter (instead of `query`)
73
+ - New helper methods: `store_query_with_hash()` and `retrieve_query_by_hash()`
74
+ - Frontend stores `data-query-hash` attribute on EXPLAIN ANALYZE buttons
75
+ - JavaScript validation happens client-side before API calls
29
76
 
30
77
  ### Security
31
78
 
32
- - Raw SQL execution from dashboard is now **disabled by default** to prevent unauthorized data access
79
+ - **Critical Security Enhancement**: Raw SQL execution from dashboard is now **disabled by default** to prevent unauthorized data access
80
+ - **Query Tampering Prevention**: Frontend cannot modify queries - hash-based verification ensures query integrity
81
+ - **SQL Injection Protection**: Strict validation on backend prevents any non-SELECT queries or dangerous keywords
82
+ - **Multiple Statement Prevention**: Semicolon detection blocks SQL injection attempts with multiple statements
83
+ - **Cache Dependency**: Query execution temporarily disabled if Redis/cache backend is unavailable (fail-secure)
33
84
  - Recommended setup: only enable in development/staging environments
34
- - Existing safety measures (SELECT/SHOW only, automatic LIMIT) still apply when enabled
85
+ - Existing safety measures (automatic LIMIT) still apply when enabled
35
86
 
36
87
  ## [0.5.0] - 2026-02-07
37
88
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # PgReports
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/pg_reports.svg)](https://badge.fury.io/rb/pg_reports)
3
+ [![Gem Version](https://img.shields.io/gem/v/pg_reports.svg)](https://rubygems.org/gems/pg_reports)
4
4
  [![Ruby](https://img.shields.io/badge/Ruby-2.7%2B-red.svg)](https://www.ruby-lang.org/)
5
5
  [![Rails](https://img.shields.io/badge/Rails-5.0%2B-red.svg)](https://rubyonrails.org/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -277,6 +277,8 @@ For query analysis, you need to enable `pg_stat_statements`:
277
277
  PgReports.enable_pg_stat_statements!
278
278
  ```
279
279
 
280
+ > **Note**: PgReports does **not** require the `pg_read_all_settings` role. It detects `pg_stat_statements` availability by directly querying the extension, making it compatible with CloudnativePG, managed databases, and other environments with restricted permissions.
281
+
280
282
  ## Report Object
281
283
 
282
284
  Every method returns a `PgReports::Report` object:
@@ -776,6 +776,53 @@
776
776
  text-decoration: underline;
777
777
  }
778
778
 
779
+ /* IDE Dropdown for query source links */
780
+ .ide-dropdown-overlay {
781
+ position: fixed;
782
+ inset: 0;
783
+ z-index: 999;
784
+ display: none;
785
+ }
786
+
787
+ .ide-dropdown-overlay.show {
788
+ display: block;
789
+ }
790
+
791
+ .ide-menu {
792
+ position: fixed;
793
+ background: var(--bg-card);
794
+ border: 1px solid var(--border-color);
795
+ border-radius: 6px;
796
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
797
+ z-index: 1000;
798
+ min-width: 160px;
799
+ overflow: hidden;
800
+ display: none;
801
+ }
802
+
803
+ .ide-menu.show {
804
+ display: block;
805
+ }
806
+
807
+ .ide-menu a {
808
+ display: block;
809
+ padding: 0.6rem 0.875rem;
810
+ color: var(--text-secondary);
811
+ text-decoration: none;
812
+ font-size: 0.75rem;
813
+ transition: all 0.15s ease;
814
+ border-bottom: 1px solid var(--border-color);
815
+ }
816
+
817
+ .ide-menu a:last-child {
818
+ border-bottom: none;
819
+ }
820
+
821
+ .ide-menu a:hover {
822
+ background: var(--bg-tertiary);
823
+ color: var(--text-primary);
824
+ }
825
+
779
826
  /* Download Dropdown */
780
827
  .download-dropdown {
781
828
  position: relative;
@@ -831,6 +878,58 @@
831
878
  opacity: 0.9;
832
879
  transform: translateY(-1px);
833
880
  }
881
+
882
+ /* IDE Settings Modal */
883
+ .settings-label {
884
+ color: var(--text-secondary);
885
+ margin-bottom: 1rem;
886
+ font-size: 0.9rem;
887
+ }
888
+
889
+ .ide-options {
890
+ display: flex;
891
+ flex-direction: column;
892
+ gap: 0.5rem;
893
+ }
894
+
895
+ .ide-option {
896
+ display: flex;
897
+ align-items: center;
898
+ gap: 0.75rem;
899
+ padding: 0.75rem 1rem;
900
+ background: var(--bg-tertiary);
901
+ border: 1px solid var(--border-color);
902
+ border-radius: 8px;
903
+ cursor: pointer;
904
+ transition: all 0.15s;
905
+ }
906
+
907
+ .ide-option:hover {
908
+ background: var(--bg-secondary);
909
+ border-color: var(--accent-purple);
910
+ }
911
+
912
+ .ide-option:has(input:checked) {
913
+ background: rgba(157, 140, 214, 0.15);
914
+ border-color: var(--accent-purple);
915
+ }
916
+
917
+ .ide-option input[type="radio"] {
918
+ accent-color: var(--accent-purple);
919
+ }
920
+
921
+ .ide-option span {
922
+ color: var(--text-secondary);
923
+ font-size: 0.875rem;
924
+ }
925
+
926
+ .ide-option:has(input:checked) span {
927
+ color: var(--text-primary);
928
+ }
929
+
930
+ .modal-small {
931
+ max-width: 360px;
932
+ }
834
933
  </style>
835
934
  </head>
836
935
  <body>
@@ -840,6 +939,10 @@
840
939
 
841
940
  <div id="toast" class="toast"></div>
842
941
 
942
+ <!-- IDE Dropdown overlay and menu -->
943
+ <div id="ide-dropdown-overlay" class="ide-dropdown-overlay" onclick="closeIdeMenu()"></div>
944
+ <div id="ide-menu" class="ide-menu"></div>
945
+
843
946
  <script>
844
947
  const pgReportsRoot = document.querySelector('meta[name="pg-reports-root"]')?.content || '/pg_reports';
845
948
 
@@ -20,6 +20,7 @@
20
20
  </button>
21
21
  <button class="btn-info" onclick="showPgStatInfo()">?</button>
22
22
  <% end %>
23
+ <button class="btn-info" onclick="showIdeSettingsModal()" title="IDE Settings">⚙️</button>
23
24
  <div class="header-badge" id="pg-stat-badge">
24
25
  <% if @pg_stat_status[:ready] %>
25
26
  <span class="badge-dot"></span>
@@ -85,6 +86,49 @@ pg_stat_statements.track = all</pre>
85
86
  </div>
86
87
  </div>
87
88
 
89
+ <!-- IDE Settings Modal -->
90
+ <div id="ide-settings-modal" class="modal" style="display: none;">
91
+ <div class="modal-content modal-small">
92
+ <div class="modal-header">
93
+ <h3>⚙️ IDE Settings</h3>
94
+ <button class="modal-close" onclick="closeIdeSettingsModal()">&times;</button>
95
+ </div>
96
+ <div class="modal-body">
97
+ <p class="settings-label">Default IDE for source links:</p>
98
+ <div class="ide-options">
99
+ <label class="ide-option">
100
+ <input type="radio" name="default-ide" value="" onchange="setDefaultIde('')">
101
+ <span>Show menu (default)</span>
102
+ </label>
103
+ <label class="ide-option">
104
+ <input type="radio" name="default-ide" value="vscode-wsl" onchange="setDefaultIde('vscode-wsl')">
105
+ <span>VS Code (WSL)</span>
106
+ </label>
107
+ <label class="ide-option">
108
+ <input type="radio" name="default-ide" value="vscode" onchange="setDefaultIde('vscode')">
109
+ <span>VS Code</span>
110
+ </label>
111
+ <label class="ide-option">
112
+ <input type="radio" name="default-ide" value="rubymine" onchange="setDefaultIde('rubymine')">
113
+ <span>RubyMine</span>
114
+ </label>
115
+ <label class="ide-option">
116
+ <input type="radio" name="default-ide" value="intellij" onchange="setDefaultIde('intellij')">
117
+ <span>IntelliJ IDEA</span>
118
+ </label>
119
+ <label class="ide-option">
120
+ <input type="radio" name="default-ide" value="cursor-wsl" onchange="setDefaultIde('cursor-wsl')">
121
+ <span>Cursor (WSL)</span>
122
+ </label>
123
+ <label class="ide-option">
124
+ <input type="radio" name="default-ide" value="cursor" onchange="setDefaultIde('cursor')">
125
+ <span>Cursor</span>
126
+ </label>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
88
132
  <!-- Live Monitoring Panel -->
89
133
  <div id="live-monitoring" class="live-monitoring-panel" style="display: none;">
90
134
  <div class="live-monitoring-header">
@@ -1131,6 +1175,159 @@ pg_stat_statements.track = all</pre>
1131
1175
  document.addEventListener('DOMContentLoaded', initLiveMonitoring);
1132
1176
  window.addEventListener('beforeunload', stopPolling);
1133
1177
 
1178
+ // ============================================
1179
+ // IDE Integration
1180
+ // ============================================
1181
+
1182
+ const railsRootPath = '<%= Rails.root.to_s %>';
1183
+ const wslDistro = 'Ubuntu';
1184
+
1185
+ const ideKeyMap = {
1186
+ 'vscode-wsl': 0,
1187
+ 'vscode': 1,
1188
+ 'rubymine': 2,
1189
+ 'intellij': 3,
1190
+ 'cursor-wsl': 4,
1191
+ 'cursor': 5
1192
+ };
1193
+
1194
+ function getDefaultIde() {
1195
+ return localStorage.getItem('pgReportsDefaultIde') || '';
1196
+ }
1197
+
1198
+ function generateIdeUrls(filePath, lineNumber) {
1199
+ if (!filePath) return [];
1200
+
1201
+ // Convert relative paths to absolute paths using Rails.root
1202
+ let absolutePath = filePath;
1203
+ if (!filePath.startsWith('/')) {
1204
+ absolutePath = `${railsRootPath}/${filePath}`;
1205
+ }
1206
+
1207
+ const line = lineNumber || 1;
1208
+ const urls = [];
1209
+
1210
+ // VSCode (WSL Remote format)
1211
+ urls.push({
1212
+ name: 'VS Code (WSL)',
1213
+ url: `vscode://vscode-remote/wsl+${wslDistro}${absolutePath}:${line}`
1214
+ });
1215
+
1216
+ // VSCode (direct path)
1217
+ urls.push({
1218
+ name: 'VS Code',
1219
+ url: `vscode://file${absolutePath}:${line}`
1220
+ });
1221
+
1222
+ // RubyMine
1223
+ urls.push({
1224
+ name: 'RubyMine',
1225
+ url: `rubymine://open?file=${absolutePath}&line=${line}`
1226
+ });
1227
+
1228
+ // IntelliJ
1229
+ urls.push({
1230
+ name: 'IntelliJ',
1231
+ url: `idea://open?file=${absolutePath}&line=${line}`
1232
+ });
1233
+
1234
+ // Cursor (WSL Remote format)
1235
+ urls.push({
1236
+ name: 'Cursor (WSL)',
1237
+ url: `cursor://vscode-remote/wsl+${wslDistro}${absolutePath}:${line}`
1238
+ });
1239
+
1240
+ // Cursor (direct path)
1241
+ urls.push({
1242
+ name: 'Cursor',
1243
+ url: `cursor://file${absolutePath}:${line}`
1244
+ });
1245
+
1246
+ return urls;
1247
+ }
1248
+
1249
+ function openInIDE(filePath, lineNumber, event) {
1250
+ if (event) {
1251
+ event.preventDefault();
1252
+ event.stopPropagation();
1253
+ }
1254
+
1255
+ const ideUrls = generateIdeUrls(filePath, lineNumber);
1256
+ if (ideUrls.length === 0) return;
1257
+
1258
+ const defaultIde = getDefaultIde();
1259
+
1260
+ // If default IDE is set, open directly
1261
+ if (defaultIde && ideKeyMap[defaultIde] !== undefined) {
1262
+ const ideUrl = ideUrls[ideKeyMap[defaultIde]];
1263
+ if (ideUrl) {
1264
+ window.location.href = ideUrl.url;
1265
+ return;
1266
+ }
1267
+ }
1268
+
1269
+ // No default IDE - show dropdown menu
1270
+ const menu = document.getElementById('ide-menu');
1271
+ const overlay = document.getElementById('ide-dropdown-overlay');
1272
+
1273
+ let menuHtml = '';
1274
+ ideUrls.forEach(ide => {
1275
+ menuHtml += `<a href="${ide.url}" onclick="closeIdeMenu()">${ide.name}</a>`;
1276
+ });
1277
+ menu.innerHTML = menuHtml;
1278
+
1279
+ // Position menu near the clicked element
1280
+ if (event && event.target) {
1281
+ const rect = event.target.getBoundingClientRect();
1282
+ menu.style.top = (rect.bottom + 4) + 'px';
1283
+ menu.style.left = rect.left + 'px';
1284
+ }
1285
+
1286
+ // Show menu and overlay
1287
+ menu.classList.add('show');
1288
+ overlay.classList.add('show');
1289
+ }
1290
+
1291
+ function closeIdeMenu() {
1292
+ document.getElementById('ide-menu')?.classList.remove('show');
1293
+ document.getElementById('ide-dropdown-overlay')?.classList.remove('show');
1294
+ }
1295
+
1296
+ function showIdeSettingsModal() {
1297
+ const modal = document.getElementById('ide-settings-modal');
1298
+ modal.style.display = 'flex';
1299
+ loadIdeSettingsState();
1300
+ }
1301
+
1302
+ function closeIdeSettingsModal() {
1303
+ document.getElementById('ide-settings-modal').style.display = 'none';
1304
+ }
1305
+
1306
+ function setDefaultIde(ideKey) {
1307
+ if (ideKey) {
1308
+ localStorage.setItem('pgReportsDefaultIde', ideKey);
1309
+ } else {
1310
+ localStorage.removeItem('pgReportsDefaultIde');
1311
+ }
1312
+ }
1313
+
1314
+ function loadIdeSettingsState() {
1315
+ const currentIde = getDefaultIde();
1316
+ const radios = document.querySelectorAll('input[name="default-ide"]');
1317
+ radios.forEach(radio => {
1318
+ radio.checked = (radio.value === currentIde);
1319
+ });
1320
+ }
1321
+
1322
+ // Close IDE settings modal on backdrop click
1323
+ document.addEventListener('DOMContentLoaded', function() {
1324
+ document.getElementById('ide-settings-modal')?.addEventListener('click', function(e) {
1325
+ if (e.target === this) {
1326
+ closeIdeSettingsModal();
1327
+ }
1328
+ });
1329
+ });
1330
+
1134
1331
  // ============================================
1135
1332
  // Query Monitoring
1136
1333
  // ============================================
@@ -1405,7 +1602,7 @@ pg_stat_statements.track = all</pre>
1405
1602
  if (query.source_location && query.source_location.file) {
1406
1603
  const file = query.source_location.file;
1407
1604
  const line = query.source_location.line;
1408
- sourceInfo = `<a href="#" onclick="openInIDE('${escapeHtml(file)}', ${line}); return false;">
1605
+ sourceInfo = `<a href="#" onclick="openInIDE('${escapeHtml(file)}', ${line}, event); return false;">
1409
1606
  ${escapeHtml(file)}:${line}
1410
1607
  </a>`;
1411
1608
  }
@@ -25,14 +25,19 @@ module PgReports
25
25
  result.first&.fetch("available", false) || false
26
26
  end
27
27
 
28
- # Check if pg_stat_statements is in shared_preload_libraries
28
+ # Check if pg_stat_statements is preloaded and functional
29
29
  # @return [Boolean] Whether pg_stat_statements is preloaded
30
+ # @note This method tries to query pg_stat_statements directly instead of
31
+ # checking shared_preload_libraries, which requires pg_read_all_settings role
30
32
  def pg_stat_statements_preloaded?
31
- result = executor.execute(<<~SQL)
32
- SELECT setting FROM pg_settings WHERE name = 'shared_preload_libraries'
33
- SQL
34
- setting = result.first&.fetch("setting", "") || ""
35
- setting.include?("pg_stat_statements")
33
+ # If extension is not installed, it can't be preloaded
34
+ return false unless pg_stat_statements_available?
35
+
36
+ # Try to query pg_stat_statements - if it works, it's properly preloaded
37
+ executor.execute("SELECT 1 FROM pg_stat_statements LIMIT 1")
38
+ true
39
+ rescue
40
+ false
36
41
  end
37
42
 
38
43
  # Get pg_stat_statements status details
@@ -94,14 +99,20 @@ module PgReports
94
99
  executor.execute("CREATE EXTENSION IF NOT EXISTS pg_stat_statements")
95
100
 
96
101
  # Verify it worked
97
- if pg_stat_statements_available?
102
+ if pg_stat_statements_available? && pg_stat_statements_preloaded?
98
103
  {success: true, message: "pg_stat_statements extension created successfully"}
99
- else
104
+ elsif pg_stat_statements_available?
100
105
  {
101
106
  success: false,
102
- message: "Extension created but not working. Check shared_preload_libraries in postgresql.conf",
107
+ message: "Extension created but not preloaded. Add 'pg_stat_statements' to shared_preload_libraries in postgresql.conf and restart PostgreSQL.",
103
108
  requires_restart: true
104
109
  }
110
+ else
111
+ {
112
+ success: false,
113
+ message: "Failed to create extension. Check database permissions.",
114
+ requires_restart: false
115
+ }
105
116
  end
106
117
  rescue => e
107
118
  error_message = e.message
@@ -200,10 +200,10 @@ module PgReports
200
200
  # Filter queries from pg_reports internal modules only:
201
201
  # - Installed gem: /gems/pg_reports-X.Y.Z/lib/
202
202
  # - Local gem: /pg_reports/lib/pg_reports/modules/
203
- # - Dashboard controller: /pg_reports/app/controllers/pg_reports/
203
+ # Note: We intentionally DO NOT filter dashboard_controller.rb
204
+ # to allow monitoring of user application queries made during dashboard page loads
204
205
  path.match?(%r{/gems/pg_reports[-\d.]+/lib/}) ||
205
- path.match?(%r{/pg_reports/lib/pg_reports/modules/}) ||
206
- path.match?(%r{/pg_reports/app/controllers/pg_reports/dashboard_controller\.rb})
206
+ path.match?(%r{/pg_reports/lib/pg_reports/modules/})
207
207
  end
208
208
  end
209
209
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgReports
4
- VERSION = "0.5.1"
4
+ VERSION = "0.5.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_reports
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eldar Avatov