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
@@ -0,0 +1,229 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ type: String, // "bar", "line", "area", "sparkline"
6
+ data: Object, // Chart data
7
+ options: Object, // ECharts configuration
8
+ theme: String // ECharts theme
9
+ }
10
+
11
+ connect() {
12
+ this.initializeChart()
13
+ this.handleColorSchemeChange = this.onColorSchemeChange.bind(this)
14
+ document.addEventListener('rails-pulse:color-scheme-changed', this.handleColorSchemeChange)
15
+ }
16
+
17
+ disconnect() {
18
+ document.removeEventListener('rails-pulse:color-scheme-changed', this.handleColorSchemeChange)
19
+ this.disposeChart()
20
+ }
21
+
22
+ // Main initialization with retry logic
23
+ initializeChart() {
24
+ this.retryCount = 0
25
+ this.maxRetries = 100 // 5 seconds
26
+ this.attemptInit()
27
+ }
28
+
29
+ attemptInit() {
30
+ if (typeof echarts === 'undefined') {
31
+ this.retryCount++
32
+ if (this.retryCount >= this.maxRetries) {
33
+ console.error('[RailsPulse] echarts not loaded after 5 seconds for', this.element.id)
34
+ this.showError()
35
+ return
36
+ }
37
+ setTimeout(() => this.attemptInit(), 50)
38
+ return
39
+ }
40
+
41
+ this.renderChart()
42
+ }
43
+
44
+ renderChart() {
45
+ try {
46
+ // Initialize chart
47
+ this.chart = echarts.init(this.element, this.themeValue || 'railspulse')
48
+
49
+ // Build and set options
50
+ const config = this.buildChartConfig()
51
+ this.chart.setOption(config)
52
+
53
+ // Apply current color scheme
54
+ this.applyColorScheme()
55
+
56
+ // Dispatch event for other controllers (event-based communication)
57
+ document.dispatchEvent(new CustomEvent('stimulus:echarts:rendered', {
58
+ detail: {
59
+ containerId: this.element.id,
60
+ chart: this.chart,
61
+ controller: this
62
+ }
63
+ }))
64
+
65
+ // Responsive resize
66
+ this.resizeObserver = new ResizeObserver(() => {
67
+ if (this.chart) {
68
+ this.chart.resize()
69
+ }
70
+ })
71
+ this.resizeObserver.observe(this.element)
72
+
73
+ // Mark as rendered for tests
74
+ this.element.setAttribute('data-chart-rendered', 'true')
75
+
76
+ } catch (error) {
77
+ console.error('[RailsPulse] Error initializing chart:', error)
78
+ this.showError()
79
+ }
80
+ }
81
+
82
+ buildChartConfig() {
83
+ // Start with provided options
84
+ const config = { ...this.optionsValue }
85
+
86
+ // Process formatters (convert function strings to actual functions)
87
+ this.processFormatters(config)
88
+
89
+ // Set data (xAxis and series)
90
+ this.setChartData(config)
91
+
92
+ return config
93
+ }
94
+
95
+ setChartData(config) {
96
+ const data = this.dataValue
97
+
98
+ // Extract labels and values
99
+ const labels = Object.keys(data).map(k => {
100
+ const num = Number(k)
101
+ return isNaN(num) ? k : num
102
+ })
103
+
104
+ const values = Object.values(data).map(v => {
105
+ if (typeof v === 'object' && v !== null) {
106
+ return v.value !== undefined ? v.value : v
107
+ }
108
+ return v
109
+ })
110
+
111
+ // Set xAxis data
112
+ config.xAxis = config.xAxis || {}
113
+ config.xAxis.type = 'category'
114
+ config.xAxis.data = labels
115
+
116
+ // Set yAxis
117
+ config.yAxis = config.yAxis || {}
118
+ config.yAxis.type = 'value'
119
+
120
+ // Set series data
121
+ if (Array.isArray(config.series)) {
122
+ // If series is already an array, update first series
123
+ config.series[0] = config.series[0] || {}
124
+ config.series[0].type = this.typeValue
125
+ config.series[0].data = values
126
+ } else if (config.series && typeof config.series === 'object') {
127
+ // If series is a single object (from helper), convert to array
128
+ const seriesConfig = { ...config.series }
129
+ config.series = [{
130
+ type: this.typeValue,
131
+ data: values,
132
+ ...seriesConfig
133
+ }]
134
+ } else {
135
+ // No series provided, create default
136
+ config.series = [{
137
+ type: this.typeValue,
138
+ data: values
139
+ }]
140
+ }
141
+ }
142
+
143
+ processFormatters(config) {
144
+ // Process tooltip formatter
145
+ if (config.tooltip?.formatter && typeof config.tooltip.formatter === 'string') {
146
+ config.tooltip.formatter = this.parseFormatter(config.tooltip.formatter)
147
+ }
148
+
149
+ // Process xAxis formatter
150
+ if (config.xAxis?.axisLabel?.formatter && typeof config.xAxis.axisLabel.formatter === 'string') {
151
+ config.xAxis.axisLabel.formatter = this.parseFormatter(config.xAxis.axisLabel.formatter)
152
+ }
153
+
154
+ // Process yAxis formatter
155
+ if (config.yAxis?.axisLabel?.formatter && typeof config.yAxis.axisLabel.formatter === 'string') {
156
+ config.yAxis.axisLabel.formatter = this.parseFormatter(config.yAxis.axisLabel.formatter)
157
+ }
158
+ }
159
+
160
+ parseFormatter(formatterString) {
161
+ // Remove function markers if present
162
+ const cleanString = formatterString.replace(/__FUNCTION_START__|__FUNCTION_END__/g, '')
163
+
164
+ // If it's a function string, parse it
165
+ if (cleanString.trim().startsWith('function')) {
166
+ try {
167
+ // eslint-disable-next-line no-eval
168
+ return eval(`(${cleanString})`)
169
+ } catch (error) {
170
+ console.error('[RailsPulse] Error parsing formatter function:', error)
171
+ return cleanString
172
+ }
173
+ }
174
+ return cleanString
175
+ }
176
+
177
+ showError() {
178
+ this.element.classList.add('chart-error')
179
+ this.element.innerHTML = '<p class="text-subtle p-4">Chart failed to load</p>'
180
+ }
181
+
182
+ // Public accessor for chart instance
183
+ get chartInstance() {
184
+ return this.chart
185
+ }
186
+
187
+ disposeChart() {
188
+ if (this.resizeObserver) {
189
+ this.resizeObserver.disconnect()
190
+ }
191
+
192
+ if (this.chart) {
193
+ this.chart.dispose()
194
+ this.chart = null
195
+ }
196
+ }
197
+
198
+ // Action for dynamic updates
199
+ update(event) {
200
+ if (event.detail?.data) {
201
+ this.dataValue = event.detail.data
202
+ }
203
+ if (event.detail?.options) {
204
+ this.optionsValue = event.detail.options
205
+ }
206
+ if (this.chart) {
207
+ const config = this.buildChartConfig()
208
+ this.chart.setOption(config, true) // true = not merge
209
+ }
210
+ }
211
+
212
+ // Color scheme management
213
+ onColorSchemeChange() {
214
+ this.applyColorScheme()
215
+ }
216
+
217
+ applyColorScheme() {
218
+ if (!this.chart) return
219
+
220
+ const scheme = document.documentElement.getAttribute('data-color-scheme')
221
+ const isDark = scheme === 'dark'
222
+ const axisColor = isDark ? '#ffffff' : '#999999'
223
+
224
+ this.chart.setOption({
225
+ xAxis: { axisLabel: { color: axisColor } },
226
+ yAxis: { axisLabel: { color: axisColor } }
227
+ })
228
+ }
229
+ }
@@ -15,21 +15,16 @@ export default class extends Controller {
15
15
  originalSeriesOption = null;
16
16
 
17
17
  connect() {
18
- // Listen for the custom event 'chart:initialized' to set up the chart.
19
- // This event is sent from the RailsCharts library when the chart is ready.
18
+ // Listen for the custom event 'stimulus:echarts:rendered' to set up the chart.
19
+ // This event is dispatched by the chart controller when the chart is ready.
20
20
  this.handleChartInitialized = this.onChartInitialized.bind(this);
21
21
 
22
- document.addEventListener('chart:rendered', this.handleChartInitialized);
23
-
24
- // If the chart is already initialized (e.g., on back navigation), set up immediately
25
- if (window.RailsCharts?.charts?.[this.chartIdValue]) {
26
- this.setup();
27
- }
22
+ document.addEventListener('stimulus:echarts:rendered', this.handleChartInitialized);
28
23
  }
29
24
 
30
25
  disconnect() {
31
- // Remove the event listener from RailsCharts when the controller is disconnected
32
- document.removeEventListener('chart:rendered', this.handleChartInitialized);
26
+ // Remove the event listener when the controller is disconnected
27
+ document.removeEventListener('stimulus:echarts:rendered', this.handleChartInitialized);
33
28
 
34
29
  // Remove chart event listeners if they exist
35
30
  if (this.hasChartTarget && this.chartTarget) {
@@ -47,6 +42,8 @@ export default class extends Controller {
47
42
  // After the chart is initialized, set up the event listeners and data tracking
48
43
  onChartInitialized(event) {
49
44
  if (event.detail.containerId === this.chartIdValue) {
45
+ // Store the chart instance from the event
46
+ this.chart = event.detail.chart;
50
47
  this.setup();
51
48
  }
52
49
  }
@@ -56,7 +53,7 @@ export default class extends Controller {
56
53
  return; // Prevent multiple setups
57
54
  }
58
55
 
59
- // We need both the chart target in DOM and the chart object from RailsCharts
56
+ // We need both the chart target in DOM and the chart object from the event
60
57
  let hasTarget = false;
61
58
  try {
62
59
  hasTarget = !!this.chartTarget;
@@ -64,10 +61,8 @@ export default class extends Controller {
64
61
  hasTarget = false;
65
62
  }
66
63
 
67
- // Get the chart element which the RailsCharts library has created
68
- this.chart = window.RailsCharts.charts[this.chartIdValue];
69
-
70
64
  // Only proceed if we have BOTH the DOM target and the chart object
65
+ // (chart is set by onChartInitialized from the event)
71
66
  if (!hasTarget || !this.chart) {
72
67
  return;
73
68
  }
@@ -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
 
@@ -0,0 +1,63 @@
1
+ module RailsPulse
2
+ module Taggable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ # Callbacks
7
+ before_save :ensure_tags_is_array
8
+
9
+ # Scopes with table name qualification to avoid ambiguity
10
+ scope :with_tag, ->(tag) { where("#{table_name}.tags LIKE ?", "%#{tag}%") }
11
+ scope :without_tag, ->(tag) { where.not("#{table_name}.tags LIKE ?", "%#{tag}%") }
12
+ scope :with_tags, -> { where("#{table_name}.tags IS NOT NULL AND #{table_name}.tags != '[]'") }
13
+ end
14
+
15
+ # Tag management methods
16
+ def tag_list
17
+ parsed_tags || []
18
+ end
19
+
20
+ def tag_list=(value)
21
+ self.tags = value.to_json
22
+ end
23
+
24
+ def has_tag?(tag)
25
+ tag_list.include?(tag.to_s)
26
+ end
27
+
28
+ def add_tag(tag)
29
+ current_tags = tag_list
30
+ unless current_tags.include?(tag.to_s)
31
+ current_tags << tag.to_s
32
+ self.tag_list = current_tags
33
+ save
34
+ end
35
+ end
36
+
37
+ def remove_tag(tag)
38
+ current_tags = tag_list
39
+ if current_tags.include?(tag.to_s)
40
+ current_tags.delete(tag.to_s)
41
+ self.tag_list = current_tags
42
+ save
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def parsed_tags
49
+ return [] if tags.nil? || tags.empty?
50
+ JSON.parse(tags)
51
+ rescue JSON::ParserError
52
+ []
53
+ end
54
+
55
+ def ensure_tags_is_array
56
+ if tags.nil?
57
+ self.tags = "[]"
58
+ elsif tags.is_a?(Array)
59
+ self.tags = tags.to_json
60
+ end
61
+ end
62
+ end
63
+ end
@@ -2,6 +2,11 @@ module RailsPulse
2
2
  module Dashboard
3
3
  module Charts
4
4
  class AverageResponseTime
5
+ def initialize(disabled_tags: [], show_non_tagged: true)
6
+ @disabled_tags = disabled_tags
7
+ @show_non_tagged = show_non_tagged
8
+ end
9
+
5
10
  def to_chart_data
6
11
  # Create a range of all dates in the past 2 weeks
7
12
  start_date = 2.weeks.ago.beginning_of_day.to_date
@@ -9,11 +14,13 @@ module RailsPulse
9
14
  date_range = (start_date..end_date)
10
15
 
11
16
  # Get the actual data from Summary records (routes)
12
- summaries = RailsPulse::Summary.where(
13
- summarizable_type: "RailsPulse::Route",
14
- period_type: "day",
15
- period_start: start_date.beginning_of_day..end_date.end_of_day
16
- )
17
+ summaries = RailsPulse::Summary
18
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
19
+ .where(
20
+ summarizable_type: "RailsPulse::Route",
21
+ period_type: "day",
22
+ period_start: start_date.beginning_of_day..end_date.end_of_day
23
+ )
17
24
 
18
25
  # Group by day manually for cross-database compatibility
19
26
  actual_data = {}
@@ -2,6 +2,11 @@ module RailsPulse
2
2
  module Dashboard
3
3
  module Charts
4
4
  class P95ResponseTime
5
+ def initialize(disabled_tags: [], show_non_tagged: true)
6
+ @disabled_tags = disabled_tags
7
+ @show_non_tagged = show_non_tagged
8
+ end
9
+
5
10
  def to_chart_data
6
11
  # Create a range of all dates in the past 2 weeks
7
12
  start_date = 2.weeks.ago.beginning_of_day.to_date
@@ -9,11 +14,13 @@ module RailsPulse
9
14
  date_range = (start_date..end_date)
10
15
 
11
16
  # Get the actual data from Summary records (queries for P95)
12
- summaries = RailsPulse::Summary.where(
13
- summarizable_type: "RailsPulse::Query",
14
- period_type: "day",
15
- period_start: start_date.beginning_of_day..end_date.end_of_day
16
- )
17
+ summaries = RailsPulse::Summary
18
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
19
+ .where(
20
+ summarizable_type: "RailsPulse::Query",
21
+ period_type: "day",
22
+ period_start: start_date.beginning_of_day..end_date.end_of_day
23
+ )
17
24
 
18
25
  actual_data = summaries
19
26
  .group_by_day(:period_start, time_zone: Time.zone)
@@ -3,6 +3,12 @@ module RailsPulse
3
3
  module Tables
4
4
  class SlowQueries
5
5
  include RailsPulse::FormattingHelper
6
+
7
+ def initialize(disabled_tags: [], show_non_tagged: true)
8
+ @disabled_tags = disabled_tags
9
+ @show_non_tagged = show_non_tagged
10
+ end
11
+
6
12
  def to_table_data
7
13
  # Get data for this week
8
14
  this_week_start = 1.week.ago.beginning_of_week
@@ -10,6 +16,7 @@ module RailsPulse
10
16
 
11
17
  # Fetch query data from Summary records for this week
12
18
  query_data = RailsPulse::Summary
19
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
13
20
  .joins("INNER JOIN rails_pulse_queries ON rails_pulse_queries.id = rails_pulse_summaries.summarizable_id")
14
21
  .where(
15
22
  summarizable_type: "RailsPulse::Query",
@@ -4,6 +4,11 @@ module RailsPulse
4
4
  class SlowRoutes
5
5
  include RailsPulse::FormattingHelper
6
6
 
7
+ def initialize(disabled_tags: [], show_non_tagged: true)
8
+ @disabled_tags = disabled_tags
9
+ @show_non_tagged = show_non_tagged
10
+ end
11
+
7
12
  def to_table_data
8
13
  # Get data for this week
9
14
  this_week_start = 1.week.ago.beginning_of_week
@@ -11,6 +16,7 @@ module RailsPulse
11
16
 
12
17
  # Fetch route data from Summary records for this week
13
18
  route_data = RailsPulse::Summary
19
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
14
20
  .joins("INNER JOIN rails_pulse_routes ON rails_pulse_routes.id = rails_pulse_summaries.summarizable_id")
15
21
  .where(
16
22
  summarizable_type: "RailsPulse::Route",
@@ -2,8 +2,10 @@ module RailsPulse
2
2
  module Queries
3
3
  module Cards
4
4
  class AverageQueryTimes
5
- def initialize(query: nil)
5
+ def initialize(query: nil, disabled_tags: [], show_non_tagged: true)
6
6
  @query = query
7
+ @disabled_tags = disabled_tags
8
+ @show_non_tagged = show_non_tagged
7
9
  end
8
10
 
9
11
  def to_metric_card
@@ -11,11 +13,13 @@ module RailsPulse
11
13
  previous_7_days = 14.days.ago.beginning_of_day
12
14
 
13
15
  # Single query to get all aggregated metrics with conditional sums
14
- base_query = RailsPulse::Summary.where(
15
- summarizable_type: "RailsPulse::Query",
16
- period_type: "day",
17
- period_start: 2.weeks.ago.beginning_of_day..Time.current
18
- )
16
+ base_query = RailsPulse::Summary
17
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
18
+ .where(
19
+ summarizable_type: "RailsPulse::Query",
20
+ period_type: "day",
21
+ period_start: 2.weeks.ago.beginning_of_day..Time.current
22
+ )
19
23
  base_query = base_query.where(summarizable_id: @query.id) if @query
20
24
 
21
25
  metrics = base_query.select(
@@ -2,8 +2,10 @@ module RailsPulse
2
2
  module Queries
3
3
  module Cards
4
4
  class ExecutionRate
5
- def initialize(query: nil)
5
+ def initialize(query: nil, disabled_tags: [], show_non_tagged: true)
6
6
  @query = query
7
+ @disabled_tags = disabled_tags
8
+ @show_non_tagged = show_non_tagged
7
9
  end
8
10
 
9
11
  def to_metric_card
@@ -12,20 +14,24 @@ module RailsPulse
12
14
 
13
15
  # Get the most common period type for this query, or fall back to "day"
14
16
  period_type = if @query
15
- RailsPulse::Summary.where(
16
- summarizable_type: "RailsPulse::Query",
17
- summarizable_id: @query.id
18
- ).group(:period_type).count.max_by(&:last)&.first || "day"
17
+ RailsPulse::Summary
18
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
19
+ .where(
20
+ summarizable_type: "RailsPulse::Query",
21
+ summarizable_id: @query.id
22
+ ).group(:period_type).count.max_by(&:last)&.first || "day"
19
23
  else
20
24
  "day"
21
25
  end
22
26
 
23
27
  # Single query to get all count metrics with conditional aggregation
24
- base_query = RailsPulse::Summary.where(
25
- summarizable_type: "RailsPulse::Query",
26
- period_type: period_type,
27
- period_start: 2.weeks.ago.beginning_of_day..Time.current
28
- )
28
+ base_query = RailsPulse::Summary
29
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
30
+ .where(
31
+ summarizable_type: "RailsPulse::Query",
32
+ period_type: period_type,
33
+ period_start: 2.weeks.ago.beginning_of_day..Time.current
34
+ )
29
35
  base_query = base_query.where(summarizable_id: @query.id) if @query
30
36
 
31
37
  metrics = base_query.select(
@@ -2,8 +2,10 @@ module RailsPulse
2
2
  module Queries
3
3
  module Cards
4
4
  class PercentileQueryTimes
5
- def initialize(query: nil)
5
+ def initialize(query: nil, disabled_tags: [], show_non_tagged: true)
6
6
  @query = query
7
+ @disabled_tags = disabled_tags
8
+ @show_non_tagged = show_non_tagged
7
9
  end
8
10
 
9
11
  def to_metric_card
@@ -11,11 +13,13 @@ module RailsPulse
11
13
  previous_7_days = 14.days.ago.beginning_of_day
12
14
 
13
15
  # Single query to get all P95 metrics with conditional aggregation
14
- base_query = RailsPulse::Summary.where(
15
- summarizable_type: "RailsPulse::Query",
16
- period_type: "day",
17
- period_start: 2.weeks.ago.beginning_of_day..Time.current
18
- )
16
+ base_query = RailsPulse::Summary
17
+ .with_tag_filters(@disabled_tags, @show_non_tagged)
18
+ .where(
19
+ summarizable_type: "RailsPulse::Query",
20
+ period_type: "day",
21
+ period_start: 2.weeks.ago.beginning_of_day..Time.current
22
+ )
19
23
  base_query = base_query.where(summarizable_id: @query.id) if @query
20
24
 
21
25
  metrics = base_query.select(