rails_pulse 0.1.1 → 0.1.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -177
  3. data/Rakefile +77 -2
  4. data/app/assets/images/rails_pulse/dashboard.png +0 -0
  5. data/app/assets/images/rails_pulse/request.png +0 -0
  6. data/app/assets/stylesheets/rails_pulse/application.css +28 -17
  7. data/app/assets/stylesheets/rails_pulse/components/badge.css +13 -0
  8. data/app/assets/stylesheets/rails_pulse/components/base.css +12 -2
  9. data/app/assets/stylesheets/rails_pulse/components/collapsible.css +30 -0
  10. data/app/assets/stylesheets/rails_pulse/components/popover.css +0 -1
  11. data/app/assets/stylesheets/rails_pulse/components/row.css +55 -3
  12. data/app/assets/stylesheets/rails_pulse/components/sidebar_menu.css +23 -0
  13. data/app/controllers/concerns/chart_table_concern.rb +21 -4
  14. data/app/controllers/concerns/response_range_concern.rb +6 -3
  15. data/app/controllers/concerns/time_range_concern.rb +5 -10
  16. data/app/controllers/concerns/zoom_range_concern.rb +32 -1
  17. data/app/controllers/rails_pulse/application_controller.rb +13 -5
  18. data/app/controllers/rails_pulse/dashboard_controller.rb +12 -0
  19. data/app/controllers/rails_pulse/queries_controller.rb +111 -51
  20. data/app/controllers/rails_pulse/requests_controller.rb +37 -12
  21. data/app/controllers/rails_pulse/routes_controller.rb +98 -24
  22. data/app/helpers/rails_pulse/application_helper.rb +0 -1
  23. data/app/helpers/rails_pulse/chart_formatters.rb +3 -3
  24. data/app/helpers/rails_pulse/chart_helper.rb +21 -9
  25. data/app/helpers/rails_pulse/status_helper.rb +10 -4
  26. data/app/javascript/rails_pulse/application.js +34 -3
  27. data/app/javascript/rails_pulse/controllers/collapsible_controller.js +32 -0
  28. data/app/javascript/rails_pulse/controllers/color_scheme_controller.js +2 -1
  29. data/app/javascript/rails_pulse/controllers/expandable_rows_controller.js +58 -0
  30. data/app/javascript/rails_pulse/controllers/index_controller.js +353 -39
  31. data/app/javascript/rails_pulse/controllers/pagination_controller.js +17 -27
  32. data/app/javascript/rails_pulse/controllers/popover_controller.js +28 -4
  33. data/app/javascript/rails_pulse/controllers/table_sort_controller.js +14 -0
  34. data/app/jobs/rails_pulse/backfill_summaries_job.rb +41 -0
  35. data/app/jobs/rails_pulse/summary_job.rb +53 -0
  36. data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +28 -7
  37. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +18 -22
  38. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +18 -7
  39. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +34 -41
  40. data/app/models/rails_pulse/operation.rb +1 -1
  41. data/app/models/rails_pulse/queries/cards/average_query_times.rb +49 -25
  42. data/app/models/rails_pulse/queries/cards/execution_rate.rb +40 -28
  43. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +37 -43
  44. data/app/models/rails_pulse/queries/charts/average_query_times.rb +23 -97
  45. data/app/models/rails_pulse/queries/tables/index.rb +74 -0
  46. data/app/models/rails_pulse/query.rb +47 -0
  47. data/app/models/rails_pulse/requests/charts/average_response_times.rb +23 -84
  48. data/app/models/rails_pulse/route.rb +1 -6
  49. data/app/models/rails_pulse/routes/cards/average_response_times.rb +45 -25
  50. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +43 -45
  51. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +36 -44
  52. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +37 -27
  53. data/app/models/rails_pulse/routes/charts/average_response_times.rb +23 -100
  54. data/app/models/rails_pulse/routes/tables/index.rb +57 -40
  55. data/app/models/rails_pulse/summary.rb +143 -0
  56. data/app/services/rails_pulse/analysis/backtrace_analyzer.rb +256 -0
  57. data/app/services/rails_pulse/analysis/base_analyzer.rb +67 -0
  58. data/app/services/rails_pulse/analysis/explain_plan_analyzer.rb +206 -0
  59. data/app/services/rails_pulse/analysis/index_recommendation_engine.rb +326 -0
  60. data/app/services/rails_pulse/analysis/n_plus_one_detector.rb +241 -0
  61. data/app/services/rails_pulse/analysis/query_characteristics_analyzer.rb +146 -0
  62. data/app/services/rails_pulse/analysis/suggestion_generator.rb +217 -0
  63. data/app/services/rails_pulse/query_analysis_service.rb +125 -0
  64. data/app/services/rails_pulse/summary_service.rb +199 -0
  65. data/app/views/layouts/rails_pulse/_sidebar_menu.html.erb +0 -1
  66. data/app/views/layouts/rails_pulse/application.html.erb +4 -6
  67. data/app/views/rails_pulse/components/_breadcrumbs.html.erb +1 -1
  68. data/app/views/rails_pulse/components/_code_panel.html.erb +17 -3
  69. data/app/views/rails_pulse/components/_empty_state.html.erb +11 -0
  70. data/app/views/rails_pulse/components/_metric_card.html.erb +37 -28
  71. data/app/views/rails_pulse/components/_panel.html.erb +1 -1
  72. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +5 -7
  73. data/app/views/rails_pulse/components/_table_head.html.erb +6 -1
  74. data/app/views/rails_pulse/dashboard/index.html.erb +55 -37
  75. data/app/views/rails_pulse/operations/show.html.erb +17 -15
  76. data/app/views/rails_pulse/queries/_analysis_error.html.erb +15 -0
  77. data/app/views/rails_pulse/queries/_analysis_prompt.html.erb +27 -0
  78. data/app/views/rails_pulse/queries/_analysis_results.html.erb +87 -0
  79. data/app/views/rails_pulse/queries/_analysis_section.html.erb +39 -0
  80. data/app/views/rails_pulse/queries/_show_table.html.erb +2 -2
  81. data/app/views/rails_pulse/queries/_table.html.erb +11 -13
  82. data/app/views/rails_pulse/queries/index.html.erb +32 -28
  83. data/app/views/rails_pulse/queries/show.html.erb +45 -34
  84. data/app/views/rails_pulse/requests/_operations.html.erb +38 -45
  85. data/app/views/rails_pulse/requests/_table.html.erb +3 -3
  86. data/app/views/rails_pulse/requests/index.html.erb +33 -28
  87. data/app/views/rails_pulse/routes/_table.html.erb +14 -14
  88. data/app/views/rails_pulse/routes/index.html.erb +34 -29
  89. data/app/views/rails_pulse/routes/show.html.erb +43 -36
  90. data/config/initializers/rails_pulse.rb +0 -12
  91. data/config/routes.rb +5 -1
  92. data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +54 -0
  93. data/db/migrate/20250916031656_add_analysis_to_rails_pulse_queries.rb +13 -0
  94. data/db/rails_pulse_schema.rb +130 -0
  95. data/lib/generators/rails_pulse/convert_to_migrations_generator.rb +65 -0
  96. data/lib/generators/rails_pulse/install_generator.rb +94 -4
  97. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +60 -0
  98. data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +22 -0
  99. data/lib/generators/rails_pulse/templates/migrations/upgrade_rails_pulse_tables.rb +19 -0
  100. data/lib/generators/rails_pulse/templates/rails_pulse.rb +0 -12
  101. data/lib/generators/rails_pulse/upgrade_generator.rb +225 -0
  102. data/lib/rails_pulse/configuration.rb +0 -11
  103. data/lib/rails_pulse/engine.rb +0 -1
  104. data/lib/rails_pulse/version.rb +1 -1
  105. data/lib/tasks/rails_pulse.rake +77 -0
  106. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  107. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  108. data/public/rails-pulse-assets/rails-pulse.js +53 -53
  109. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  110. data/public/rails-pulse-assets/search.svg +43 -0
  111. metadata +48 -14
  112. data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
  113. data/app/assets/images/rails_pulse/routes.png +0 -0
  114. data/app/controllers/rails_pulse/caches_controller.rb +0 -115
  115. data/app/helpers/rails_pulse/cached_component_helper.rb +0 -73
  116. data/app/javascript/rails_pulse/controllers/expandable_row_controller.js +0 -67
  117. data/app/models/rails_pulse/component_cache_key.rb +0 -33
  118. data/app/views/rails_pulse/caches/show.html.erb +0 -9
  119. data/db/migrate/20250227235904_create_routes.rb +0 -12
  120. data/db/migrate/20250227235915_create_requests.rb +0 -19
  121. data/db/migrate/20250228000000_create_queries.rb +0 -14
  122. data/db/migrate/20250228000056_create_operations.rb +0 -24
  123. 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.3
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-24 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.
@@ -159,9 +173,7 @@ files:
159
173
  - Rakefile
160
174
  - app/assets/images/rails_pulse/dashboard.png
161
175
  - app/assets/images/rails_pulse/menu.svg
162
- - app/assets/images/rails_pulse/rails-pulse-logo.png
163
176
  - app/assets/images/rails_pulse/request.png
164
- - app/assets/images/rails_pulse/routes.png
165
177
  - app/assets/stylesheets/rails_pulse/application.css
166
178
  - app/assets/stylesheets/rails_pulse/components/alert.css
167
179
  - app/assets/stylesheets/rails_pulse/components/badge.css
@@ -170,6 +182,7 @@ files:
170
182
  - app/assets/stylesheets/rails_pulse/components/button.css
171
183
  - app/assets/stylesheets/rails_pulse/components/card.css
172
184
  - app/assets/stylesheets/rails_pulse/components/chart.css
185
+ - app/assets/stylesheets/rails_pulse/components/collapsible.css
173
186
  - app/assets/stylesheets/rails_pulse/components/csp_safe_positioning.css
174
187
  - app/assets/stylesheets/rails_pulse/components/descriptive_list.css
175
188
  - app/assets/stylesheets/rails_pulse/components/dialog.css
@@ -190,7 +203,6 @@ files:
190
203
  - app/controllers/concerns/zoom_range_concern.rb
191
204
  - app/controllers/rails_pulse/application_controller.rb
192
205
  - app/controllers/rails_pulse/assets_controller.rb
193
- - app/controllers/rails_pulse/caches_controller.rb
194
206
  - app/controllers/rails_pulse/csp_test_controller.rb
195
207
  - app/controllers/rails_pulse/dashboard_controller.rb
196
208
  - app/controllers/rails_pulse/operations_controller.rb
@@ -199,30 +211,32 @@ files:
199
211
  - app/controllers/rails_pulse/routes_controller.rb
200
212
  - app/helpers/rails_pulse/application_helper.rb
201
213
  - app/helpers/rails_pulse/breadcrumbs_helper.rb
202
- - app/helpers/rails_pulse/cached_component_helper.rb
203
214
  - app/helpers/rails_pulse/chart_formatters.rb
204
215
  - app/helpers/rails_pulse/chart_helper.rb
205
216
  - app/helpers/rails_pulse/formatting_helper.rb
206
217
  - app/helpers/rails_pulse/status_helper.rb
207
218
  - app/helpers/rails_pulse/table_helper.rb
208
219
  - app/javascript/rails_pulse/application.js
220
+ - app/javascript/rails_pulse/controllers/collapsible_controller.js
209
221
  - app/javascript/rails_pulse/controllers/color_scheme_controller.js
210
222
  - app/javascript/rails_pulse/controllers/context_menu_controller.js
211
223
  - app/javascript/rails_pulse/controllers/dialog_controller.js
212
- - app/javascript/rails_pulse/controllers/expandable_row_controller.js
224
+ - app/javascript/rails_pulse/controllers/expandable_rows_controller.js
213
225
  - app/javascript/rails_pulse/controllers/form_controller.js
214
226
  - app/javascript/rails_pulse/controllers/icon_controller.js
215
227
  - app/javascript/rails_pulse/controllers/index_controller.js
216
228
  - app/javascript/rails_pulse/controllers/menu_controller.js
217
229
  - app/javascript/rails_pulse/controllers/pagination_controller.js
218
230
  - app/javascript/rails_pulse/controllers/popover_controller.js
231
+ - app/javascript/rails_pulse/controllers/table_sort_controller.js
219
232
  - app/javascript/rails_pulse/controllers/timezone_controller.js
220
233
  - app/javascript/rails_pulse/theme.js
221
234
  - app/jobs/rails_pulse/application_job.rb
235
+ - app/jobs/rails_pulse/backfill_summaries_job.rb
222
236
  - app/jobs/rails_pulse/cleanup_job.rb
237
+ - app/jobs/rails_pulse/summary_job.rb
223
238
  - app/mailers/rails_pulse/application_mailer.rb
224
239
  - app/models/rails_pulse/application_record.rb
225
- - app/models/rails_pulse/component_cache_key.rb
226
240
  - app/models/rails_pulse/dashboard/charts/average_response_time.rb
227
241
  - app/models/rails_pulse/dashboard/charts/p95_response_time.rb
228
242
  - app/models/rails_pulse/dashboard/tables/slow_queries.rb
@@ -232,6 +246,7 @@ files:
232
246
  - app/models/rails_pulse/queries/cards/execution_rate.rb
233
247
  - app/models/rails_pulse/queries/cards/percentile_query_times.rb
234
248
  - app/models/rails_pulse/queries/charts/average_query_times.rb
249
+ - app/models/rails_pulse/queries/tables/index.rb
235
250
  - app/models/rails_pulse/query.rb
236
251
  - app/models/rails_pulse/request.rb
237
252
  - app/models/rails_pulse/requests/charts/average_response_times.rb
@@ -243,13 +258,23 @@ files:
243
258
  - app/models/rails_pulse/routes/cards/request_count_totals.rb
244
259
  - app/models/rails_pulse/routes/charts/average_response_times.rb
245
260
  - app/models/rails_pulse/routes/tables/index.rb
261
+ - app/models/rails_pulse/summary.rb
262
+ - app/services/rails_pulse/analysis/backtrace_analyzer.rb
263
+ - app/services/rails_pulse/analysis/base_analyzer.rb
264
+ - app/services/rails_pulse/analysis/explain_plan_analyzer.rb
265
+ - app/services/rails_pulse/analysis/index_recommendation_engine.rb
266
+ - app/services/rails_pulse/analysis/n_plus_one_detector.rb
267
+ - app/services/rails_pulse/analysis/query_characteristics_analyzer.rb
268
+ - app/services/rails_pulse/analysis/suggestion_generator.rb
269
+ - app/services/rails_pulse/query_analysis_service.rb
246
270
  - app/services/rails_pulse/sql_query_normalizer.rb
271
+ - app/services/rails_pulse/summary_service.rb
247
272
  - app/views/layouts/rails_pulse/_menu_items.html.erb
248
273
  - app/views/layouts/rails_pulse/_sidebar_menu.html.erb
249
274
  - app/views/layouts/rails_pulse/application.html.erb
250
- - app/views/rails_pulse/caches/show.html.erb
251
275
  - app/views/rails_pulse/components/_breadcrumbs.html.erb
252
276
  - app/views/rails_pulse/components/_code_panel.html.erb
277
+ - app/views/rails_pulse/components/_empty_state.html.erb
253
278
  - app/views/rails_pulse/components/_metric_card.html.erb
254
279
  - app/views/rails_pulse/components/_metric_row.html.erb
255
280
  - app/views/rails_pulse/components/_operation_details_popover.html.erb
@@ -270,6 +295,10 @@ files:
270
295
  - app/views/rails_pulse/operations/_operation_analysis_other.html.erb
271
296
  - app/views/rails_pulse/operations/_operation_analysis_view.html.erb
272
297
  - app/views/rails_pulse/operations/show.html.erb
298
+ - app/views/rails_pulse/queries/_analysis_error.html.erb
299
+ - app/views/rails_pulse/queries/_analysis_prompt.html.erb
300
+ - app/views/rails_pulse/queries/_analysis_results.html.erb
301
+ - app/views/rails_pulse/queries/_analysis_section.html.erb
273
302
  - app/views/rails_pulse/queries/_show_table.html.erb
274
303
  - app/views/rails_pulse/queries/_table.html.erb
275
304
  - app/views/rails_pulse/queries/index.html.erb
@@ -289,21 +318,25 @@ files:
289
318
  - config/initializers/rails_charts_csp_patch.rb
290
319
  - config/initializers/rails_pulse.rb
291
320
  - 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
321
+ - db/migrate/20241222000001_create_rails_pulse_summaries.rb
322
+ - db/migrate/20250916031656_add_analysis_to_rails_pulse_queries.rb
323
+ - db/rails_pulse_schema.rb
324
+ - lib/generators/rails_pulse/convert_to_migrations_generator.rb
296
325
  - lib/generators/rails_pulse/install_generator.rb
326
+ - lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb
327
+ - lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb
328
+ - lib/generators/rails_pulse/templates/migrations/upgrade_rails_pulse_tables.rb
297
329
  - lib/generators/rails_pulse/templates/rails_pulse.rb
330
+ - lib/generators/rails_pulse/upgrade_generator.rb
298
331
  - lib/rails_pulse.rb
299
332
  - lib/rails_pulse/cleanup_service.rb
300
333
  - lib/rails_pulse/configuration.rb
301
334
  - lib/rails_pulse/engine.rb
302
335
  - lib/rails_pulse/middleware/asset_server.rb
303
336
  - lib/rails_pulse/middleware/request_collector.rb
304
- - lib/rails_pulse/migration.rb
305
337
  - lib/rails_pulse/subscribers/operation_subscriber.rb
306
338
  - lib/rails_pulse/version.rb
339
+ - lib/tasks/rails_pulse.rake
307
340
  - lib/tasks/rails_pulse_tasks.rake
308
341
  - public/rails-pulse-assets/csp-test.js
309
342
  - public/rails-pulse-assets/rails-pulse-icons.js
@@ -312,6 +345,7 @@ files:
312
345
  - public/rails-pulse-assets/rails-pulse.css.map
313
346
  - public/rails-pulse-assets/rails-pulse.js
314
347
  - public/rails-pulse-assets/rails-pulse.js.map
348
+ - public/rails-pulse-assets/search.svg
315
349
  homepage: https://www.railspulse.com
316
350
  licenses:
317
351
  - 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,67 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus"
2
-
3
- export default class extends Controller {
4
- static targets = ["trigger", "details", "chevron"]
5
-
6
- connect() {
7
- // Ensure details row is initially hidden
8
- this.detailsTarget.classList.add("hidden")
9
- this.loaded = false
10
- }
11
-
12
- toggle(event) {
13
- event.preventDefault()
14
- event.stopPropagation()
15
-
16
- const isExpanded = !this.detailsTarget.classList.contains("hidden")
17
- console.log('Toggle clicked, currently expanded:', isExpanded)
18
-
19
- console.log('isExpanded', isExpanded)
20
- if (isExpanded) {
21
- console.log('Collapsing...')
22
- this.collapse()
23
- } else {
24
- console.log('Expanding...')
25
- this.expand()
26
- }
27
- }
28
-
29
- expand() {
30
- // Show details row
31
- this.detailsTarget.classList.remove("hidden")
32
-
33
- // Rotate chevron to point down
34
- this.chevronTarget.style.transform = "rotate(90deg)"
35
-
36
- // Add expanded state class to trigger row
37
- this.triggerTarget.classList.add("expanded")
38
-
39
- // Load content lazily on first expansion
40
- if (!this.loaded) {
41
- this.loadOperationDetails()
42
- this.loaded = true
43
- }
44
- }
45
-
46
- loadOperationDetails() {
47
- // Find the turbo frame and set its src to trigger loading
48
- const turboFrame = this.detailsTarget.querySelector('turbo-frame')
49
- if (turboFrame) {
50
- const operationUrl = turboFrame.dataset.operationUrl
51
- if (operationUrl) {
52
- turboFrame.src = operationUrl
53
- }
54
- }
55
- }
56
-
57
- collapse() {
58
- // Hide details row
59
- this.detailsTarget.classList.add("hidden")
60
-
61
- // Rotate chevron back to point right
62
- this.chevronTarget.style.transform = "rotate(0deg)"
63
-
64
- // Remove expanded state class from trigger row
65
- this.triggerTarget.classList.remove("expanded")
66
- }
67
- }
@@ -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