rails_pulse 0.2.3 → 0.2.4

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/controllers/concerns/chart_table_concern.rb +2 -3
  4. data/app/controllers/rails_pulse/application_controller.rb +10 -3
  5. data/app/controllers/rails_pulse/queries_controller.rb +1 -1
  6. data/app/controllers/rails_pulse/requests_controller.rb +2 -1
  7. data/app/controllers/rails_pulse/routes_controller.rb +1 -1
  8. data/app/helpers/rails_pulse/application_helper.rb +47 -2
  9. data/app/helpers/rails_pulse/chart_helper.rb +32 -2
  10. data/app/javascript/rails_pulse/application.js +3 -54
  11. data/app/javascript/rails_pulse/controllers/chart_controller.js +229 -0
  12. data/app/javascript/rails_pulse/controllers/index_controller.js +9 -14
  13. data/app/javascript/rails_pulse/controllers/pagination_controller.js +27 -33
  14. data/app/jobs/rails_pulse/backfill_summaries_job.rb +0 -2
  15. data/app/jobs/rails_pulse/cleanup_job.rb +0 -2
  16. data/app/jobs/rails_pulse/summary_job.rb +0 -2
  17. data/app/models/rails_pulse/queries/charts/average_query_times.rb +1 -1
  18. data/app/models/rails_pulse/requests/charts/average_response_times.rb +1 -1
  19. data/app/models/rails_pulse/routes/charts/average_response_times.rb +1 -1
  20. data/app/views/rails_pulse/components/_metric_card.html.erb +2 -2
  21. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +1 -1
  22. data/app/views/rails_pulse/components/_table_pagination.html.erb +8 -6
  23. data/app/views/rails_pulse/csp_test/show.html.erb +1 -1
  24. data/app/views/rails_pulse/dashboard/charts/_bar_chart.html.erb +1 -1
  25. data/app/views/rails_pulse/dashboard/index.html.erb +4 -3
  26. data/app/views/rails_pulse/queries/index.html.erb +2 -1
  27. data/app/views/rails_pulse/queries/show.html.erb +2 -1
  28. data/app/views/rails_pulse/routes/index.html.erb +2 -1
  29. data/app/views/rails_pulse/routes/show.html.erb +2 -1
  30. data/config/importmap.rb +1 -1
  31. data/lib/rails_pulse/engine.rb +0 -30
  32. data/lib/rails_pulse/version.rb +1 -1
  33. data/public/rails-pulse-assets/csp-test.js +10 -10
  34. data/public/rails-pulse-assets/rails-pulse.js +48 -48
  35. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  36. metadata +5 -25
  37. data/config/initializers/rails_charts_csp_patch.rb +0 -75
@@ -3,56 +3,50 @@ import { Controller } from "@hotwired/stimulus"
3
3
  export default class extends Controller {
4
4
  static targets = ["limit"]
5
5
  static values = {
6
- storageKey: { type: String, default: "rails_pulse_pagination_limit" },
7
- url: String
6
+ storageKey: { type: String, default: "rails_pulse_pagination_limit" }
8
7
  }
9
8
 
10
9
  connect() {
11
10
  this.restorePaginationLimit()
12
11
  }
13
12
 
14
- // Update pagination limit and refresh the turbo frame
13
+ // Update pagination limit and navigate to page 1 with new limit
15
14
  updateLimit() {
16
15
  const limit = this.limitTarget.value
17
16
 
18
- // Save to session storage only - no server request needed
17
+ // Save to session storage for persistence
19
18
  sessionStorage.setItem(this.storageKeyValue, limit)
20
19
 
21
- // Find the closest turbo frame and reload it to apply new pagination
22
- const turboFrame = this.element.closest('turbo-frame')
23
- if (turboFrame) {
24
- // Add the limit as a URL parameter so server picks it up
25
- const currentUrl = new URL(window.location)
26
- currentUrl.searchParams.set('limit', limit)
27
- turboFrame.src = currentUrl.pathname + currentUrl.search
20
+ // Build URL with limit parameter and reset to page 1
21
+ const currentUrl = new URL(window.location)
22
+ currentUrl.searchParams.set('limit', limit)
23
+ currentUrl.searchParams.delete('page')
24
+
25
+ // Use Turbo.visit for smooth navigation that preserves query params
26
+ if (typeof Turbo !== 'undefined') {
27
+ Turbo.visit(currentUrl.toString(), { action: 'replace' })
28
28
  } else {
29
- // Fallback to page reload if not within a turbo frame
30
- const currentUrl = new URL(window.location)
31
- currentUrl.searchParams.set('limit', limit)
32
- window.location.href = currentUrl.pathname + currentUrl.search
29
+ window.location.href = currentUrl.toString()
33
30
  }
34
31
  }
35
32
 
36
- // Get CSRF token from meta tag
37
- getCSRFToken() {
38
- const token = document.querySelector('meta[name="csrf-token"]')
39
- return token ? token.getAttribute('content') : ''
40
- }
41
-
42
- // Save the pagination limit to session storage when it changes
43
- savePaginationLimit() {
44
- const limit = this.limitTarget.value
45
- sessionStorage.setItem(this.storageKeyValue, limit)
46
- }
47
-
48
- // Restore the pagination limit from session storage on page load
33
+ // Restore the pagination limit from URL or session storage on page load
49
34
  restorePaginationLimit() {
50
- const savedLimit = sessionStorage.getItem(this.storageKeyValue)
51
- if (savedLimit && this.limitTarget) {
52
- // Only set if the current value is different (prevents unnecessary DOM updates)
53
- if (this.limitTarget.value !== savedLimit) {
35
+ // URL params take precedence over session storage
36
+ const urlParams = new URLSearchParams(window.location.search)
37
+ const urlLimit = urlParams.get('limit')
38
+
39
+ if (urlLimit && this.limitTarget) {
40
+ // Sync sessionStorage with URL param
41
+ sessionStorage.setItem(this.storageKeyValue, urlLimit)
42
+ if (this.limitTarget.value !== urlLimit) {
43
+ this.limitTarget.value = urlLimit
44
+ }
45
+ } else {
46
+ // Fall back to sessionStorage if no URL param
47
+ const savedLimit = sessionStorage.getItem(this.storageKeyValue)
48
+ if (savedLimit && this.limitTarget && this.limitTarget.value !== savedLimit) {
54
49
  this.limitTarget.value = savedLimit
55
- // Don't trigger change event when restoring from session - prevents infinite loops
56
50
  }
57
51
  }
58
52
  }
@@ -1,7 +1,5 @@
1
1
  module RailsPulse
2
2
  class BackfillSummariesJob < ApplicationJob
3
- queue_as :low_priority
4
-
5
3
  def perform(start_date, end_date, period_types = [ "hour", "day" ])
6
4
  start_date = start_date.to_datetime
7
5
  end_date = end_date.to_datetime
@@ -1,7 +1,5 @@
1
1
  module RailsPulse
2
2
  class CleanupJob < ApplicationJob
3
- queue_as :default
4
-
5
3
  def perform
6
4
  return unless RailsPulse.configuration.archiving_enabled
7
5
 
@@ -1,7 +1,5 @@
1
1
  module RailsPulse
2
2
  class SummaryJob < ApplicationJob
3
- queue_as :low_priority
4
-
5
3
  def perform(target_hour = nil)
6
4
  target_hour ||= 1.hour.ago.beginning_of_hour
7
5
 
@@ -13,7 +13,7 @@ module RailsPulse
13
13
  @show_non_tagged = show_non_tagged
14
14
  end
15
15
 
16
- def to_rails_chart
16
+ def to_chart_data
17
17
  # The ransack query already contains the correct filters, just add period_type and tag filters
18
18
  summaries = @ransack_query.result(distinct: false)
19
19
  .with_tag_filters(@disabled_tags, @show_non_tagged)
@@ -13,7 +13,7 @@ module RailsPulse
13
13
  @show_non_tagged = show_non_tagged
14
14
  end
15
15
 
16
- def to_rails_chart
16
+ def to_chart_data
17
17
  # Note: Overall request summaries (summarizable_id: 0) are not filtered by tags
18
18
  # as they aggregate all requests regardless of route tags
19
19
  summaries = @ransack_query.result(distinct: false)
@@ -13,7 +13,7 @@ module RailsPulse
13
13
  @show_non_tagged = show_non_tagged
14
14
  end
15
15
 
16
- def to_rails_chart
16
+ def to_chart_data
17
17
  summaries = @ransack_query.result(distinct: false)
18
18
  .with_tag_filters(@disabled_tags, @show_non_tagged)
19
19
  .where(
@@ -9,7 +9,7 @@
9
9
  trend_amount = data[:trend_amount]
10
10
  trend_text = data[:trend_text]
11
11
  %>
12
- <div class="grid-item" data-controller="rails-pulse--chart" id="<%= id %>">
12
+ <div class="grid-item" id="<%= id %>">
13
13
  <%= render 'rails_pulse/components/panel', { title: title, card_classes: 'card-compact' } do %>
14
14
  <div class="row mbs-2" style="--columns: 2; align-items: center; margin-bottom: 0;">
15
15
  <div class="grid-item">
@@ -38,7 +38,7 @@
38
38
  }
39
39
  )
40
40
  %>
41
- <%= bar_chart chart_data, height: "100%", options: chart_options %>
41
+ <%= render_stimulus_chart chart_data, type: 'bar', height: "100%", options: chart_options %>
42
42
  </div>
43
43
  </div>
44
44
  </div>
@@ -2,7 +2,7 @@
2
2
  <h4 class="text-xl mbs-1 font-bold"><%= summary %></h4>
3
3
  </div>
4
4
  <div class="chart-container chart-container--slim">
5
- <%= bar_chart chart_data, height: "100%", options: sparkline_chart_options %>
5
+ <%= render_stimulus_chart chart_data, type: 'bar', height: "100%", options: sparkline_chart_options %>
6
6
  </div>
7
7
  <div>
8
8
  <span class="badge badge--<%= trend_direction == "down" ? "positive" : "negative" %>-inverse p-0">
@@ -7,7 +7,7 @@
7
7
  data-rails-pulse--pagination-url-value="<%= rails_pulse.pagination_limit_path %>">
8
8
  <label for="pagination_limit" class="text-sm font-medium">Rows per page</label>
9
9
  <%= select_tag :limit,
10
- options_for_select([[10, 10], [20, 20], [30, 30], [40, 40], [50, 50]], @pagy.vars[:items]),
10
+ options_for_select([[10, 10], [20, 20], [30, 30], [40, 40], [50, 50]], pagy_items(@pagy)),
11
11
  {
12
12
  id: "pagination_limit",
13
13
  class: "input",
@@ -21,22 +21,24 @@
21
21
  %>
22
22
  </div>
23
23
 
24
- <div class="text-sm font-medium"><%= "Page #{@pagy.page} of #{@pagy.pages}" %></div>
24
+ <div class="text-sm font-medium"><%= "Page #{@pagy.page} of #{@pagy.last}" %></div>
25
25
 
26
26
  <nav class="flex items-center gap shrink-0" style="--btn-padding: .5rem;" aria-label="Pagination">
27
- <%= link_to pagy_url_for(@pagy, 1), class: "btn", aria: { disabled: @pagy.prev.nil? }.compact_blank do %>
27
+ <% previous_page = pagy_previous(@pagy) %>
28
+ <% next_page = pagy_next(@pagy) %>
29
+ <%= link_to pagy_page_url(@pagy, 1), class: "btn", aria: { disabled: previous_page.nil? }.compact_blank do %>
28
30
  <%= rails_pulse_icon 'chevrons-left', width: '16', height: '16' %>
29
31
  <span class="sr-only">Go to first page</span>
30
32
  <% end %>
31
- <%= link_to pagy_url_for(@pagy, @pagy.prev || @pagy.page), class: "btn", aria: { disabled: @pagy.prev.nil? }.compact_blank do %>
33
+ <%= link_to pagy_page_url(@pagy, previous_page || @pagy.page), class: "btn", aria: { disabled: previous_page.nil? }.compact_blank do %>
32
34
  <%= rails_pulse_icon 'chevron-left', width: '16', height: '16' %>
33
35
  <span class="sr-only">Go to previous page</span>
34
36
  <% end %>
35
- <%= link_to pagy_url_for(@pagy, @pagy.next || @pagy.page), class: "btn", aria: { disabled: @pagy.next.nil? }.compact_blank do %>
37
+ <%= link_to pagy_page_url(@pagy, next_page || @pagy.page), class: "btn", aria: { disabled: next_page.nil? }.compact_blank do %>
36
38
  <%= rails_pulse_icon 'chevron-right', width: '16', height: '16' %>
37
39
  <span class="sr-only">Go to next page</span>
38
40
  <% end %>
39
- <%= link_to pagy_url_for(@pagy, @pagy.last), class: "btn", aria: { disabled: @pagy.next.nil? }.compact_blank do %>
41
+ <%= link_to pagy_page_url(@pagy, @pagy.last), class: "btn", aria: { disabled: next_page.nil? }.compact_blank do %>
40
42
  <%= rails_pulse_icon 'chevrons-right', width: '16', height: '16' %>
41
43
  <span class="sr-only">Go to last page</span>
42
44
  <% end %>
@@ -171,7 +171,7 @@
171
171
  <div class="card">
172
172
  <div class="p-4">
173
173
  <h2 class="text-lg font-semibold mb-3">Chart Component Test</h2>
174
- <p class="text-subtle mb-4">Testing rails_charts CSP compliance:</p>
174
+ <p class="text-subtle mb-4">Testing chart CSP compliance:</p>
175
175
 
176
176
  <div id="chart-container" class="bg-shade border border-main rounded-md p-4 text-center">
177
177
  <span class="text-subtle">Chart components would render here in a real dashboard</span>
@@ -1 +1 @@
1
- <%= bar_chart @component_data, height: "100%", options: bar_chart_options(units: "ms") %>
1
+ <%= render_stimulus_chart @component_data, type: 'bar', height: "100%", options: bar_chart_options(units: "ms") %>
@@ -20,9 +20,9 @@
20
20
  } do %>
21
21
  <% if @average_response_time_chart_data.present? %>
22
22
  <div class="chart-container chart-container--slim">
23
- <%= bar_chart(
23
+ <%= render_stimulus_chart(
24
24
  @average_response_time_chart_data,
25
- code: false,
25
+ type: 'bar',
26
26
  id: "dashboard_average_response_time_chart",
27
27
  height: "100%",
28
28
  options: bar_chart_options(
@@ -44,8 +44,9 @@
44
44
  } do %>
45
45
  <% if @p95_response_time_chart_data.present? %>
46
46
  <div class="chart-container chart-container--slim">
47
- <%= bar_chart(
47
+ <%= render_stimulus_chart(
48
48
  @p95_response_time_chart_data,
49
+ type: 'bar',
49
50
  code: false,
50
51
  id: "dashboard_p95_response_time_chart",
51
52
  height: "100%",
@@ -32,8 +32,9 @@
32
32
  class="chart-container chart-container--slim"
33
33
  data-rails-pulse--index-target="chart"
34
34
  >
35
- <%= bar_chart(
35
+ <%= render_stimulus_chart(
36
36
  @chart_data,
37
+ type: 'bar',
37
38
  code: false,
38
39
  id: "average_query_times_chart",
39
40
  height: "100%",
@@ -32,8 +32,9 @@
32
32
  class="chart-container chart-container--slim"
33
33
  data-rails-pulse--index-target="chart"
34
34
  >
35
- <%= bar_chart(
35
+ <%= render_stimulus_chart(
36
36
  @chart_data,
37
+ type: 'bar',
37
38
  code: false,
38
39
  id: "query_responses_chart",
39
40
  height: "100%",
@@ -34,8 +34,9 @@
34
34
  class="chart-container chart-container--slim"
35
35
  data-rails-pulse--index-target="chart"
36
36
  >
37
- <%= bar_chart(
37
+ <%= render_stimulus_chart(
38
38
  @chart_data,
39
+ type: 'bar',
39
40
  code: false,
40
41
  id: "average_response_times_chart",
41
42
  height: "100%",
@@ -35,8 +35,9 @@
35
35
  class="chart-container chart-container--slim"
36
36
  data-rails-pulse--index-target="chart"
37
37
  >
38
- <%= bar_chart(
38
+ <%= render_stimulus_chart(
39
39
  @chart_data,
40
+ type: 'bar',
40
41
  code: false,
41
42
  id: "route_responses_chart",
42
43
  height: "100%",
data/config/importmap.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  pin "application", to: "rails_pulse/application.js"
2
2
 
3
- # echarts is a dependency of the rails_charts gem
3
+ # echarts is used for chart rendering
4
4
  pin "echarts", to: "echarts.min.js"
5
5
  # pin "echarts/theme/inspired", to: "echarts/theme/inspired.js"
6
6
  pin "rails_pulse/theme", to: "rails_pulse/theme.js"
@@ -4,7 +4,6 @@ require "rails_pulse/middleware/asset_server"
4
4
  require "rails_pulse/subscribers/operation_subscriber"
5
5
  require "request_store"
6
6
  require "rack/static"
7
- require "rails_charts"
8
7
  require "ransack"
9
8
  require "pagy"
10
9
  require "turbo-rails"
@@ -14,17 +13,6 @@ module RailsPulse
14
13
  class Engine < ::Rails::Engine
15
14
  isolate_namespace RailsPulse
16
15
 
17
- # Prevent rails_charts from polluting the global ActionView namespace
18
- # This MUST happen before any initializers run to avoid conflicts with host apps
19
- # that use Chartkick or other chart libraries
20
- if defined?(RailsCharts::Engine)
21
- # Find and remove the rails_charts.helpers initializer
22
- RailsCharts::Engine.initializers.delete_if do |init|
23
- init.name == "rails_charts.helpers"
24
- end
25
- end
26
-
27
-
28
16
  # Load Rake tasks
29
17
  rake_tasks do
30
18
  Dir.glob(File.expand_path("../tasks/**/*.rake", __FILE__)).each { |file| load file }
@@ -57,24 +45,6 @@ module RailsPulse
57
45
  RailsPulse::Subscribers::OperationSubscriber.subscribe!
58
46
  end
59
47
 
60
- initializer "rails_pulse.rails_charts_theme" do
61
- RailsCharts.options[:theme] = "railspulse"
62
- end
63
-
64
- # Manually include RailsCharts helpers only in RailsPulse views
65
- # This ensures rails_charts methods are only available in RailsPulse namespace,
66
- # not in the host application
67
- initializer "rails_pulse.include_rails_charts_helpers" do
68
- ActiveSupport.on_load :action_view do
69
- if defined?(RailsCharts::Helpers) && defined?(RailsPulse::ChartHelper)
70
- unless RailsPulse::ChartHelper.include?(RailsCharts::Helpers)
71
- RailsPulse::ChartHelper.include(RailsCharts::Helpers)
72
- end
73
- end
74
- end
75
- end
76
-
77
-
78
48
  initializer "rails_pulse.ransack", after: "ransack.initialize" do
79
49
  # Ensure Ransack is loaded before our models
80
50
  end
@@ -1,3 +1,3 @@
1
1
  module RailsPulse
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
@@ -90,21 +90,21 @@
90
90
  checkAssetLoading();
91
91
  setupAjaxTest();
92
92
  trackCSPViolations();
93
+
94
+ // Add a visible indicator for system tests
95
+ const indicator = document.createElement('div');
96
+ indicator.id = 'js-loaded-indicator';
97
+ indicator.textContent = 'CSP Test JS loaded successfully';
98
+ indicator.style.display = 'none'; // Hidden but accessible to tests
99
+ document.body.appendChild(indicator);
93
100
  }
94
-
101
+
102
+ console.log('CSP Test JS loaded successfully - monitoring for violations');
103
+
95
104
  // Run tests when DOM is ready
96
105
  if (document.readyState === 'loading') {
97
106
  document.addEventListener('DOMContentLoaded', initializeTests);
98
107
  } else {
99
108
  initializeTests();
100
109
  }
101
-
102
- console.log('CSP Test JS loaded successfully - monitoring for violations');
103
-
104
- // Add a visible indicator for system tests
105
- const indicator = document.createElement('div');
106
- indicator.id = 'js-loaded-indicator';
107
- indicator.textContent = 'CSP Test JS loaded successfully';
108
- indicator.style.display = 'none'; // Hidden but accessible to tests
109
- document.body.appendChild(indicator);
110
110
  })();