rails_pulse 0.1.3 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +134 -16
  3. data/Rakefile +315 -83
  4. data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
  5. data/app/assets/stylesheets/rails_pulse/components/datepicker.css +191 -0
  6. data/app/assets/stylesheets/rails_pulse/components/switch.css +36 -0
  7. data/app/assets/stylesheets/rails_pulse/components/tags.css +98 -0
  8. data/app/assets/stylesheets/rails_pulse/components/utilities.css +26 -0
  9. data/app/controllers/concerns/response_range_concern.rb +15 -2
  10. data/app/controllers/concerns/tag_filter_concern.rb +26 -0
  11. data/app/controllers/concerns/time_range_concern.rb +27 -8
  12. data/app/controllers/rails_pulse/application_controller.rb +73 -0
  13. data/app/controllers/rails_pulse/queries_controller.rb +18 -21
  14. data/app/controllers/rails_pulse/requests_controller.rb +80 -35
  15. data/app/controllers/rails_pulse/routes_controller.rb +4 -2
  16. data/app/controllers/rails_pulse/tags_controller.rb +51 -0
  17. data/app/helpers/rails_pulse/application_helper.rb +2 -0
  18. data/app/helpers/rails_pulse/breadcrumbs_helper.rb +1 -1
  19. data/app/helpers/rails_pulse/chart_helper.rb +1 -1
  20. data/app/helpers/rails_pulse/form_helper.rb +75 -0
  21. data/app/helpers/rails_pulse/formatting_helper.rb +21 -2
  22. data/app/helpers/rails_pulse/tags_helper.rb +29 -0
  23. data/app/javascript/rails_pulse/application.js +6 -0
  24. data/app/javascript/rails_pulse/controllers/custom_range_controller.js +115 -0
  25. data/app/javascript/rails_pulse/controllers/datepicker_controller.js +48 -0
  26. data/app/javascript/rails_pulse/controllers/global_filters_controller.js +110 -0
  27. data/app/javascript/rails_pulse/controllers/index_controller.js +11 -3
  28. data/app/models/concerns/taggable.rb +61 -0
  29. data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +1 -1
  30. data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +1 -1
  31. data/app/models/rails_pulse/queries/cards/average_query_times.rb +1 -1
  32. data/app/models/rails_pulse/queries/cards/execution_rate.rb +56 -17
  33. data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +1 -1
  34. data/app/models/rails_pulse/queries/charts/average_query_times.rb +3 -7
  35. data/app/models/rails_pulse/queries/tables/index.rb +10 -2
  36. data/app/models/rails_pulse/query.rb +2 -0
  37. data/app/models/rails_pulse/request.rb +10 -2
  38. data/app/models/rails_pulse/requests/charts/average_response_times.rb +2 -2
  39. data/app/models/rails_pulse/requests/tables/index.rb +77 -0
  40. data/app/models/rails_pulse/route.rb +2 -0
  41. data/app/models/rails_pulse/routes/cards/average_response_times.rb +1 -1
  42. data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +1 -1
  43. data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +1 -1
  44. data/app/models/rails_pulse/routes/cards/request_count_totals.rb +16 -5
  45. data/app/models/rails_pulse/routes/tables/index.rb +14 -4
  46. data/app/models/rails_pulse/summary.rb +7 -7
  47. data/app/services/rails_pulse/analysis/query_characteristics_analyzer.rb +11 -3
  48. data/app/services/rails_pulse/summary_service.rb +2 -0
  49. data/app/views/layouts/rails_pulse/_global_filters.html.erb +84 -0
  50. data/app/views/layouts/rails_pulse/_menu_items.html.erb +5 -5
  51. data/app/views/layouts/rails_pulse/application.html.erb +8 -5
  52. data/app/views/rails_pulse/components/_metric_card.html.erb +2 -2
  53. data/app/views/rails_pulse/components/_operation_details_popover.html.erb +1 -1
  54. data/app/views/rails_pulse/components/_page_header.html.erb +20 -0
  55. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +1 -1
  56. data/app/views/rails_pulse/dashboard/index.html.erb +1 -1
  57. data/app/views/rails_pulse/operations/show.html.erb +1 -1
  58. data/app/views/rails_pulse/queries/_analysis_results.html.erb +53 -23
  59. data/app/views/rails_pulse/queries/_show_table.html.erb +33 -5
  60. data/app/views/rails_pulse/queries/_table.html.erb +4 -6
  61. data/app/views/rails_pulse/queries/index.html.erb +3 -7
  62. data/app/views/rails_pulse/queries/show.html.erb +3 -7
  63. data/app/views/rails_pulse/requests/_table.html.erb +32 -19
  64. data/app/views/rails_pulse/requests/index.html.erb +45 -55
  65. data/app/views/rails_pulse/requests/show.html.erb +1 -3
  66. data/app/views/rails_pulse/routes/_requests_table.html.erb +41 -0
  67. data/app/views/rails_pulse/routes/_table.html.erb +4 -8
  68. data/app/views/rails_pulse/routes/index.html.erb +4 -8
  69. data/app/views/rails_pulse/routes/show.html.erb +6 -12
  70. data/app/views/rails_pulse/tags/_tag_manager.html.erb +73 -0
  71. data/config/initializers/rails_charts_csp_patch.rb +32 -40
  72. data/config/routes.rb +5 -0
  73. data/db/migrate/20250930105043_install_rails_pulse_tables.rb +23 -0
  74. data/db/rails_pulse_schema.rb +4 -1
  75. data/lib/generators/rails_pulse/convert_to_migrations_generator.rb +25 -9
  76. data/lib/generators/rails_pulse/install_generator.rb +30 -7
  77. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +75 -2
  78. data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +3 -2
  79. data/lib/generators/rails_pulse/templates/rails_pulse.rb +21 -0
  80. data/lib/generators/rails_pulse/upgrade_generator.rb +147 -30
  81. data/lib/rails_pulse/configuration.rb +16 -1
  82. data/lib/rails_pulse/engine.rb +21 -0
  83. data/lib/rails_pulse/version.rb +1 -1
  84. data/public/rails-pulse-assets/rails-pulse-icons.js +16 -15
  85. data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
  86. data/public/rails-pulse-assets/rails-pulse.css +1 -1
  87. data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
  88. data/public/rails-pulse-assets/rails-pulse.js +73 -69
  89. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  90. metadata +20 -5
  91. data/app/views/rails_pulse/components/_breadcrumbs.html.erb +0 -12
  92. data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +0 -54
  93. data/db/migrate/20250916031656_add_analysis_to_rails_pulse_queries.rb +0 -13
@@ -0,0 +1,191 @@
1
+ @import url("https://esm.sh/flatpickr@4.6.13/dist/flatpickr.min.css");
2
+
3
+ .flatpickr-calendar {
4
+ --calendar-size: 250px;
5
+ --container-size: 220px;
6
+ --day-size: var(--size-8);
7
+
8
+ background: var(--color-bg);
9
+ border: 1px solid var(--color-border);
10
+ border-radius: var(--rounded-md);
11
+ box-shadow: var(--shadow-md);
12
+ font-size: var(--text-sm);
13
+ inline-size: var(--calendar-size);
14
+
15
+ .flatpickr-innerContainer {
16
+ justify-content: center;
17
+ padding-block-end: var(--size-3);
18
+ }
19
+
20
+ .flatpickr-days {
21
+ inline-size: var(--container-size);
22
+ }
23
+
24
+ .dayContainer {
25
+ inline-size: var(--container-size);
26
+ min-inline-size: var(--container-size);
27
+ max-inline-size: var(--container-size);
28
+ }
29
+
30
+ .dayContainer + .dayContainer {
31
+ box-shadow: -1px 0 0 var(--color-border);
32
+ }
33
+
34
+ .flatpickr-months {
35
+ .flatpickr-month {
36
+ color: var(--color-text);
37
+ }
38
+
39
+ span.cur-month {
40
+ font-size: var(--text-sm);
41
+ font-weight: var(--font-medium);
42
+ }
43
+
44
+ svg {
45
+ fill: var(--color-border-dark);
46
+ }
47
+
48
+ .flatpickr-prev-month:hover svg {
49
+ fill: var(--color-text);
50
+ }
51
+
52
+ .flatpickr-next-month:hover svg {
53
+ fill: var(--color-text);
54
+ }
55
+ }
56
+
57
+ .flatpickr-monthDropdown-months {
58
+ appearance: none;
59
+ border-radius: var(--rounded-md);
60
+ font-size: var(--text-sm);
61
+ font-weight: var(--font-medium);
62
+ line-height: var(--leading-normal);
63
+ padding: 0;
64
+ text-align: center;
65
+
66
+ &:hover {
67
+ background: var(--color-border-light);
68
+ }
69
+ }
70
+
71
+ .numInputWrapper {
72
+ input {
73
+ border-radius: var(--rounded-md);
74
+ color: var(--color-text);
75
+ font-size: var(--text-sm);
76
+ font-weight: var(--font-medium);
77
+ line-height: var(--leading-normal);
78
+ padding: 0;
79
+ text-align: center;
80
+ }
81
+
82
+ span {
83
+ border-color: var(--color-border);
84
+ }
85
+
86
+ span:hover {
87
+ background: transparent;
88
+ }
89
+
90
+ span.arrowUp::after {
91
+ border-bottom-color: var(--color-text);
92
+ }
93
+
94
+ span.arrowDown::after {
95
+ border-top-color: var(--color-text);
96
+ }
97
+
98
+ &:hover {
99
+ background: transparent;
100
+ }
101
+ }
102
+
103
+ .flatpickr-weekday {
104
+ color: var(--color-text-subtle);
105
+ font-weight: var(--font-normal);
106
+ }
107
+
108
+ .flatpickr-time {
109
+ .hasTime & {
110
+ border-top-color: var(--color-border);
111
+ }
112
+
113
+ .hasTime.noCalendar & {
114
+ border: 0;
115
+ }
116
+
117
+ .numInput {
118
+ background: transparent;
119
+ color: var(--color-text);
120
+ }
121
+
122
+ .flatpickr-time-separator {
123
+ color: var(--color-text);
124
+ }
125
+
126
+ .flatpickr-am-pm {
127
+ background: transparent;
128
+ color: var(--color-text);
129
+ }
130
+ }
131
+
132
+ .flatpickr-day {
133
+ border-radius: var(--rounded-md);
134
+ border-color: transparent !important;
135
+ box-shadow: none !important;
136
+ color: var(--color-text);
137
+ height: var(--day-size);
138
+ line-height: var(--day-size);
139
+ margin-block-start: var(--size-2);
140
+ max-width: var(--day-size);
141
+
142
+ &:is(.inRange) {
143
+ border-radius: 0;
144
+ }
145
+
146
+ &:is(.today, .inRange, :hover, :focus) {
147
+ background: var(--color-secondary);
148
+ color: var(--color-text);
149
+ }
150
+
151
+ &:is(
152
+ .flatpickr-disabled,
153
+ .flatpickr-disabled:hover,
154
+ .prevMonthDay,
155
+ .nextMonthDay,
156
+ .notAllowed,
157
+ .notAllowed.prevMonthDay,
158
+ .notAllowed.nextMonthDay
159
+ ) {
160
+ color: var(--color-text-subtle);
161
+ }
162
+
163
+ &:is(
164
+ .selected,
165
+ .startRange,
166
+ .endRange,
167
+ .selected.inRange,
168
+ .startRange.inRange,
169
+ .endRange.inRange,
170
+ .selected:focus,
171
+ .startRange:focus,
172
+ .endRange:focus,
173
+ .selected:hover,
174
+ .startRange:hover,
175
+ .endRange:hover,
176
+ .selected.prevMonthDay,
177
+ .startRange.prevMonthDay,
178
+ .endRange.prevMonthDay,
179
+ .selected.nextMonthDay,
180
+ .startRange.nextMonthDay,
181
+ .endRange.nextMonthDay
182
+ ) {
183
+ background: var(--color-primary);
184
+ color: var(--color-text-reversed);
185
+ }
186
+ }
187
+
188
+ &::before, &::after {
189
+ display: none;
190
+ }
191
+ }
@@ -0,0 +1,36 @@
1
+ .switch {
2
+ appearance: none;
3
+ background-color: var(--color-border);
4
+ border-color: transparent;
5
+ border-radius: var(--rounded-full);
6
+ border-width: var(--border-2);
7
+ block-size: var(--size-5);
8
+ inline-size: var(--size-9);
9
+ transition: background-color var(--time-150);
10
+
11
+ &:checked {
12
+ background-color: var(--color-primary);
13
+ }
14
+
15
+ &:checked::before {
16
+ margin-inline-start: var(--size-4);
17
+ }
18
+
19
+ &::before {
20
+ aspect-ratio: var(--aspect-square);
21
+ background-color: var(--color-text-reversed);
22
+ block-size: var(--size-full);
23
+ border-radius: var(--rounded-full);
24
+ content: "";
25
+ display: block;
26
+ transition: margin var(--time-150);
27
+ }
28
+
29
+ &:focus-visible {
30
+ outline: var(--border-2) solid var(--color-selected-dark);
31
+ }
32
+
33
+ &:disabled {
34
+ cursor: not-allowed; opacity: var(--opacity-50);
35
+ }
36
+ }
@@ -0,0 +1,98 @@
1
+ /* Tag Manager Container */
2
+ .breadcrumb-container {
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: space-between;
6
+ gap: 1rem;
7
+ flex-wrap: wrap;
8
+ }
9
+
10
+ .breadcrumb-tags {
11
+ display: flex;
12
+ align-items: center;
13
+ }
14
+
15
+ /* Tag Manager */
16
+ .tag-manager {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 0.5rem;
20
+ }
21
+
22
+ .tag-list {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 0.5rem;
26
+ flex-wrap: wrap;
27
+ }
28
+
29
+ /* Individual Tag */
30
+ .tag {
31
+ display: inline-flex;
32
+ align-items: center;
33
+ gap: 0.25rem;
34
+ padding: 0.25rem 0.5rem;
35
+ background-color: var(--color-background-secondary);
36
+ border: 1px solid var(--color-border);
37
+ border-radius: 0.375rem;
38
+ font-size: 0.875rem;
39
+ line-height: 1.25;
40
+ white-space: nowrap;
41
+ }
42
+
43
+ /* Tag Remove Button */
44
+ .tag-remove {
45
+ all: unset;
46
+ display: inline-flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ width: 1rem;
50
+ height: 1rem;
51
+ padding: 0;
52
+ margin: 0;
53
+ background: none;
54
+ border: none;
55
+ cursor: pointer;
56
+ color: currentColor;
57
+ opacity: 0.6;
58
+ transition: opacity 0.15s ease;
59
+ }
60
+
61
+ .tag-remove:hover {
62
+ opacity: 1;
63
+ }
64
+
65
+ .tag-remove span {
66
+ font-size: 1.25rem;
67
+ line-height: 1;
68
+ font-weight: bold;
69
+ }
70
+
71
+ /* Add Tag Container */
72
+ .tag-add-container {
73
+ position: relative;
74
+ display: inline-block;
75
+ }
76
+
77
+ .tag-add-button {
78
+ padding: 0.25rem 0.5rem;
79
+ font-size: 0.875rem;
80
+ line-height: 1.25;
81
+ white-space: nowrap;
82
+ }
83
+
84
+ /* Responsive Design */
85
+ @media (max-width: 768px) {
86
+ .breadcrumb-container {
87
+ flex-direction: column;
88
+ align-items: flex-start;
89
+ }
90
+
91
+ .breadcrumb-tags {
92
+ width: 100%;
93
+ }
94
+
95
+ .tag-manager {
96
+ width: 100%;
97
+ }
98
+ }
@@ -34,3 +34,29 @@
34
34
  .max-w-md { max-width: 28rem; }
35
35
  .max-w-lg { max-width: 32rem; }
36
36
  .max-w-xl { max-width: 36rem; }
37
+
38
+ /* Global filters active indicator */
39
+ .global-filters-active {
40
+ position: relative;
41
+ }
42
+
43
+ .global-filters-active::after {
44
+ content: "";
45
+ position: absolute;
46
+ top: -2px;
47
+ right: -2px;
48
+ width: 8px;
49
+ height: 8px;
50
+ background-color: var(--color-primary);
51
+ border-radius: 50%;
52
+ border: 2px solid var(--color-bg);
53
+ }
54
+
55
+ /* Flatpickr z-index fix - ensure calendar appears above dialogs */
56
+ .flatpickr-calendar,
57
+ .flatpickr-calendar.open,
58
+ .flatpickr-calendar.inline,
59
+ .flatpickr-calendar.static,
60
+ .flatpickr-calendar.static.open {
61
+ z-index: 999999 !important;
62
+ }
@@ -5,9 +5,11 @@ module ResponseRangeConcern
5
5
  ransack_params = params[:q] || {}
6
6
  thresholds = RailsPulse.configuration.public_send("#{type}_thresholds")
7
7
 
8
- # Check both avg_duration (for Summary) and duration (for Request/Operation)
9
- duration_param = ransack_params[:avg_duration] || ransack_params[:duration]
8
+ # Check all duration-related parameters
9
+ # avg_duration for Summary, duration for Request/Operation, duration_gteq for direct Ransack filtering
10
+ duration_param = ransack_params[:avg_duration] || ransack_params[:duration] || ransack_params[:duration_gteq]
10
11
 
12
+ # Priority 1: Page-specific duration filter
11
13
  if duration_param.present?
12
14
  selected_range = duration_param
13
15
  start_duration =
@@ -17,6 +19,17 @@ module ResponseRangeConcern
17
19
  when :critical then thresholds[:critical]
18
20
  else 0
19
21
  end
22
+ # Priority 2: Global performance threshold filter
23
+ elsif (global_threshold = session_global_filters["performance_threshold"]).present?
24
+ selected_range = global_threshold.to_sym
25
+ start_duration =
26
+ case global_threshold.to_sym
27
+ when :slow then thresholds[:slow]
28
+ when :very_slow then thresholds[:very_slow]
29
+ when :critical then thresholds[:critical]
30
+ else 0
31
+ end
32
+ # Priority 3: No filter (show all)
20
33
  else
21
34
  start_duration = 0
22
35
  selected_range = :all
@@ -0,0 +1,26 @@
1
+ module TagFilterConcern
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ # Apply tag filters to a query
7
+ # Excludes records that have ANY of the disabled tags
8
+ def apply_tag_filters(query)
9
+ disabled_tags = session_disabled_tags
10
+ query = disabled_tags.reduce(query) do |q, tag|
11
+ q.without_tag(tag)
12
+ end
13
+
14
+ apply_non_tagged_filter(query)
15
+ end
16
+
17
+ # Apply non-tagged filter to a query
18
+ # If show_non_tagged is false, exclude records with no tags
19
+ def apply_non_tagged_filter(query)
20
+ if session[:show_non_tagged] == false
21
+ query.with_tags
22
+ else
23
+ query
24
+ end
25
+ end
26
+ end
@@ -6,7 +6,8 @@ module TimeRangeConcern
6
6
  const_set(:TIME_RANGE_OPTIONS, [
7
7
  [ "Last 24 hours", :last_day ],
8
8
  [ "Last Week", :last_week ],
9
- [ "Last Month", :last_month ]
9
+ [ "Last Month", :last_month ],
10
+ [ "Custom Range...", :custom ]
10
11
  ].freeze)
11
12
  end
12
13
 
@@ -17,11 +18,8 @@ module TimeRangeConcern
17
18
 
18
19
  ransack_params = params[:q] || {}
19
20
 
20
- if ransack_params[:occurred_at_gteq].present?
21
- # Custom time range from chart zoom where there is no association
22
- start_time = parse_time_param(ransack_params[:occurred_at_gteq])
23
- end_time = parse_time_param(ransack_params[:occurred_at_lt])
24
- elsif ransack_params[:period_start_range]
21
+ # Priority 1: Page-specific preset from dropdown (check this first!)
22
+ if ransack_params[:period_start_range].present? && ransack_params[:period_start_range].to_sym != :custom
25
23
  # Predefined time range from dropdown
26
24
  selected_time_range = ransack_params[:period_start_range]
27
25
  start_time =
@@ -31,7 +29,26 @@ module TimeRangeConcern
31
29
  when :last_month then 1.month.ago
32
30
  else 1.day.ago # Default fallback
33
31
  end
32
+ # Priority 2: Page-specific custom datetime range from picker (only if period_start_range is :custom)
33
+ elsif ransack_params[:period_start_range].present? && ransack_params[:period_start_range].to_sym == :custom && ransack_params[:custom_date_range].present? && ransack_params[:custom_date_range].include?(" to ")
34
+ # Custom datetime range from custom range picker
35
+ dates = ransack_params[:custom_date_range].split(" to ")
36
+ start_time = parse_time_param(dates[0].strip)
37
+ end_time = parse_time_param(dates[1].strip)
38
+ selected_time_range = :custom
39
+ # Priority 3: Page-specific filters (chart zoom)
40
+ elsif ransack_params[:occurred_at_gteq].present? && ransack_params[:occurred_at_lt].present?
41
+ # Custom time range from chart zoom
42
+ start_time = parse_time_param(ransack_params[:occurred_at_gteq])
43
+ end_time = parse_time_param(ransack_params[:occurred_at_lt])
44
+ selected_time_range = :custom
45
+ # Priority 4: Global filters (from session)
46
+ elsif session_global_filters["start_time"].present? || session_global_filters["end_time"].present?
47
+ start_time = parse_time_param(session_global_filters["start_time"]) if session_global_filters["start_time"].present?
48
+ end_time = parse_time_param(session_global_filters["end_time"]) if session_global_filters["end_time"].present?
49
+ selected_time_range = :custom
34
50
  end
51
+ # Priority 5: Default time range (already set above)
35
52
 
36
53
  time_diff = (end_time.to_i - start_time.to_i) / 3600.0
37
54
 
@@ -43,7 +60,7 @@ module TimeRangeConcern
43
60
  end_time = end_time.end_of_day
44
61
  end
45
62
 
46
- [ start_time, end_time, selected_time_range, time_diff ]
63
+ [ start_time.to_i, end_time.to_i, selected_time_range, time_diff ]
47
64
  end
48
65
 
49
66
  private
@@ -53,7 +70,9 @@ module TimeRangeConcern
53
70
  when Time, DateTime
54
71
  param.in_time_zone
55
72
  when String
56
- Time.zone.parse(param)
73
+ # Parse as server local time (not UTC, not Time.zone)
74
+ # This ensures flatpickr datetime strings are interpreted in server's timezone
75
+ Time.parse(param).localtime
57
76
  else
58
77
  # Assume it's an integer timestamp
59
78
  Time.zone.at(param.to_i)
@@ -1,6 +1,8 @@
1
1
  module RailsPulse
2
2
  class ApplicationController < ActionController::Base
3
3
  before_action :authenticate_rails_pulse_user!
4
+ before_action :set_show_non_tagged_default
5
+ helper_method :session_global_filters, :session_disabled_tags
4
6
 
5
7
  def set_pagination_limit(limit = nil)
6
8
  limit = limit || params[:limit]
@@ -12,6 +14,48 @@ module RailsPulse
12
14
  end
13
15
  end
14
16
 
17
+ def set_global_filters
18
+ if params[:clear] == "true"
19
+ session.delete(:global_filters)
20
+ else
21
+ filters = session[:global_filters] || {}
22
+
23
+ # Update time filters if provided
24
+ if params[:start_time].present? && params[:end_time].present?
25
+ filters["start_time"] = params[:start_time]
26
+ filters["end_time"] = params[:end_time]
27
+ end
28
+
29
+ # Update performance threshold if provided (or remove if empty)
30
+ if params[:performance_threshold].present?
31
+ filters["performance_threshold"] = params[:performance_threshold]
32
+ else
33
+ filters.delete("performance_threshold")
34
+ end
35
+
36
+ # Update tag visibility - convert enabled tags to disabled tags
37
+ all_tags = RailsPulse.configuration.tags
38
+ enabled_tags = params[:enabled_tags] || []
39
+
40
+ # Handle "non_tagged" separately
41
+ session[:show_non_tagged] = enabled_tags.include?("non_tagged")
42
+ enabled_tags = enabled_tags - [ "non_tagged" ]
43
+
44
+ disabled_tags = all_tags - enabled_tags
45
+
46
+ if disabled_tags.any?
47
+ filters["disabled_tags"] = disabled_tags
48
+ else
49
+ filters.delete("disabled_tags")
50
+ end
51
+
52
+ session[:global_filters] = filters
53
+ end
54
+
55
+ # Redirect back to the referring page or root
56
+ redirect_back(fallback_location: root_path)
57
+ end
58
+
15
59
  private
16
60
 
17
61
  def authenticate_rails_pulse_user!
@@ -71,5 +115,34 @@ module RailsPulse
71
115
  validated_limit = limit.to_i.clamp(5, 50)
72
116
  session[:pagination_limit] = validated_limit if limit.present?
73
117
  end
118
+
119
+ def session_global_filters
120
+ session[:global_filters] || {}
121
+ end
122
+
123
+ def session_disabled_tags
124
+ session_global_filters["disabled_tags"] || []
125
+ end
126
+
127
+ # Get the minimum duration based on global performance threshold
128
+ # Returns nil if no threshold is set (show all)
129
+ # context: :route, :request, or :query
130
+ def global_performance_threshold_duration(context)
131
+ threshold = session_global_filters["performance_threshold"]
132
+ return nil unless threshold.present?
133
+
134
+ config_key = "#{context}_thresholds".to_sym
135
+ thresholds = RailsPulse.configuration.public_send(config_key)
136
+
137
+ thresholds[threshold.to_sym]
138
+ rescue StandardError => e
139
+ Rails.logger.warn "Failed to get performance threshold: #{e.message}"
140
+ nil
141
+ end
142
+
143
+ # Set default value for show_non_tagged if not already set
144
+ def set_show_non_tagged_default
145
+ session[:show_non_tagged] = true if session[:show_non_tagged].nil?
146
+ end
74
147
  end
75
148
  end
@@ -1,6 +1,7 @@
1
1
  module RailsPulse
2
2
  class QueriesController < ApplicationController
3
3
  include ChartTableConcern
4
+ include TagFilterConcern
4
5
 
5
6
  before_action :set_query, only: [ :show, :analyze ]
6
7
 
@@ -55,7 +56,7 @@ module RailsPulse
55
56
  end
56
57
 
57
58
  def table_model
58
- show_action? ? Operation : Summary
59
+ Summary
59
60
  end
60
61
 
61
62
  def chart_class
@@ -76,7 +77,10 @@ module RailsPulse
76
77
  base_params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
77
78
 
78
79
  if show_action?
79
- base_params.merge(summarizable_id_eq: @query.id)
80
+ base_params.merge(
81
+ summarizable_id_eq: @query.id,
82
+ summarizable_type_eq: "RailsPulse::Query"
83
+ )
80
84
  else
81
85
  base_params
82
86
  end
@@ -84,13 +88,14 @@ module RailsPulse
84
88
 
85
89
  def build_table_ransack_params(ransack_params)
86
90
  if show_action?
87
- # For Operation model on show page
91
+ # For Summary model on show page
88
92
  params = ransack_params.merge(
89
- occurred_at_gteq: Time.at(@table_start_time),
90
- occurred_at_lt: Time.at(@table_end_time),
91
- query_id_eq: @query.id
93
+ period_start_gteq: Time.at(@table_start_time),
94
+ period_start_lt: Time.at(@table_end_time),
95
+ summarizable_id_eq: @query.id,
96
+ summarizable_type_eq: "RailsPulse::Query"
92
97
  )
93
- params[:duration_gteq] = @start_duration if @start_duration && @start_duration > 0
98
+ params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
94
99
  params
95
100
  else
96
101
  # For Summary model on index page
@@ -104,29 +109,21 @@ module RailsPulse
104
109
  end
105
110
 
106
111
  def default_table_sort
107
- show_action? ? "occurred_at desc" : "period_start desc"
112
+ "period_start desc"
108
113
  end
109
114
 
110
115
  def build_table_results
111
116
  if show_action?
112
- # Only show operations that belong to time periods where we have query summaries
113
- # This ensures the table data is consistent with the chart data
114
- @ransack_query.result
115
- .joins(<<~SQL)
116
- INNER JOIN rails_pulse_summaries ON
117
- rails_pulse_summaries.summarizable_id = rails_pulse_operations.query_id AND
118
- rails_pulse_summaries.summarizable_type = 'RailsPulse::Query' AND
119
- rails_pulse_summaries.period_type = '#{period_type}' AND
120
- rails_pulse_operations.occurred_at >= rails_pulse_summaries.period_start AND
121
- rails_pulse_operations.occurred_at < rails_pulse_summaries.period_end
122
- SQL
123
- .distinct
117
+ # For Summary model on show page - ransack params already include query ID and type filters
118
+ # Summaries aren't taggable, so we don't apply tag filters here
119
+ @ransack_query.result.where(period_type: period_type)
124
120
  else
125
121
  Queries::Tables::Index.new(
126
122
  ransack_query: @ransack_query,
127
123
  period_type: period_type,
128
124
  start_time: @start_time,
129
- params: params
125
+ params: params,
126
+ disabled_tags: session_disabled_tags
130
127
  ).to_table
131
128
  end
132
129
  end