rails_pulse 0.1.1 → 0.1.2

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -176
  3. data/Rakefile +77 -2
  4. data/app/assets/stylesheets/rails_pulse/application.css +0 -12
  5. data/app/controllers/concerns/chart_table_concern.rb +21 -4
  6. data/app/controllers/concerns/response_range_concern.rb +6 -3
  7. data/app/controllers/concerns/time_range_concern.rb +5 -10
  8. data/app/controllers/concerns/zoom_range_concern.rb +1 -1
  9. data/app/controllers/rails_pulse/application_controller.rb +8 -4
  10. data/app/controllers/rails_pulse/dashboard_controller.rb +12 -0
  11. data/app/controllers/rails_pulse/queries_controller.rb +65 -50
  12. data/app/controllers/rails_pulse/requests_controller.rb +24 -12
  13. data/app/controllers/rails_pulse/routes_controller.rb +59 -24
  14. data/app/helpers/rails_pulse/application_helper.rb +0 -1
  15. data/app/helpers/rails_pulse/chart_formatters.rb +3 -3
  16. data/app/helpers/rails_pulse/chart_helper.rb +6 -2
  17. data/app/helpers/rails_pulse/status_helper.rb +10 -4
  18. data/app/javascript/rails_pulse/controllers/index_controller.js +117 -33
  19. data/app/javascript/rails_pulse/controllers/pagination_controller.js +17 -27
  20. data/app/jobs/rails_pulse/backfill_summaries_job.rb +41 -0
  21. data/app/jobs/rails_pulse/summary_job.rb +53 -0
  22. data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +28 -7
  23. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +18 -22
  24. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +18 -7
  25. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +34 -41
  26. data/app/models/rails_pulse/operation.rb +1 -1
  27. data/app/models/rails_pulse/queries/cards/average_query_times.rb +47 -23
  28. data/app/models/rails_pulse/queries/cards/execution_rate.rb +33 -26
  29. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +34 -45
  30. data/app/models/rails_pulse/queries/charts/average_query_times.rb +23 -97
  31. data/app/models/rails_pulse/queries/tables/index.rb +74 -0
  32. data/app/models/rails_pulse/query.rb +1 -0
  33. data/app/models/rails_pulse/requests/charts/average_response_times.rb +23 -84
  34. data/app/models/rails_pulse/route.rb +1 -6
  35. data/app/models/rails_pulse/routes/cards/average_response_times.rb +45 -23
  36. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +38 -45
  37. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +34 -47
  38. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +30 -25
  39. data/app/models/rails_pulse/routes/charts/average_response_times.rb +23 -100
  40. data/app/models/rails_pulse/routes/tables/index.rb +57 -40
  41. data/app/models/rails_pulse/summary.rb +143 -0
  42. data/app/services/rails_pulse/summary_service.rb +199 -0
  43. data/app/views/layouts/rails_pulse/application.html.erb +4 -4
  44. data/app/views/rails_pulse/components/_empty_state.html.erb +11 -0
  45. data/app/views/rails_pulse/components/_metric_card.html.erb +10 -24
  46. data/app/views/rails_pulse/dashboard/index.html.erb +54 -36
  47. data/app/views/rails_pulse/queries/_show_table.html.erb +1 -1
  48. data/app/views/rails_pulse/queries/_table.html.erb +10 -12
  49. data/app/views/rails_pulse/queries/index.html.erb +41 -34
  50. data/app/views/rails_pulse/queries/show.html.erb +38 -31
  51. data/app/views/rails_pulse/requests/_operations.html.erb +32 -26
  52. data/app/views/rails_pulse/requests/_table.html.erb +1 -3
  53. data/app/views/rails_pulse/requests/index.html.erb +42 -34
  54. data/app/views/rails_pulse/routes/_table.html.erb +13 -13
  55. data/app/views/rails_pulse/routes/index.html.erb +43 -35
  56. data/app/views/rails_pulse/routes/show.html.erb +42 -35
  57. data/config/initializers/rails_pulse.rb +0 -12
  58. data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +54 -0
  59. data/db/rails_pulse_schema.rb +121 -0
  60. data/lib/generators/rails_pulse/install_generator.rb +41 -4
  61. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +60 -0
  62. data/lib/generators/rails_pulse/templates/rails_pulse.rb +0 -12
  63. data/lib/rails_pulse/configuration.rb +0 -11
  64. data/lib/rails_pulse/engine.rb +0 -1
  65. data/lib/rails_pulse/version.rb +1 -1
  66. data/lib/tasks/rails_pulse.rake +58 -0
  67. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  68. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  69. data/public/rails-pulse-assets/rails-pulse.js +1 -1
  70. data/public/rails-pulse-assets/rails-pulse.js.map +3 -3
  71. data/public/rails-pulse-assets/search.svg +43 -0
  72. metadata +27 -11
  73. data/app/controllers/rails_pulse/caches_controller.rb +0 -115
  74. data/app/helpers/rails_pulse/cached_component_helper.rb +0 -73
  75. data/app/models/rails_pulse/component_cache_key.rb +0 -33
  76. data/app/views/rails_pulse/caches/show.html.erb +0 -9
  77. data/db/migrate/20250227235904_create_routes.rb +0 -12
  78. data/db/migrate/20250227235915_create_requests.rb +0 -19
  79. data/db/migrate/20250228000000_create_queries.rb +0 -14
  80. data/db/migrate/20250228000056_create_operations.rb +0 -24
  81. data/lib/rails_pulse/migration.rb +0 -29
@@ -0,0 +1,43 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="748.974" height="457.275" viewBox="0 0 748.974 457.275" xmlns:xlink="http://www.w3.org/1999/xlink" role="img" artist="Katerina Limpitsouni" source="https://undraw.co/">
2
+ <g id="Group_201" data-name="Group 201" transform="translate(-382.003 -195.455)">
3
+ <g id="Group_200" data-name="Group 200" transform="translate(382.003 195.455)">
4
+ <path id="Path_3120-1325" data-name="Path 3120" d="M695.225,508.82,433.394,576.244a34.622,34.622,0,0,1-42.114-24.866L312.1,243.879a34.622,34.622,0,0,1,24.866-42.114l243.591-62.727L642.9,166.948l77.191,299.757A34.622,34.622,0,0,1,695.225,508.82Z" transform="translate(-311.003 -139.037)" fill="#f2f2f2"/>
5
+ <path id="Path_3121-1326" data-name="Path 3121" d="M338.989,210.925a24.655,24.655,0,0,0-17.708,29.99l79.185,307.5a24.655,24.655,0,0,0,29.99,17.708L692.287,498.7a24.655,24.655,0,0,0,17.708-29.99L634,173.595l-54.792-24.529Z" transform="translate(-310.548 -138.556)" fill="#fff"/>
6
+ <path id="Path_3122-1327" data-name="Path 3122" d="M629.927,168.5l-40.522,10.435a11.518,11.518,0,0,1-14.026-8.282l-7.707-29.929a.72.72,0,0,1,.989-.837l61.379,27.258a.72.72,0,0,1-.113,1.355Z" transform="translate(-298.695 -139)" fill="#f2f2f2"/>
7
+ <path id="Path_3123-1328" data-name="Path 3123" d="M612.519,418.284l-119.208,30.7a5.759,5.759,0,0,1-2.872-11.154l119.208-30.7a5.759,5.759,0,1,1,2.872,11.154Z" transform="translate(-302.605 -126.189)" fill="#ccc"/>
8
+ <path id="Path_3124-1329" data-name="Path 3124" d="M640.149,430.592,497.936,467.214a5.759,5.759,0,1,1-2.872-11.154l142.213-36.622a5.759,5.759,0,0,1,2.872,11.154Z" transform="translate(-302.384 -125.599)" fill="#ccc"/>
9
+ <circle id="Ellipse_44" data-name="Ellipse 44" cx="20.355" cy="20.355" r="20.355" transform="translate(121.697 319.055)" fill="#FFC91F"/>
10
+ <path id="Path_3125-1330" data-name="Path 3125" d="M604.421,374.437,446.1,415.191a17.835,17.835,0,0,1-21.694-12.812L391.229,273.49A17.835,17.835,0,0,1,404.041,251.8l158.32-40.754a17.835,17.835,0,0,1,21.694,12.812l33.178,128.889A17.835,17.835,0,0,1,604.421,374.437Z" transform="translate(-307.183 -135.611)" fill="#fff"/>
11
+ <path id="Path_3126-1331" data-name="Path 3126" d="M604.421,374.437,446.1,415.191a17.835,17.835,0,0,1-21.694-12.812L391.229,273.49A17.835,17.835,0,0,1,404.041,251.8l158.32-40.754a17.835,17.835,0,0,1,21.694,12.812l33.178,128.889A17.835,17.835,0,0,1,604.421,374.437ZM404.563,253.826a15.737,15.737,0,0,0-11.3,19.142l33.178,128.889a15.737,15.737,0,0,0,19.142,11.3L603.9,372.407a15.737,15.737,0,0,0,11.3-19.142L582.025,224.376a15.737,15.737,0,0,0-19.142-11.3Z" transform="translate(-307.183 -135.611)" fill="#e6e6e6"/>
12
+ <path id="Path_411-1332" data-name="Path 411" d="M550.66,252.63l-79.9,20.568a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l81.335-20.937c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-303.514 -133.861)" fill="#f2f2f2"/>
13
+ <path id="Path_412-1333" data-name="Path 412" d="M554.1,266l-79.9,20.568a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l81.335-20.937c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-303.349 -133.22)" fill="#f2f2f2"/>
14
+ <path id="Path_413-1334" data-name="Path 413" d="M461.146,298.825,436.761,305.1a3.1,3.1,0,0,1-3.776-2.23L425.577,274.1a3.1,3.1,0,0,1,2.23-3.776l24.385-6.277a3.105,3.105,0,0,1,3.776,2.23l7.408,28.777a3.1,3.1,0,0,1-2.23,3.776Z" transform="translate(-305.513 -133.047)" fill="#FFC91F"/>
15
+ <path id="Path_414-1335" data-name="Path 414" d="M562.854,293.445,440.909,324.835a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-304.946 -131.904)" fill="#f2f2f2"/>
16
+ <path id="Path_415-1336" data-name="Path 415" d="M566.3,306.822,444.353,338.213a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-304.781 -131.263)" fill="#f2f2f2"/>
17
+ <path id="Path_416-1337" data-name="Path 416" d="M569.739,320.192,447.794,351.582a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.379-31.76c3.286,1.665,2.421,5.07.091,5.67Z" transform="translate(-304.616 -130.621)" fill="#f2f2f2"/>
18
+ <path id="Path_417-1338" data-name="Path 417" d="M573.183,333.569,451.237,364.959a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76C576.377,329.564,575.513,332.969,573.183,333.569Z" transform="translate(-304.45 -129.98)" fill="#f2f2f2"/>
19
+ <path id="Path_418-1339" data-name="Path 418" d="M576.624,346.939,454.679,378.329a2.862,2.862,0,0,1-3.467-1.8,2.757,2.757,0,0,1,1.942-3.5l123.38-31.76C579.819,342.934,578.954,346.339,576.624,346.939Z" transform="translate(-304.285 -129.339)" fill="#f2f2f2"/>
20
+ <path id="Path_395-1340" data-name="Path 395" d="M448.363,470.511a2.111,2.111,0,0,1-1.335-.092l-.026-.011-5.545-2.351a2.126,2.126,0,1,1,1.664-3.913l3.593,1.528,4.708-11.076a2.125,2.125,0,0,1,2.787-1.124h0l-.028.072.029-.073a2.127,2.127,0,0,1,1.124,2.788l-5.539,13.023a2.126,2.126,0,0,1-1.431,1.224Z" transform="translate(-304.809 -123.966)" fill="#fff"/>
21
+ </g>
22
+ <g id="Group_199" data-name="Group 199" transform="translate(673.007 225.872) rotate(-8)">
23
+ <g id="Group_198" data-name="Group 198" transform="translate(125.896 0) rotate(19)">
24
+ <path id="Path_3127-1341" data-name="Path 3127" d="M304.956,386.7H34.583A34.622,34.622,0,0,1,0,352.114V34.583A34.622,34.622,0,0,1,34.583,0H286.121l53.418,42.577V352.114A34.622,34.622,0,0,1,304.956,386.7Z" transform="translate(0 0)" fill="#e6e6e6"/>
25
+ <path id="Path_3128-1342" data-name="Path 3128" d="M24.627,0A24.655,24.655,0,0,0,0,24.627V342.158a24.655,24.655,0,0,0,24.627,24.627H295a24.655,24.655,0,0,0,24.627-24.627V37.418L272.683,0Z" transform="translate(9.956 9.956)" fill="#fff"/>
26
+ <path id="Path_3129-1343" data-name="Path 3129" d="M128.856,11.518H5.759A5.759,5.759,0,0,1,5.759,0h123.1a5.759,5.759,0,0,1,0,11.518Z" transform="translate(123.512 90.767)" fill="#FFC91F"/>
27
+ <path id="Path_3130-1344" data-name="Path 3130" d="M152.612,11.518H5.759A5.759,5.759,0,0,1,5.759,0H152.612a5.759,5.759,0,1,1,0,11.518Z" transform="translate(123.512 110.204)" fill="#FFC91F"/>
28
+ <path id="Path_3131-1345" data-name="Path 3131" d="M128.852,0H5.758a5.758,5.758,0,1,0,0,11.517H128.852a5.759,5.759,0,0,0,0-11.517Z" transform="translate(123.517 177.868)" fill="#ccc"/>
29
+ <path id="Path_3132-1346" data-name="Path 3132" d="M152.609,0H5.758a5.759,5.759,0,1,0,0,11.517h146.85a5.759,5.759,0,1,0,0-11.517Z" transform="translate(123.517 197.307)" fill="#ccc"/>
30
+ <path id="Path_3133-1347" data-name="Path 3133" d="M128.856,11.518H5.759A5.759,5.759,0,0,1,5.759,0h123.1a5.759,5.759,0,0,1,0,11.518Z" transform="translate(123.512 264.975)" fill="#ccc"/>
31
+ <path id="Path_3134-1348" data-name="Path 3134" d="M152.612,11.518H5.759A5.759,5.759,0,0,1,5.759,0H152.612a5.759,5.759,0,1,1,0,11.518Z" transform="translate(123.512 284.411)" fill="#ccc"/>
32
+ <circle id="Ellipse_44-2" data-name="Ellipse 44" cx="20.355" cy="20.355" r="20.355" transform="translate(57.655 85.89)" fill="#FFC91F"/>
33
+ <path id="Path_395-2-1349" data-name="Path 395" d="M6.909,15.481a2.111,2.111,0,0,1-1.27-.422l-.023-.017L.832,11.382A2.126,2.126,0,0,1,3.419,8.008l3.1,2.376L13.839.832A2.125,2.125,0,0,1,16.819.439h0L16.774.5l.047-.063a2.127,2.127,0,0,1,.393,2.98L8.6,14.649a2.126,2.126,0,0,1-1.691.829Z" transform="translate(69.085 98.528)" fill="#fff"/>
34
+ <path id="Path_3135-1350" data-name="Path 3135" d="M40.707,20.359A20.354,20.354,0,0,1,20.356,40.721a4.372,4.372,0,0,1-.524-.021A20.353,20.353,0,1,1,40.707,20.359Z" transform="translate(59.75 172.987)" fill="#FFC91F"/>
35
+ <circle id="Ellipse_44-3" data-name="Ellipse 44" cx="20.355" cy="20.355" r="20.355" transform="translate(57.655 260.097)" fill="#FFC91F"/>
36
+ <path id="Path_3136-1351" data-name="Path 3136" d="M53.362,43.143H11.518A11.518,11.518,0,0,1,0,31.625V.72A.72.72,0,0,1,1.167.156l52.642,41.7a.72.72,0,0,1-.447,1.284Z" transform="translate(285.137 0.805)" fill="#ccc"/>
37
+ </g>
38
+ </g>
39
+ <path id="Path_3140-1352" data-name="Path 3140" d="M754.518,518.049a9.158,9.158,0,0,1-12.587,3.05L635.078,455.923a9.158,9.158,0,0,1,9.538-15.637l106.852,65.176a9.158,9.158,0,0,1,3.049,12.587Z" transform="translate(123.58 101.359)" fill="#3f3d56"/>
40
+ <path id="Path_3141-1353" data-name="Path 3141" d="M688.648,486.5a73.265,73.265,0,1,1-24.4-100.7A73.265,73.265,0,0,1,688.648,486.5ZM579.19,419.73a54.949,54.949,0,1,0,75.524-18.3,54.949,54.949,0,0,0-75.524,18.3Z" transform="translate(82.597 67.737)" fill="#3f3d56"/>
41
+ <circle id="Ellipse_44-4" data-name="Ellipse 44" cx="57.007" cy="57.007" r="57.007" transform="translate(672.542 442.858) rotate(19)" fill="#FFC91F"/>
42
+ </g>
43
+ </svg>
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_pulse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rails Pulse
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-12 00:00:00.000000000 Z
10
+ date: 2025-09-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -145,6 +145,20 @@ dependencies:
145
145
  - - "~>"
146
146
  - !ruby/object:Gem::Version
147
147
  version: '6.0'
148
+ - !ruby/object:Gem::Dependency
149
+ name: rails-controller-testing
150
+ requirement: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ type: :development
156
+ prerelease: false
157
+ version_requirements: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
148
162
  description: Ruby on Rails performance monitoring tool that provides insights into
149
163
  your application's performance, helping you identify bottlenecks and optimize your
150
164
  code for better efficiency.
@@ -190,7 +204,6 @@ files:
190
204
  - app/controllers/concerns/zoom_range_concern.rb
191
205
  - app/controllers/rails_pulse/application_controller.rb
192
206
  - app/controllers/rails_pulse/assets_controller.rb
193
- - app/controllers/rails_pulse/caches_controller.rb
194
207
  - app/controllers/rails_pulse/csp_test_controller.rb
195
208
  - app/controllers/rails_pulse/dashboard_controller.rb
196
209
  - app/controllers/rails_pulse/operations_controller.rb
@@ -199,7 +212,6 @@ files:
199
212
  - app/controllers/rails_pulse/routes_controller.rb
200
213
  - app/helpers/rails_pulse/application_helper.rb
201
214
  - app/helpers/rails_pulse/breadcrumbs_helper.rb
202
- - app/helpers/rails_pulse/cached_component_helper.rb
203
215
  - app/helpers/rails_pulse/chart_formatters.rb
204
216
  - app/helpers/rails_pulse/chart_helper.rb
205
217
  - app/helpers/rails_pulse/formatting_helper.rb
@@ -219,10 +231,11 @@ files:
219
231
  - app/javascript/rails_pulse/controllers/timezone_controller.js
220
232
  - app/javascript/rails_pulse/theme.js
221
233
  - app/jobs/rails_pulse/application_job.rb
234
+ - app/jobs/rails_pulse/backfill_summaries_job.rb
222
235
  - app/jobs/rails_pulse/cleanup_job.rb
236
+ - app/jobs/rails_pulse/summary_job.rb
223
237
  - app/mailers/rails_pulse/application_mailer.rb
224
238
  - app/models/rails_pulse/application_record.rb
225
- - app/models/rails_pulse/component_cache_key.rb
226
239
  - app/models/rails_pulse/dashboard/charts/average_response_time.rb
227
240
  - app/models/rails_pulse/dashboard/charts/p95_response_time.rb
228
241
  - app/models/rails_pulse/dashboard/tables/slow_queries.rb
@@ -232,6 +245,7 @@ files:
232
245
  - app/models/rails_pulse/queries/cards/execution_rate.rb
233
246
  - app/models/rails_pulse/queries/cards/percentile_query_times.rb
234
247
  - app/models/rails_pulse/queries/charts/average_query_times.rb
248
+ - app/models/rails_pulse/queries/tables/index.rb
235
249
  - app/models/rails_pulse/query.rb
236
250
  - app/models/rails_pulse/request.rb
237
251
  - app/models/rails_pulse/requests/charts/average_response_times.rb
@@ -243,13 +257,15 @@ files:
243
257
  - app/models/rails_pulse/routes/cards/request_count_totals.rb
244
258
  - app/models/rails_pulse/routes/charts/average_response_times.rb
245
259
  - app/models/rails_pulse/routes/tables/index.rb
260
+ - app/models/rails_pulse/summary.rb
246
261
  - app/services/rails_pulse/sql_query_normalizer.rb
262
+ - app/services/rails_pulse/summary_service.rb
247
263
  - app/views/layouts/rails_pulse/_menu_items.html.erb
248
264
  - app/views/layouts/rails_pulse/_sidebar_menu.html.erb
249
265
  - app/views/layouts/rails_pulse/application.html.erb
250
- - app/views/rails_pulse/caches/show.html.erb
251
266
  - app/views/rails_pulse/components/_breadcrumbs.html.erb
252
267
  - app/views/rails_pulse/components/_code_panel.html.erb
268
+ - app/views/rails_pulse/components/_empty_state.html.erb
253
269
  - app/views/rails_pulse/components/_metric_card.html.erb
254
270
  - app/views/rails_pulse/components/_metric_row.html.erb
255
271
  - app/views/rails_pulse/components/_operation_details_popover.html.erb
@@ -289,11 +305,10 @@ files:
289
305
  - config/initializers/rails_charts_csp_patch.rb
290
306
  - config/initializers/rails_pulse.rb
291
307
  - config/routes.rb
292
- - db/migrate/20250227235904_create_routes.rb
293
- - db/migrate/20250227235915_create_requests.rb
294
- - db/migrate/20250228000000_create_queries.rb
295
- - db/migrate/20250228000056_create_operations.rb
308
+ - db/migrate/20241222000001_create_rails_pulse_summaries.rb
309
+ - db/rails_pulse_schema.rb
296
310
  - lib/generators/rails_pulse/install_generator.rb
311
+ - lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb
297
312
  - lib/generators/rails_pulse/templates/rails_pulse.rb
298
313
  - lib/rails_pulse.rb
299
314
  - lib/rails_pulse/cleanup_service.rb
@@ -301,9 +316,9 @@ files:
301
316
  - lib/rails_pulse/engine.rb
302
317
  - lib/rails_pulse/middleware/asset_server.rb
303
318
  - lib/rails_pulse/middleware/request_collector.rb
304
- - lib/rails_pulse/migration.rb
305
319
  - lib/rails_pulse/subscribers/operation_subscriber.rb
306
320
  - lib/rails_pulse/version.rb
321
+ - lib/tasks/rails_pulse.rake
307
322
  - lib/tasks/rails_pulse_tasks.rake
308
323
  - public/rails-pulse-assets/csp-test.js
309
324
  - public/rails-pulse-assets/rails-pulse-icons.js
@@ -312,6 +327,7 @@ files:
312
327
  - public/rails-pulse-assets/rails-pulse.css.map
313
328
  - public/rails-pulse-assets/rails-pulse.js
314
329
  - public/rails-pulse-assets/rails-pulse.js.map
330
+ - public/rails-pulse-assets/search.svg
315
331
  homepage: https://www.railspulse.com
316
332
  licenses:
317
333
  - MIT
@@ -1,115 +0,0 @@
1
- module RailsPulse
2
- class CachesController < ApplicationController
3
- def show
4
- @component_id = params[:id]
5
- @context = params[:context]
6
- @cache_key = ComponentCacheKey.build(@component_id, @context)
7
-
8
- # Preserve component options before refresh
9
- existing_options = {}
10
- if params[:refresh]
11
- existing_cache = Rails.cache.read(@cache_key)
12
- existing_options = existing_cache[:component_options] if existing_cache&.dig(:component_options)
13
- Rails.cache.delete(@cache_key)
14
- end
15
-
16
- # Check if cache exists with just options (from render_skeleton_with_frame)
17
- cached_data = Rails.cache.read(@cache_key)
18
- if cached_data && !cached_data[:component_data]
19
- # Merge options with full data
20
- cached_data = {
21
- component_data: calculate_component_data,
22
- cached_at: Time.current,
23
- component_options: cached_data[:component_options] || {}
24
- }
25
- Rails.cache.write(@cache_key, cached_data, expires_in: ComponentCacheKey.cache_expires_in)
26
- elsif !cached_data
27
- # No cache exists, create new one (use preserved options if refreshing)
28
- cached_data = {
29
- component_data: calculate_component_data,
30
- cached_at: Time.current,
31
- component_options: existing_options
32
- }
33
- Rails.cache.write(@cache_key, cached_data, expires_in: ComponentCacheKey.cache_expires_in)
34
- end
35
-
36
- @component_data = cached_data[:component_data]
37
- @cached_at = cached_data[:cached_at]
38
- @component_options = cached_data[:component_options] || {}
39
-
40
- # Update cached_at timestamp in component options if refresh action exists
41
- if params[:refresh] && @component_options[:actions]
42
- update_cached_at_in_actions(@component_options[:actions], @cached_at)
43
- # Update the unified cache with new cached_at timestamp
44
- cached_data[:component_options] = @component_options
45
- Rails.cache.write(@cache_key, cached_data, expires_in: ComponentCacheKey.cache_expires_in)
46
- end
47
- end
48
-
49
- private
50
-
51
- def calculate_component_data
52
- route = extract_route_from_context
53
- query = extract_query_from_context
54
-
55
- case @component_id
56
- when "average_response_times"
57
- Routes::Cards::AverageResponseTimes.new(route: route).to_metric_card
58
- when "percentile_response_times"
59
- Routes::Cards::PercentileResponseTimes.new(route: route).to_metric_card
60
- when "request_count_totals"
61
- Routes::Cards::RequestCountTotals.new(route: route).to_metric_card
62
- when "error_rate_per_route"
63
- Routes::Cards::ErrorRatePerRoute.new(route: route).to_metric_card
64
- when "average_query_times"
65
- Queries::Cards::AverageQueryTimes.new(query: query).to_metric_card
66
- when "percentile_query_times"
67
- Queries::Cards::PercentileQueryTimes.new(query: query).to_metric_card
68
- when "execution_rate"
69
- Queries::Cards::ExecutionRate.new(query: query).to_metric_card
70
- when "dashboard_average_response_time"
71
- Dashboard::Charts::AverageResponseTime.new.to_chart_data
72
- when "dashboard_p95_response_time"
73
- Dashboard::Charts::P95ResponseTime.new.to_chart_data
74
- when "dashboard_slow_routes"
75
- Dashboard::Tables::SlowRoutes.new.to_table_data
76
- when "dashboard_slow_queries"
77
- Dashboard::Tables::SlowQueries.new.to_table_data
78
- else
79
- { title: "Unknown Metric", summary: "N/A" }
80
- end
81
- end
82
-
83
- def extract_route_from_context
84
- return unless @context
85
-
86
- # Extract route ID from context like "route_123" or return nil for "routes"/"requests"
87
- if @context.match(/^route_(\d+)$/)
88
- route_id = @context.match(/^route_(\d+)$/)[1]
89
- Route.find(route_id)
90
- else
91
- nil
92
- end
93
- end
94
-
95
- def extract_query_from_context
96
- return unless @context
97
-
98
- # Extract query ID from context like "query_123" or return nil for other contexts
99
- if @context.match(/^query_(\d+)$/)
100
- query_id = @context.match(/^query_(\d+)$/)[1]
101
- Query.find(query_id)
102
- else
103
- nil
104
- end
105
- end
106
-
107
- def update_cached_at_in_actions(actions, cached_at)
108
- actions.each do |action|
109
- if action.dig(:data, :rails_pulse__timezone_cached_at_value)
110
- action[:data][:rails_pulse__timezone_cached_at_value] = cached_at.iso8601
111
- end
112
- end
113
- end
114
- end
115
- end
@@ -1,73 +0,0 @@
1
- module RailsPulse
2
- module CachedComponentHelper
3
- def cached_component(options)
4
- # cache_key = ComponentCacheKey.build(options[:id], options[:context])
5
-
6
- # Add refresh action for panels if requested
7
- if options[:refresh_action] && options[:component] == "panel"
8
- options[:actions] ||= []
9
- options[:actions] << refresh_action_params(options[:id], options[:context], options[:content_partial])
10
- end
11
-
12
- # if Rails.cache.exist?(cache_key)
13
- if false
14
- render_cached_content(options)
15
- else
16
- render_skeleton_with_frame(options)
17
- end
18
- end
19
-
20
- private
21
-
22
- def render_cached_content(options)
23
- cache_key = ComponentCacheKey.build(options[:id], options[:context])
24
- cached_data = Rails.cache.read(cache_key)
25
- @component_data = cached_data[:component_data]
26
- @cached_at = cached_data[:cached_at]
27
- component_options = cached_data[:component_options] || {}
28
-
29
- # Wrap the cached content in a Turbo Frame so it can be refreshed using a refresh link in the component
30
- turbo_frame_tag "#{options[:id]}_#{options[:component]}", class: options[:class] do
31
- render "rails_pulse/components/#{options[:component]}", component_options
32
- end
33
- end
34
-
35
- def render_skeleton_with_frame(options)
36
- # Store component options temporarily so CachesController can access them
37
- cache_key = ComponentCacheKey.build(options[:id], options[:context])
38
- Rails.cache.write(cache_key, component_options: options, expires_in: 5.minutes)
39
- path_options = options.slice :id, :context
40
-
41
- turbo_frame_tag "#{options[:id]}_#{options[:component]}",
42
- src: rails_pulse.cache_path(**path_options),
43
- loading: "eager",
44
- class: options[:class] do
45
- render "rails_pulse/skeletons/#{options[:component]}", options
46
- end
47
- end
48
-
49
- def refresh_action_params(id, context, content_partial)
50
- refresh_params = {
51
- id: id,
52
- component_type: "panel",
53
- refresh: true
54
- }
55
-
56
- # Include content_partial in refresh URL if available
57
- refresh_params[:content_partial] = content_partial if content_partial
58
-
59
- {
60
- url: rails_pulse.cache_path(refresh_params),
61
- icon: "refresh-cw",
62
- title: "Refresh data",
63
- data: {
64
- controller: "rails-pulse--timezone",
65
- rails_pulse__timezone_target_frame_value: "#{id}_panel",
66
- rails_pulse__timezone_cached_at_value: Time.current.iso8601,
67
- turbo_frame: "#{id}_panel",
68
- turbo_prefetch: "false"
69
- }
70
- }
71
- end
72
- end
73
- end
@@ -1,33 +0,0 @@
1
- module RailsPulse
2
- # Utility class for generating cache keys for cached components
3
- class ComponentCacheKey
4
- # Generate a cache key for a specific component type and context
5
- #
6
- # The cache key includes several parts:
7
- # - A namespace prefix to avoid collisions with other cached data
8
- # - The context (like "routes" or "route_123") to separate different views
9
- # - The component type (like "average_response_times") to separate different components
10
- # (Note: Time-based cache expiration is now handled via expires_in option)
11
- def self.build(id, context = nil)
12
- [ "rails_pulse_component", id, context ].compact
13
- end
14
-
15
- # Generate a cache expiration duration with jitter
16
- #
17
- # This returns a duration that can be used with the expires_in option.
18
- # We add some randomness (jitter) so all components don't expire at exactly
19
- # the same time, which would cause a "thundering herd" problem where all
20
- # components recalculate simultaneously and overwhelm the database.
21
- def self.cache_expires_in
22
- # Get the configured cache duration (e.g., 5 minutes)
23
- cache_duration = RailsPulse.configuration.component_cache_duration.to_i
24
-
25
- # Add up to 25% random jitter to spread out cache expirations
26
- max_jitter = (cache_duration * 0.25).to_i
27
- jitter = rand(max_jitter)
28
-
29
- # Return the base duration plus jitter
30
- cache_duration + jitter
31
- end
32
- end
33
- end
@@ -1,9 +0,0 @@
1
- <%
2
- id = "#{@component_id}_#{@component_options[:component]}"
3
- %>
4
- <%= turbo_frame_tag id do %>
5
- <%= render partial: "rails_pulse/components/#{@component_options[:component]}", locals: @component_options %>
6
- <script nonce="<%= rails_pulse_csp_nonce %>">
7
- initializeChartsInContainer('<%= id %>');
8
- </script>
9
- <% end %>
@@ -1,12 +0,0 @@
1
- class CreateRoutes < RailsPulse::Migration
2
- def change
3
- create_table :rails_pulse_routes do |t|
4
- t.string :method, null: false, comment: "HTTP method (e.g., GET, POST)"
5
- t.string :path, null: false, comment: "Request path (e.g., /posts/index)"
6
-
7
- t.timestamps
8
- end
9
-
10
- add_index :rails_pulse_routes, [ :method, :path ], unique: true, name: 'index_rails_pulse_routes_on_method_and_path'
11
- end
12
- end
@@ -1,19 +0,0 @@
1
- class CreateRequests < RailsPulse::Migration
2
- def change
3
- create_table :rails_pulse_requests do |t|
4
- t.references :route, null: false, foreign_key: { to_table: :rails_pulse_routes }, comment: "Link to the route"
5
- t.decimal :duration, precision: 15, scale: 6, null: false, comment: "Total request duration in milliseconds"
6
- t.integer :status, null: false, comment: "HTTP status code (e.g., 200, 500)"
7
- t.boolean :is_error, null: false, default: false, comment: "True if status >= 500"
8
- t.string :request_uuid, null: false, comment: "Unique identifier for the request (e.g., UUID)"
9
- t.string :controller_action, comment: "Controller and action handling the request (e.g., PostsController#show)"
10
- t.timestamp :occurred_at, null: false, comment: "When the request started"
11
-
12
- t.timestamps
13
- end
14
-
15
- add_index :rails_pulse_requests, :occurred_at, name: 'index_rails_pulse_requests_on_occurred_at'
16
- add_index :rails_pulse_requests, :request_uuid, unique: true, name: 'index_rails_pulse_requests_on_request_uuid'
17
- add_index :rails_pulse_requests, [ :route_id, :occurred_at ], name: 'index_rails_pulse_requests_on_route_id_and_occurred_at'
18
- end
19
- end
@@ -1,14 +0,0 @@
1
- class CreateQueries < RailsPulse::Migration
2
- def change
3
- create_table :rails_pulse_queries do |t|
4
- # Use string with reasonable limit instead of text to avoid MySQL index length issues
5
- # 1000 characters should be sufficient for most normalized SQL queries
6
- t.string :normalized_sql, limit: 1000, null: false, comment: "Normalized SQL query string (e.g., SELECT * FROM users WHERE id = ?)"
7
-
8
- t.timestamps
9
- end
10
-
11
- # Now using string instead of text, no need for database-specific logic
12
- add_index :rails_pulse_queries, :normalized_sql, unique: true, name: "index_rails_pulse_queries_on_normalized_sql", length: 191
13
- end
14
- end
@@ -1,24 +0,0 @@
1
- class CreateOperations < RailsPulse::Migration
2
- def change
3
- create_table :rails_pulse_operations do |t|
4
- t.references :request, null: false, foreign_key: { to_table: :rails_pulse_requests }, comment: "Link to the request"
5
- t.references :query, foreign_key: { to_table: :rails_pulse_queries }, index: true, comment: "Link to the normalized SQL query"
6
- t.string :operation_type, null: false, comment: "Type of operation (e.g., database, view, gem_call)"
7
- t.string :label, null: false, comment: "Descriptive name (e.g., SELECT FROM users WHERE id = 1, render layout)"
8
- t.decimal :duration, precision: 15, scale: 6, null: false, comment: "Operation duration in milliseconds"
9
- t.string :codebase_location, comment: "File and line number (e.g., app/models/user.rb:25)"
10
- t.float :start_time, null: false, default: 0.0, comment: "Operation start time in milliseconds"
11
- t.timestamp :occurred_at, null: false, comment: "When the request started"
12
-
13
- t.timestamps
14
- end
15
-
16
- add_index :rails_pulse_operations, :operation_type, name: 'index_rails_pulse_operations_on_operation_type'
17
- add_index :rails_pulse_operations, :occurred_at, name: 'index_rails_pulse_operations_on_occurred_at'
18
-
19
- # Performance indexes for queries page optimization
20
- add_index :rails_pulse_operations, [ :query_id, :occurred_at ], name: 'index_rails_pulse_operations_on_query_and_time'
21
- add_index :rails_pulse_operations, [ :query_id, :duration, :occurred_at ], name: 'index_rails_pulse_operations_query_performance'
22
- add_index :rails_pulse_operations, [ :occurred_at, :duration, :operation_type ], name: 'index_rails_pulse_operations_on_time_duration_type'
23
- end
24
- end
@@ -1,29 +0,0 @@
1
- module RailsPulse
2
- # Determine the appropriate migration version based on Rails version
3
- migration_version = case Rails.version
4
- when /^8\./
5
- 8.0
6
- when /^7\.2/
7
- 7.2
8
- when /^7\.1/
9
- 7.1
10
- when /^7\.0/
11
- 7.0
12
- else
13
- 7.1 # Default fallback
14
- end
15
-
16
- class Migration < ActiveRecord::Migration[migration_version]
17
- # This base migration class ensures that Rails Pulse migrations
18
- # target the correct database when using multiple database configuration.
19
- # The connection is determined by the connects_to configuration.
20
-
21
- def connection
22
- if RailsPulse.connects_to
23
- RailsPulse::ApplicationRecord.connection
24
- else
25
- super
26
- end
27
- end
28
- end
29
- end