rails_pulse 0.2.2 → 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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/stylesheets/rails_pulse/components/tags.css +2 -2
  4. data/app/controllers/concerns/chart_table_concern.rb +4 -3
  5. data/app/controllers/rails_pulse/application_controller.rb +11 -3
  6. data/app/controllers/rails_pulse/dashboard_controller.rb +12 -8
  7. data/app/controllers/rails_pulse/queries_controller.rb +13 -8
  8. data/app/controllers/rails_pulse/requests_controller.rb +10 -5
  9. data/app/controllers/rails_pulse/routes_controller.rb +14 -7
  10. data/app/helpers/rails_pulse/application_helper.rb +47 -2
  11. data/app/helpers/rails_pulse/chart_helper.rb +32 -2
  12. data/app/javascript/rails_pulse/application.js +3 -54
  13. data/app/javascript/rails_pulse/controllers/chart_controller.js +229 -0
  14. data/app/javascript/rails_pulse/controllers/index_controller.js +9 -14
  15. data/app/javascript/rails_pulse/controllers/pagination_controller.js +27 -33
  16. data/app/jobs/rails_pulse/backfill_summaries_job.rb +0 -2
  17. data/app/jobs/rails_pulse/cleanup_job.rb +0 -2
  18. data/app/jobs/rails_pulse/summary_job.rb +0 -2
  19. data/app/models/concerns/rails_pulse/taggable.rb +63 -0
  20. data/app/models/rails_pulse/dashboard/charts/average_response_time.rb +12 -5
  21. data/app/models/rails_pulse/dashboard/charts/p95_response_time.rb +12 -5
  22. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +7 -0
  23. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +6 -0
  24. data/app/models/rails_pulse/queries/cards/average_query_times.rb +10 -6
  25. data/app/models/rails_pulse/queries/cards/execution_rate.rb +16 -10
  26. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +10 -6
  27. data/app/models/rails_pulse/queries/charts/average_query_times.rb +6 -3
  28. data/app/models/rails_pulse/queries/tables/index.rb +12 -2
  29. data/app/models/rails_pulse/requests/charts/average_response_times.rb +13 -7
  30. data/app/models/rails_pulse/routes/cards/average_response_times.rb +10 -6
  31. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +10 -6
  32. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +10 -6
  33. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +10 -6
  34. data/app/models/rails_pulse/routes/charts/average_response_times.rb +10 -6
  35. data/app/models/rails_pulse/routes/tables/index.rb +12 -2
  36. data/app/models/rails_pulse/summary.rb +55 -0
  37. data/app/views/layouts/rails_pulse/_global_filters.html.erb +9 -2
  38. data/app/views/rails_pulse/components/_active_filters.html.erb +36 -0
  39. data/app/views/rails_pulse/components/_metric_card.html.erb +2 -2
  40. data/app/views/rails_pulse/components/_page_header.html.erb +4 -0
  41. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +1 -1
  42. data/app/views/rails_pulse/components/_table_pagination.html.erb +8 -6
  43. data/app/views/rails_pulse/csp_test/show.html.erb +1 -1
  44. data/app/views/rails_pulse/dashboard/charts/_bar_chart.html.erb +1 -1
  45. data/app/views/rails_pulse/dashboard/index.html.erb +8 -3
  46. data/app/views/rails_pulse/queries/index.html.erb +3 -2
  47. data/app/views/rails_pulse/queries/show.html.erb +2 -1
  48. data/app/views/rails_pulse/requests/index.html.erb +1 -1
  49. data/app/views/rails_pulse/routes/index.html.erb +3 -2
  50. data/app/views/rails_pulse/routes/show.html.erb +2 -1
  51. data/app/views/rails_pulse/tags/_tag_manager.html.erb +2 -2
  52. data/config/importmap.rb +1 -1
  53. data/lib/rails_pulse/cleanup_service.rb +8 -0
  54. data/lib/rails_pulse/engine.rb +0 -5
  55. data/lib/rails_pulse/version.rb +1 -1
  56. data/public/rails-pulse-assets/csp-test.js +10 -10
  57. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  58. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  59. data/public/rails-pulse-assets/rails-pulse.js +48 -48
  60. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  61. metadata +7 -26
  62. data/app/models/concerns/taggable.rb +0 -61
  63. data/config/initializers/rails_charts_csp_patch.rb +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8ed469f307dd71e912b46a4da0a0daa60abbdc262a8dbd12604db00a66335fe
4
- data.tar.gz: 8af5a6bf946a86e1bc277c1d719100ace739bb429c07969fd1e4ec31887d6f70
3
+ metadata.gz: 943ab1e08e81dbe5ede71b7d3c9ca526a65c9a0ce1767c7f9c37cee8883925cf
4
+ data.tar.gz: b3661c728215dc4759e6a3e04dd49dc113d13155972ed30f6ff35d0a2a07b10c
5
5
  SHA512:
6
- metadata.gz: eceb3af7e713b6049688e50a8fd79af7b2f09c7720158c8d15257235b0c7488ebe7c0eccfaab3cd5dfb5923f80e9a8bd8693d0a9fb2f4e021aca8846c4ac435c
7
- data.tar.gz: f982a50256d99968eb68fee89e6380d3f75511e6c3e8e878ddca375919f6a68a201f9ce46c6868e2425c127ae1862ca0d1895b0e01afea89a9af6a4e85d7f0f9
6
+ metadata.gz: db5d711b9d8931c6aa489b9069f05d16514a997b9798c743f2c2998611797dbc3dbdcfdcd12e69f65b2b3459292d1e7558075c02805aa0f49a398b85716736ea
7
+ data.tar.gz: 55ccd02b5a9bb13ba46e36e0051c2440d8026956743e02660968209c66e61fe25f4ed8c3dd04a77b08a6209df9e13c922f42e481ade7cf53447d89a93976e680
data/README.md CHANGED
@@ -601,7 +601,7 @@ Rails Pulse is built using modern, battle-tested technologies that ensure reliab
601
601
  - **[Turbo Frames](https://turbo.hotwired.dev/handbook/frames)** - Lazy loading and partial page updates for optimal performance
602
602
 
603
603
  ### **Data Visualization**
604
- - **[Rails Charts](https://github.com/railsjazz/rails_charts)** - Rails wrapper around Apache ECharts
604
+ - **[Apache ECharts](https://echarts.apache.org/)** - Powerful, interactive charting library
605
605
  - **[Lucide Icons](https://lucide.dev/)** - Beautiful, consistent iconography with pre-compiled SVG bundle
606
606
 
607
607
  ### **Asset Management**
@@ -75,8 +75,8 @@
75
75
  }
76
76
 
77
77
  .tag-add-button {
78
- padding: 0.25rem 0.5rem;
79
- font-size: 0.875rem;
78
+ padding: 0.2rem 0.5rem;
79
+ font-size: 0.8rem;
80
80
  line-height: 1.25;
81
81
  white-space: nowrap;
82
82
  }
@@ -2,7 +2,6 @@ module ChartTableConcern
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- include Pagy::Backend
6
5
  include TimeRangeConcern
7
6
  include ResponseRangeConcern
8
7
  include ZoomRangeConcern
@@ -38,8 +37,10 @@ module ChartTableConcern
38
37
  start_time: @start_time,
39
38
  end_time: @end_time,
40
39
  start_duration: @start_duration,
40
+ disabled_tags: session_disabled_tags,
41
+ show_non_tagged: session[:show_non_tagged] != false,
41
42
  **chart_options
42
- ).to_rails_chart
43
+ ).to_chart_data
43
44
  end
44
45
 
45
46
  def setup_table_data(ransack_params)
@@ -50,7 +51,7 @@ module ChartTableConcern
50
51
  table_results = build_table_results
51
52
  handle_pagination
52
53
 
53
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
54
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
54
55
  end
55
56
 
56
57
  def setup_zoom_range_data
@@ -1,5 +1,12 @@
1
1
  module RailsPulse
2
2
  class ApplicationController < ActionController::Base
3
+ # Support both Pagy 8.x (Backend) and Pagy 9+ (Method)
4
+ if defined?(Pagy::Method)
5
+ include Pagy::Method
6
+ else
7
+ include Pagy::Backend
8
+ end
9
+
3
10
  before_action :authenticate_rails_pulse_user!
4
11
  before_action :set_show_non_tagged_default
5
12
  helper_method :session_global_filters, :session_disabled_tags
@@ -8,15 +15,16 @@ module RailsPulse
8
15
  limit = limit || params[:limit]
9
16
  session[:pagination_limit] = limit.to_i if limit.present?
10
17
 
11
- # Render JSON for direct API calls or AJAX requests (but not turbo frame requests)
12
- if (request.xhr? && !turbo_frame_request?) || (request.patch? && action_name == "set_pagination_limit")
18
+ # Render JSON for direct API calls or AJAX requests (but not turbo frame requests)
19
+ if (request.xhr? && !turbo_frame_request?) || (request.patch? && action_name == "set_pagination_limit")
13
20
  render json: { status: "ok" }
14
- end
21
+ end
15
22
  end
16
23
 
17
24
  def set_global_filters
18
25
  if params[:clear] == "true"
19
26
  session.delete(:global_filters)
27
+ session[:show_non_tagged] = true # Reset show_non_tagged to default
20
28
  else
21
29
  filters = session[:global_filters] || {}
22
30
 
@@ -1,18 +1,22 @@
1
1
  module RailsPulse
2
2
  class DashboardController < ApplicationController
3
3
  def index
4
- @average_query_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: nil).to_metric_card
5
- @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: nil).to_metric_card
6
- @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: nil).to_metric_card
7
- @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: nil).to_metric_card
4
+ # Get tag filter values from session
5
+ disabled_tags = session_disabled_tags
6
+ show_non_tagged = session[:show_non_tagged] != false
7
+
8
+ @average_query_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
9
+ @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
10
+ @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
11
+ @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
8
12
 
9
13
  # Generate chart data for inline rendering
10
- @average_response_time_chart_data = RailsPulse::Dashboard::Charts::AverageResponseTime.new.to_chart_data
11
- @p95_response_time_chart_data = RailsPulse::Dashboard::Charts::P95ResponseTime.new.to_chart_data
14
+ @average_response_time_chart_data = RailsPulse::Dashboard::Charts::AverageResponseTime.new(disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_chart_data
15
+ @p95_response_time_chart_data = RailsPulse::Dashboard::Charts::P95ResponseTime.new(disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_chart_data
12
16
 
13
17
  # Generate table data for inline rendering
14
- @slow_routes_table_data = RailsPulse::Dashboard::Tables::SlowRoutes.new.to_table_data
15
- @slow_queries_table_data = RailsPulse::Dashboard::Tables::SlowQueries.new.to_table_data
18
+ @slow_routes_table_data = RailsPulse::Dashboard::Tables::SlowRoutes.new(disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_table_data
19
+ @slow_queries_table_data = RailsPulse::Dashboard::Tables::SlowQueries.new(disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_table_data
16
20
  end
17
21
  end
18
22
  end
@@ -70,7 +70,8 @@ module RailsPulse
70
70
  def build_chart_ransack_params(ransack_params)
71
71
  base_params = ransack_params.except(:s).merge(
72
72
  period_start_gteq: Time.at(@start_time),
73
- period_start_lt: Time.at(@end_time)
73
+ period_start_lt: Time.at(@end_time),
74
+ summarizable_type_eq: "RailsPulse::Query"
74
75
  )
75
76
 
76
77
  # Only add duration filter if we have a meaningful threshold
@@ -78,8 +79,7 @@ module RailsPulse
78
79
 
79
80
  if show_action?
80
81
  base_params.merge(
81
- summarizable_id_eq: @query.id,
82
- summarizable_type_eq: "RailsPulse::Query"
82
+ summarizable_id_eq: @query.id
83
83
  )
84
84
  else
85
85
  base_params
@@ -123,7 +123,8 @@ module RailsPulse
123
123
  period_type: period_type,
124
124
  start_time: @start_time,
125
125
  params: params,
126
- disabled_tags: session_disabled_tags
126
+ disabled_tags: session_disabled_tags,
127
+ show_non_tagged: session[:show_non_tagged] != false
127
128
  ).to_table
128
129
  end
129
130
  end
@@ -133,9 +134,13 @@ module RailsPulse
133
134
  def setup_metric_cards
134
135
  return if turbo_frame_request?
135
136
 
136
- @average_query_times_metric_card = RailsPulse::Queries::Cards::AverageQueryTimes.new(query: @query).to_metric_card
137
- @percentile_query_times_metric_card = RailsPulse::Queries::Cards::PercentileQueryTimes.new(query: @query).to_metric_card
138
- @execution_rate_metric_card = RailsPulse::Queries::Cards::ExecutionRate.new(query: @query).to_metric_card
137
+ # Get tag filter values from session
138
+ disabled_tags = session_disabled_tags
139
+ show_non_tagged = session[:show_non_tagged] != false
140
+
141
+ @average_query_times_metric_card = RailsPulse::Queries::Cards::AverageQueryTimes.new(query: @query, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
142
+ @percentile_query_times_metric_card = RailsPulse::Queries::Cards::PercentileQueryTimes.new(query: @query, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
143
+ @execution_rate_metric_card = RailsPulse::Queries::Cards::ExecutionRate.new(query: @query, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
139
144
  end
140
145
 
141
146
  def show_action?
@@ -158,7 +163,7 @@ module RailsPulse
158
163
  table_results = build_table_results
159
164
  handle_pagination
160
165
 
161
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
166
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
162
167
  end
163
168
 
164
169
  def handle_pagination
@@ -26,10 +26,14 @@ module RailsPulse
26
26
  def setup_metric_cards
27
27
  return if turbo_frame_request?
28
28
 
29
- @average_response_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: nil).to_metric_card
30
- @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: nil).to_metric_card
31
- @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: nil).to_metric_card
32
- @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: nil).to_metric_card
29
+ # Get tag filter values from session
30
+ disabled_tags = session_disabled_tags
31
+ show_non_tagged = session[:show_non_tagged] != false
32
+
33
+ @average_response_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
34
+ @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
35
+ @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
36
+ @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: nil, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
33
37
  end
34
38
 
35
39
 
@@ -124,7 +128,8 @@ module RailsPulse
124
128
  table_results = build_table_results
125
129
  handle_pagination
126
130
 
127
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
131
+ # Use 'items:' for Pagy 8.x compatibility ('limit:' is for Pagy 43+)
132
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
128
133
  end
129
134
 
130
135
  def handle_pagination
@@ -20,10 +20,14 @@ module RailsPulse
20
20
  def setup_metric_cards
21
21
  return if turbo_frame_request?
22
22
 
23
- @average_query_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: @route).to_metric_card
24
- @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: @route).to_metric_card
25
- @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: @route).to_metric_card
26
- @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: @route).to_metric_card
23
+ # Get tag filter values from session
24
+ disabled_tags = session_disabled_tags
25
+ show_non_tagged = session[:show_non_tagged] != false
26
+
27
+ @average_query_times_metric_card = RailsPulse::Routes::Cards::AverageResponseTimes.new(route: @route, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
28
+ @percentile_response_times_metric_card = RailsPulse::Routes::Cards::PercentileResponseTimes.new(route: @route, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
29
+ @request_count_totals_metric_card = RailsPulse::Routes::Cards::RequestCountTotals.new(route: @route, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
30
+ @error_rate_per_route_metric_card = RailsPulse::Routes::Cards::ErrorRatePerRoute.new(route: @route, disabled_tags: disabled_tags, show_non_tagged: show_non_tagged).to_metric_card
27
31
  end
28
32
 
29
33
  def chart_model
@@ -87,7 +91,9 @@ module RailsPulse
87
91
  if show_action?
88
92
  # Only show requests that belong to time periods where we have route summaries
89
93
  # This ensures the table data is consistent with the chart data
90
- base_query = apply_tag_filters(@ransack_query.result)
94
+ # Note: We don't apply tag filters here because we want to show all requests
95
+ # for this specific route, regardless of individual request tags
96
+ base_query = @ransack_query.result
91
97
  .joins(<<~SQL)
92
98
  INNER JOIN rails_pulse_summaries ON
93
99
  rails_pulse_summaries.summarizable_id = rails_pulse_requests.route_id AND
@@ -110,7 +116,8 @@ module RailsPulse
110
116
  period_type: period_type,
111
117
  start_time: @start_time,
112
118
  params: params,
113
- disabled_tags: session_disabled_tags
119
+ disabled_tags: session_disabled_tags,
120
+ show_non_tagged: session[:show_non_tagged] != false
114
121
  ).to_table
115
122
  end
116
123
  end
@@ -135,7 +142,7 @@ module RailsPulse
135
142
  table_results = build_table_results
136
143
  handle_pagination
137
144
 
138
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
145
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
139
146
  end
140
147
 
141
148
  def handle_pagination
@@ -1,7 +1,5 @@
1
1
  module RailsPulse
2
2
  module ApplicationHelper
3
- include Pagy::Frontend
4
-
5
3
  include BreadcrumbsHelper
6
4
  include ChartHelper
7
5
  include FormattingHelper
@@ -10,6 +8,10 @@ module RailsPulse
10
8
  include FormHelper
11
9
  include TagsHelper
12
10
 
11
+ # Include Pagy frontend helpers for Pagy 8.x compatibility
12
+ # Pagy 43+ doesn't need this, but it doesn't hurt to include it
13
+ include Pagy::Frontend if defined?(Pagy::Frontend)
14
+
13
15
  # Replacement for lucide_icon helper that works with pre-compiled assets
14
16
  # Outputs a custom element that will be hydrated by Stimulus
15
17
  def rails_pulse_icon(name, options = {})
@@ -36,6 +38,49 @@ module RailsPulse
36
38
  # Backward compatibility alias - can be removed after migration
37
39
  alias_method :lucide_icon, :rails_pulse_icon
38
40
 
41
+ # Get items per page from Pagy instance (compatible with Pagy 8.x and 43+)
42
+ def pagy_items(pagy)
43
+ # Pagy 43+ uses options[:items] or has a limit method
44
+ if pagy.respond_to?(:options) && pagy.options.is_a?(Hash)
45
+ pagy.options[:items]
46
+ # Pagy 8.x uses vars[:items]
47
+ elsif pagy.respond_to?(:vars)
48
+ pagy.vars[:items]
49
+ # Fallback
50
+ else
51
+ pagy.limit || 10
52
+ end
53
+ end
54
+
55
+ # Get page URL from Pagy instance (compatible with Pagy 8.x and 43+)
56
+ def pagy_page_url(pagy, page_number)
57
+ # Pagy 43+ has page_url method
58
+ if pagy.respond_to?(:page_url)
59
+ pagy.page_url(page_number)
60
+ # Pagy 8.x requires using pagy_url_for helper
61
+ else
62
+ pagy_url_for(pagy, page_number)
63
+ end
64
+ end
65
+
66
+ # Get previous page number (compatible with Pagy 8.x and 43+)
67
+ def pagy_previous(pagy)
68
+ # Pagy 43+ uses 'previous'
69
+ if pagy.respond_to?(:previous)
70
+ pagy.previous
71
+ # Pagy 8.x uses 'prev'
72
+ elsif pagy.respond_to?(:prev)
73
+ pagy.prev
74
+ else
75
+ nil
76
+ end
77
+ end
78
+
79
+ # Get next page number (compatible with Pagy 8.x and 43+)
80
+ def pagy_next(pagy)
81
+ pagy.respond_to?(:next) ? pagy.next : nil
82
+ end
83
+
39
84
  # Make Rails Pulse routes available as rails_pulse in views
40
85
  def rails_pulse
41
86
  @rails_pulse_helper ||= RailsPulseHelper.new(self)
@@ -1,5 +1,30 @@
1
1
  module RailsPulse
2
2
  module ChartHelper
3
+ # Main chart rendering method - unified API for all chart types
4
+ # Uses Stimulus controller to handle chart initialization
5
+ def render_stimulus_chart(data, type:, **options)
6
+ chart_id = options[:id] || "rails-pulse-chart-#{SecureRandom.hex(8)}"
7
+ height = options[:height] || "400px"
8
+ width = options[:width] || "100%"
9
+ theme = options[:theme] || "railspulse"
10
+ chart_options = options[:options] || {}
11
+
12
+ # Build data attributes for Stimulus
13
+ stimulus_data = {
14
+ controller: "rails-pulse--chart",
15
+ rails_pulse__chart_type_value: type,
16
+ rails_pulse__chart_data_value: data.to_json,
17
+ rails_pulse__chart_options_value: chart_options.to_json,
18
+ rails_pulse__chart_theme_value: theme
19
+ }
20
+
21
+ content_tag(:div, "",
22
+ id: chart_id,
23
+ style: "height: #{height}; width: #{width};",
24
+ data: stimulus_data
25
+ )
26
+ end
27
+
3
28
  # Base chart options shared across all chart types
4
29
  def base_chart_options(units: nil, zoom: false)
5
30
  {
@@ -97,16 +122,21 @@ module RailsPulse
97
122
 
98
123
  private
99
124
 
125
+ # Wraps JavaScript function strings for later processing
126
+ def js_function(func_string)
127
+ "__FUNCTION_START__#{func_string}__FUNCTION_END__"
128
+ end
129
+
100
130
  def apply_tooltip_formatter(options, tooltip_formatter)
101
131
  return unless tooltip_formatter.present?
102
132
 
103
- options[:tooltip][:formatter] = RailsCharts.js(tooltip_formatter)
133
+ options[:tooltip][:formatter] = js_function(tooltip_formatter)
104
134
  end
105
135
 
106
136
  def apply_xaxis_formatter(options, xaxis_formatter)
107
137
  return unless xaxis_formatter.present?
108
138
 
109
- options[:xAxis][:axisLabel] ||= { formatter: RailsCharts.js(xaxis_formatter) }
139
+ options[:xAxis][:axisLabel] ||= { formatter: js_function(xaxis_formatter) }
110
140
  end
111
141
 
112
142
  def apply_zoom_configuration(options, zoom, zoom_start, zoom_end, chart_data)
@@ -12,6 +12,7 @@ import PopoverController from "./controllers/popover_controller";
12
12
  import FormController from "./controllers/form_controller";
13
13
 
14
14
  // Rails Pulse Controllers
15
+ import ChartController from "./controllers/chart_controller";
15
16
  import IndexController from "./controllers/index_controller";
16
17
  import ColorSchemeController from "./controllers/color_scheme_controller";
17
18
  import PaginationController from "./controllers/pagination_controller";
@@ -29,7 +30,7 @@ const application = Application.start();
29
30
  application.debug = false;
30
31
  window.Stimulus = application;
31
32
 
32
- // Make ECharts available globally for rails_charts gem
33
+ // Make ECharts available globally for chart rendering
33
34
  window.echarts = echarts;
34
35
 
35
36
  // Make Turbo available globally
@@ -42,6 +43,7 @@ application.register("rails-pulse--menu", MenuController);
42
43
  application.register("rails-pulse--popover", PopoverController);
43
44
  application.register("rails-pulse--form", FormController);
44
45
 
46
+ application.register("rails-pulse--chart", ChartController);
45
47
  application.register("rails-pulse--index", IndexController);
46
48
  application.register("rails-pulse--color-scheme", ColorSchemeController);
47
49
  application.register("rails-pulse--pagination", PaginationController);
@@ -96,59 +98,6 @@ echarts.registerTheme('railspulse', {
96
98
  "bar": { "itemStyle": { "barBorderWidth": 0 } }
97
99
  });
98
100
 
99
- // Chart resize functionality (moved from inline script for CSP compliance)
100
- window.addEventListener('resize', function() {
101
- if (window.RailsCharts && window.RailsCharts.charts) {
102
- Object.keys(window.RailsCharts.charts).forEach(function(chartID) {
103
- window.RailsCharts.charts[chartID].resize();
104
- });
105
- }
106
- });
107
-
108
- // Apply axis label colors based on current color scheme
109
- function applyChartAxisLabelColors() {
110
- if (!window.RailsCharts || !window.RailsCharts.charts) return;
111
- const scheme = document.documentElement.getAttribute('data-color-scheme');
112
- const isDark = scheme === 'dark';
113
- const axisColor = isDark ? '#ffffff' : '#999999';
114
- Object.keys(window.RailsCharts.charts).forEach(function(chartID) {
115
- const chart = window.RailsCharts.charts[chartID];
116
- try {
117
- chart.setOption({
118
- xAxis: { axisLabel: { color: axisColor } },
119
- yAxis: { axisLabel: { color: axisColor } }
120
- });
121
- } catch (e) {
122
- // noop
123
- }
124
- });
125
- }
126
-
127
- // Initial apply after charts initialize and on scheme changes
128
- document.addEventListener('DOMContentLoaded', () => {
129
- // run shortly after load to allow charts to initialize
130
- setTimeout(applyChartAxisLabelColors, 50);
131
- });
132
- document.addEventListener('rails-pulse:color-scheme-changed', applyChartAxisLabelColors);
133
-
134
- // Global function to initialize Rails Charts in any container.
135
- // This is needed as we render Rails Charts in Turbo Frames.
136
- window.initializeChartsInContainer = function(containerId) {
137
- requestAnimationFrame(() => {
138
- const container = containerId ? document.getElementById(containerId) : document;
139
- const scripts = container.querySelectorAll('script');
140
- scripts.forEach(script => {
141
- const content = script.textContent;
142
- const match = content.match(/function\s+(init_rails_charts_[a-f0-9]+)/);
143
- if (match && window[match[1]]) {
144
- window[match[1]]();
145
- }
146
- });
147
- // ensure colors are correct for any charts initialized in this container
148
- setTimeout(applyChartAxisLabelColors, 10);
149
- });
150
- };
151
-
152
101
  // Export for global access
153
102
  window.RailsPulse = {
154
103
  application,