rails_pulse 0.1.4 → 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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -0
  3. data/Rakefile +152 -3
  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 +4 -1
  14. data/app/controllers/rails_pulse/requests_controller.rb +40 -8
  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/form_helper.rb +75 -0
  19. data/app/helpers/rails_pulse/tags_helper.rb +29 -0
  20. data/app/javascript/rails_pulse/application.js +6 -0
  21. data/app/javascript/rails_pulse/controllers/custom_range_controller.js +115 -0
  22. data/app/javascript/rails_pulse/controllers/datepicker_controller.js +48 -0
  23. data/app/javascript/rails_pulse/controllers/global_filters_controller.js +110 -0
  24. data/app/models/concerns/taggable.rb +61 -0
  25. data/app/models/rails_pulse/queries/tables/index.rb +10 -2
  26. data/app/models/rails_pulse/query.rb +2 -0
  27. data/app/models/rails_pulse/request.rb +9 -1
  28. data/app/models/rails_pulse/route.rb +2 -0
  29. data/app/models/rails_pulse/routes/tables/index.rb +10 -2
  30. data/app/services/rails_pulse/summary_service.rb +2 -0
  31. data/app/views/layouts/rails_pulse/_global_filters.html.erb +84 -0
  32. data/app/views/layouts/rails_pulse/_menu_items.html.erb +5 -5
  33. data/app/views/layouts/rails_pulse/application.html.erb +8 -5
  34. data/app/views/rails_pulse/components/_page_header.html.erb +20 -0
  35. data/app/views/rails_pulse/operations/show.html.erb +1 -1
  36. data/app/views/rails_pulse/queries/_table.html.erb +3 -1
  37. data/app/views/rails_pulse/queries/index.html.erb +3 -7
  38. data/app/views/rails_pulse/queries/show.html.erb +3 -7
  39. data/app/views/rails_pulse/requests/_table.html.erb +3 -1
  40. data/app/views/rails_pulse/requests/index.html.erb +44 -62
  41. data/app/views/rails_pulse/requests/show.html.erb +1 -1
  42. data/app/views/rails_pulse/routes/_requests_table.html.erb +3 -1
  43. data/app/views/rails_pulse/routes/_table.html.erb +3 -1
  44. data/app/views/rails_pulse/routes/index.html.erb +4 -8
  45. data/app/views/rails_pulse/routes/show.html.erb +3 -7
  46. data/app/views/rails_pulse/tags/_tag_manager.html.erb +73 -0
  47. data/config/routes.rb +5 -0
  48. data/db/rails_pulse_schema.rb +3 -0
  49. data/lib/generators/rails_pulse/install_generator.rb +21 -2
  50. data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +3 -0
  51. data/lib/generators/rails_pulse/templates/rails_pulse.rb +21 -0
  52. data/lib/generators/rails_pulse/upgrade_generator.rb +145 -29
  53. data/lib/rails_pulse/configuration.rb +16 -1
  54. data/lib/rails_pulse/version.rb +1 -1
  55. data/public/rails-pulse-assets/rails-pulse-icons.js +16 -15
  56. data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
  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 +73 -69
  60. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  61. metadata +17 -3
  62. data/app/views/rails_pulse/components/_breadcrumbs.html.erb +0 -12
@@ -0,0 +1,110 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["wrapper", "dialog", "dateRange", "indicator", "form"]
5
+ static values = {
6
+ active: { type: Boolean, default: false }
7
+ }
8
+
9
+ connect() {
10
+ this.updateIndicator()
11
+ }
12
+
13
+ // Open the global filters dialog
14
+ open(event) {
15
+ event.preventDefault()
16
+
17
+ // If there's a value in the date range input, make sure flatpickr knows about it
18
+ if (this.dateRangeTarget.value) {
19
+ const datepickerController = this.application.getControllerForElementAndIdentifier(
20
+ this.dateRangeTarget,
21
+ 'rails-pulse--datepicker'
22
+ )
23
+
24
+ if (datepickerController && datepickerController.flatpickr) {
25
+ const value = this.dateRangeTarget.value
26
+ // Parse the "start to end" format
27
+ if (value.includes(' to ')) {
28
+ const [start, end] = value.split(' to ').map(d => d.trim())
29
+ // Set the dates in flatpickr
30
+ datepickerController.flatpickr.setDate([start, end], false)
31
+ }
32
+ }
33
+ }
34
+
35
+ this.wrapperTarget.style.display = 'flex'
36
+ // Prevent body scroll when dialog is open
37
+ document.body.style.overflow = 'hidden'
38
+ }
39
+
40
+ // Close the dialog
41
+ close(event) {
42
+ if (event) {
43
+ event.preventDefault()
44
+ }
45
+ this.wrapperTarget.style.display = 'none'
46
+ // Restore body scroll
47
+ document.body.style.overflow = ''
48
+ }
49
+
50
+ // Close dialog when clicking outside
51
+ closeOnClickOutside(event) {
52
+ if (event.target === this.wrapperTarget) {
53
+ this.close(event)
54
+ }
55
+ }
56
+
57
+ // Handle form submission - parse date range and add individual params
58
+ submit(event) {
59
+ // If clear button was clicked, let it through as-is
60
+ if (event.submitter && event.submitter.name === "clear") {
61
+ return
62
+ }
63
+
64
+ const dateRangeValue = this.dateRangeTarget.value
65
+ const form = event.target
66
+
67
+ // Parse date range if provided
68
+ if (dateRangeValue && dateRangeValue.includes(' to ')) {
69
+ const [startTime, endTime] = dateRangeValue.split(' to ').map(d => d.trim())
70
+
71
+ // Remove any existing hidden inputs
72
+ form.querySelectorAll('input[name="start_time"], input[name="end_time"]').forEach(el => el.remove())
73
+
74
+ // Add new hidden inputs
75
+ const startInput = document.createElement('input')
76
+ startInput.type = 'hidden'
77
+ startInput.name = 'start_time'
78
+ startInput.value = startTime
79
+ form.appendChild(startInput)
80
+
81
+ const endInput = document.createElement('input')
82
+ endInput.type = 'hidden'
83
+ endInput.name = 'end_time'
84
+ endInput.value = endTime
85
+ form.appendChild(endInput)
86
+ }
87
+
88
+ // Tag switches are already being submitted as enabled_tags[]
89
+ // The controller will convert these to disabled_tags
90
+ // No additional processing needed here
91
+
92
+ // No validation needed - user can apply any combination of filters
93
+ }
94
+
95
+ // Update visual indicator based on activeValue
96
+ updateIndicator() {
97
+ if (this.hasIndicatorTarget) {
98
+ if (this.activeValue) {
99
+ this.indicatorTarget.classList.add("global-filters-active")
100
+ } else {
101
+ this.indicatorTarget.classList.remove("global-filters-active")
102
+ }
103
+ }
104
+ }
105
+
106
+ // Called when activeValue changes
107
+ activeValueChanged() {
108
+ this.updateIndicator()
109
+ }
110
+ }
@@ -0,0 +1,61 @@
1
+ module Taggable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Callbacks
6
+ before_save :ensure_tags_is_array
7
+
8
+ # Scopes with table name qualification to avoid ambiguity
9
+ scope :with_tag, ->(tag) { where("#{table_name}.tags LIKE ?", "%#{tag}%") }
10
+ scope :without_tag, ->(tag) { where.not("#{table_name}.tags LIKE ?", "%#{tag}%") }
11
+ scope :with_tags, -> { where("#{table_name}.tags IS NOT NULL AND #{table_name}.tags != '[]'") }
12
+ end
13
+
14
+ # Tag management methods
15
+ def tag_list
16
+ parsed_tags || []
17
+ end
18
+
19
+ def tag_list=(value)
20
+ self.tags = value.to_json
21
+ end
22
+
23
+ def has_tag?(tag)
24
+ tag_list.include?(tag.to_s)
25
+ end
26
+
27
+ def add_tag(tag)
28
+ current_tags = tag_list
29
+ unless current_tags.include?(tag.to_s)
30
+ current_tags << tag.to_s
31
+ self.tag_list = current_tags
32
+ save
33
+ end
34
+ end
35
+
36
+ def remove_tag(tag)
37
+ current_tags = tag_list
38
+ if current_tags.include?(tag.to_s)
39
+ current_tags.delete(tag.to_s)
40
+ self.tag_list = current_tags
41
+ save
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def parsed_tags
48
+ return [] if tags.nil? || tags.empty?
49
+ JSON.parse(tags)
50
+ rescue JSON::ParserError
51
+ []
52
+ end
53
+
54
+ def ensure_tags_is_array
55
+ if tags.nil?
56
+ self.tags = "[]"
57
+ elsif tags.is_a?(Array)
58
+ self.tags = tags.to_json
59
+ end
60
+ end
61
+ end
@@ -2,12 +2,13 @@ module RailsPulse
2
2
  module Queries
3
3
  module Tables
4
4
  class Index
5
- def initialize(ransack_query:, period_type: nil, start_time:, params:, query: nil)
5
+ def initialize(ransack_query:, period_type: nil, start_time:, params:, query: nil, disabled_tags: [])
6
6
  @ransack_query = ransack_query
7
7
  @period_type = period_type
8
8
  @start_time = start_time
9
9
  @params = params
10
10
  @query = query
11
+ @disabled_tags = disabled_tags
11
12
  end
12
13
 
13
14
  def to_table
@@ -21,6 +22,11 @@ module RailsPulse
21
22
  period_type: @period_type
22
23
  )
23
24
 
25
+ # Apply tag filters by excluding queries with disabled tags
26
+ @disabled_tags.each do |tag|
27
+ base_query = base_query.where.not("rails_pulse_queries.tags LIKE ?", "%#{tag}%")
28
+ end
29
+
24
30
  base_query = base_query.where(summarizable_id: @query.id) if @query
25
31
 
26
32
  # Apply grouping and aggregation
@@ -29,13 +35,15 @@ module RailsPulse
29
35
  "rails_pulse_summaries.summarizable_id",
30
36
  "rails_pulse_summaries.summarizable_type",
31
37
  "rails_pulse_queries.id",
32
- "rails_pulse_queries.normalized_sql"
38
+ "rails_pulse_queries.normalized_sql",
39
+ "rails_pulse_queries.tags"
33
40
  )
34
41
  .select(
35
42
  "rails_pulse_summaries.summarizable_id",
36
43
  "rails_pulse_summaries.summarizable_type",
37
44
  "rails_pulse_queries.id as query_id",
38
45
  "rails_pulse_queries.normalized_sql",
46
+ "rails_pulse_queries.tags",
39
47
  "AVG(rails_pulse_summaries.avg_duration) as avg_duration",
40
48
  "MAX(rails_pulse_summaries.max_duration) as max_duration",
41
49
  "SUM(rails_pulse_summaries.count) as execution_count",
@@ -1,5 +1,7 @@
1
1
  module RailsPulse
2
2
  class Query < RailsPulse::ApplicationRecord
3
+ include Taggable
4
+
3
5
  self.table_name = "rails_pulse_queries"
4
6
 
5
7
  # Associations
@@ -1,5 +1,7 @@
1
1
  module RailsPulse
2
2
  class Request < RailsPulse::ApplicationRecord
3
+ include Taggable
4
+
3
5
  self.table_name = "rails_pulse_requests"
4
6
 
5
7
  # Associations
@@ -17,7 +19,7 @@ module RailsPulse
17
19
  before_create :set_request_uuid
18
20
 
19
21
  def self.ransackable_attributes(auth_object = nil)
20
- %w[id route_id occurred_at duration status status_indicator route_path]
22
+ %w[id route_id occurred_at duration status status_category status_indicator route_path]
21
23
  end
22
24
 
23
25
  def self.ransackable_associations(auth_object = nil)
@@ -32,6 +34,12 @@ module RailsPulse
32
34
  Arel.sql("rails_pulse_routes.path")
33
35
  end
34
36
 
37
+ ransacker :status_category do |parent|
38
+ # Returns the first digit of the status code (2, 3, 4, or 5)
39
+ # Use FLOOR instead of CAST for cross-database compatibility
40
+ Arel.sql("FLOOR(#{parent.table[:status].name} / 100)")
41
+ end
42
+
35
43
  ransacker :status_indicator do |parent|
36
44
  # Calculate status indicator based on request_thresholds with safe defaults
37
45
  config = RailsPulse.configuration rescue nil
@@ -1,5 +1,7 @@
1
1
  module RailsPulse
2
2
  class Route < RailsPulse::ApplicationRecord
3
+ include Taggable
4
+
3
5
  self.table_name = "rails_pulse_routes"
4
6
 
5
7
  # Associations
@@ -2,11 +2,12 @@ module RailsPulse
2
2
  module Routes
3
3
  module Tables
4
4
  class Index
5
- def initialize(ransack_query:, period_type: nil, start_time:, params:)
5
+ def initialize(ransack_query:, period_type: nil, start_time:, params:, disabled_tags: [])
6
6
  @ransack_query = ransack_query
7
7
  @period_type = period_type
8
8
  @start_time = start_time
9
9
  @params = params
10
+ @disabled_tags = disabled_tags
10
11
  end
11
12
 
12
13
  def to_table
@@ -22,6 +23,11 @@ module RailsPulse
22
23
  period_type: @period_type
23
24
  )
24
25
 
26
+ # Apply tag filters by excluding routes with disabled tags
27
+ @disabled_tags.each do |tag|
28
+ base_query = base_query.where.not("rails_pulse_routes.tags LIKE ?", "%#{tag}%")
29
+ end
30
+
25
31
  base_query = base_query.where(summarizable_id: @route.id) if @route
26
32
 
27
33
  # Apply grouping and aggregation
@@ -31,7 +37,8 @@ module RailsPulse
31
37
  "rails_pulse_summaries.summarizable_type",
32
38
  "rails_pulse_routes.id",
33
39
  "rails_pulse_routes.path",
34
- "rails_pulse_routes.method"
40
+ "rails_pulse_routes.method",
41
+ "rails_pulse_routes.tags"
35
42
  )
36
43
  .select(
37
44
  "rails_pulse_summaries.summarizable_id",
@@ -39,6 +46,7 @@ module RailsPulse
39
46
  "rails_pulse_routes.id as route_id",
40
47
  "rails_pulse_routes.path",
41
48
  "rails_pulse_routes.method as route_method",
49
+ "rails_pulse_routes.tags",
42
50
  "AVG(rails_pulse_summaries.avg_duration) as avg_duration",
43
51
  "MAX(rails_pulse_summaries.max_duration) as max_duration",
44
52
  "SUM(rails_pulse_summaries.count) as count",
@@ -74,6 +74,7 @@ module RailsPulse
74
74
  route_groups = Request
75
75
  .where(occurred_at: start_time...end_time)
76
76
  .where.not(route_id: nil)
77
+ .joins(:route)
77
78
  .group(:route_id)
78
79
 
79
80
  # Calculate basic aggregates
@@ -132,6 +133,7 @@ module RailsPulse
132
133
  query_groups = Operation
133
134
  .where(occurred_at: start_time...end_time)
134
135
  .where.not(query_id: nil)
136
+ .joins(:query)
135
137
  .group(:query_id)
136
138
 
137
139
  basic_stats = query_groups.pluck(
@@ -0,0 +1,84 @@
1
+ <% global_filters = session_global_filters %>
2
+ <% has_global_filters = global_filters['start_time'].present? && global_filters['end_time'].present? %>
3
+ <% has_performance_filter = global_filters['performance_threshold'].present? %>
4
+ <% disabled_tags = global_filters['disabled_tags'] || [] %>
5
+ <% has_tag_filters = disabled_tags.any? %>
6
+ <% has_any_filters = has_global_filters || has_performance_filter %>
7
+ <% current_date_range = has_global_filters ? "#{global_filters['start_time']} to #{global_filters['end_time']}" : "" %>
8
+ <% current_threshold = global_filters['performance_threshold'] %>
9
+
10
+ <div data-controller="rails-pulse--global-filters" data-rails-pulse--global-filters-active-value="<%= has_any_filters %>">
11
+ <%= link_to '#', "aria-label": "Global filters", role: "button", data: { action: "rails-pulse--global-filters#open", "rails-pulse--global-filters-target": "indicator" } do %>
12
+ <%= rails_pulse_icon has_any_filters ? 'list-filter-plus' : 'list-filter', width: '20' %>
13
+ <% end %>
14
+
15
+ <div data-rails-pulse--global-filters-target="wrapper" data-action="click->rails-pulse--global-filters#closeOnClickOutside" style="display: none; position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.8); z-index: 1000; align-items: center; justify-content: center;">
16
+ <div class="dialog" data-rails-pulse--global-filters-target="dialog" style="position: relative; opacity: 1; transform: scale(1);">
17
+ <div class="dialog__content">
18
+ <h2 class="text-lg font-semibold mb-4">Global Filters</h2>
19
+ <p class="text-sm text-subtle mb-4">Set default time filters that persist across all pages. These can be overridden by page-specific filters.</p>
20
+
21
+ <%= form_with url: settings_global_filters_path, method: :patch, local: true, data: { action: "submit->rails-pulse--global-filters#submit" } do |form| %>
22
+ <div class="flex flex-col gap mb-4">
23
+ <div class="flex flex-col gap" style="--row-gap: 0.5rem">
24
+ <label for="global_date_range" class="text-sm font-medium">Date Range</label>
25
+ <input
26
+ type="text"
27
+ name="date_range"
28
+ id="global_date_range"
29
+ value="<%= current_date_range %>"
30
+ placeholder="Pick date range"
31
+ class="input"
32
+ data-controller="rails-pulse--datepicker"
33
+ data-rails-pulse--datepicker-mode-value="range"
34
+ data-rails-pulse--datepicker-show-months-value="2"
35
+ data-rails-pulse--datepicker-type-value="datetime"
36
+ data-rails-pulse--global-filters-target="dateRange"
37
+ />
38
+ </div>
39
+
40
+ <div class="flex flex-col gap" style="--row-gap: 0.5rem">
41
+ <label for="global_performance_threshold" class="text-sm font-medium">Performance Threshold</label>
42
+ <select name="performance_threshold" id="global_performance_threshold" class="input">
43
+ <option value="">All Requests</option>
44
+ <option value="slow" <%= 'selected' if current_threshold == 'slow' %>>Slow and Above</option>
45
+ <option value="very_slow" <%= 'selected' if current_threshold == 'very_slow' %>>Very Slow and Above</option>
46
+ <option value="critical" <%= 'selected' if current_threshold == 'critical' %>>Critical Only</option>
47
+ </select>
48
+ <p class="text-xs text-subtle">Filter requests/queries by performance threshold</p>
49
+ </div>
50
+
51
+ <div class="flex flex-col gap" style="--row-gap: 0.5rem">
52
+ <label class="text-sm font-medium">Tag Visibility</label>
53
+ <p class="text-xs text-subtle">Toggle visibility of tagged data. Unchecked tags will be hidden from all views.</p>
54
+ <div class="flex flex-col gap" style="--row-gap: 0.75rem">
55
+ <% tags = RailsPulse.configuration.tags + ["non_tagged"] %>
56
+ <% tags.each do |tag| %>
57
+ <div class="flex items-center gap">
58
+ <input
59
+ type="checkbox"
60
+ name="enabled_tags[]"
61
+ id="tag_<%= tag %>"
62
+ value="<%= tag %>"
63
+ <%= 'checked="checked"' if tag == "non_tagged" ? session[:show_non_tagged] != false : !disabled_tags.include?(tag) %>
64
+ class="switch"
65
+ role="switch"
66
+ />
67
+ <label class="text-sm font-medium" for="tag_<%= tag %>">
68
+ <%= tag.humanize %>
69
+ </label>
70
+ </div>
71
+ <% end %>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <div class="flex items-center justify-end gap">
77
+ <%= form.button "Clear", type: "submit", name: "clear", value: "true", class: "btn btn--borderless", formnovalidate: true %>
78
+ <%= form.submit "Apply Filters", class: "btn" %>
79
+ </div>
80
+ <% end %>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
@@ -8,12 +8,12 @@
8
8
  <span class="overflow-ellipsis">Routes</span>
9
9
  <% end %>
10
10
 
11
- <%= link_to requests_path, class: 'btn sidebar-menu__button' do %>
12
- <%= rails_pulse_icon 'audio-lines', width: '16' %>
13
- <span class="overflow-ellipsis">Requests</span>
14
- <% end %>
15
-
16
11
  <%= link_to queries_path, class: 'btn sidebar-menu__button' do %>
17
12
  <%= rails_pulse_icon 'database', width: '16' %>
18
13
  <span class="overflow-ellipsis">Queries</span>
19
14
  <% end %>
15
+
16
+ <%= link_to requests_path, class: 'btn sidebar-menu__button' do %>
17
+ <%= rails_pulse_icon 'audio-lines', width: '16' %>
18
+ <span class="overflow-ellipsis">Requests</span>
19
+ <% end %>
@@ -53,10 +53,14 @@
53
53
  </nav>
54
54
  </div>
55
55
 
56
- <div class="flex items-center" data-controller="rails-pulse--color-scheme">
57
- <%= link_to '#', "aria-label": "Toggle color scheme", role: "button", data: { action: "rails-pulse--color-scheme#toggle" } do %>
58
- <%= rails_pulse_icon 'sun', width: '48' %>
59
- <% end %>
56
+ <div class="flex items-center gap" style="--column-gap: 0.5rem">
57
+ <%= render 'layouts/rails_pulse/global_filters' %>
58
+
59
+ <div data-controller="rails-pulse--color-scheme">
60
+ <%= link_to '#', "aria-label": "Toggle color scheme", role: "button", data: { action: "rails-pulse--color-scheme#toggle" } do %>
61
+ <%= rails_pulse_icon 'sun', width: '48' %>
62
+ <% end %>
63
+ </div>
60
64
  </div>
61
65
  </header>
62
66
 
@@ -65,6 +69,5 @@
65
69
  <%= yield %>
66
70
  </div>
67
71
  </main>
68
-
69
72
  </body>
70
73
  </html>
@@ -0,0 +1,20 @@
1
+ <div class="breadcrumb-container">
2
+ <nav class="breadcrumb mis-2 mbs-2" aria-label="Breadcrumb">
3
+ <% breadcrumbs.each_with_index do |crumb, index| %>
4
+ <% if crumb[:current] %>
5
+ <span class="text-primary" aria-disabled="true" aria-current="page" role="link"><%= crumb[:title] %></span>
6
+ <% else %>
7
+ <%= link_to crumb[:title], crumb[:path] %>
8
+ <% end %>
9
+ <% unless index == breadcrumbs.length - 1 %>
10
+ <%= rails_pulse_icon 'chevron-right', width: 14, height: 14, class: "breadcrumb-separator mbe-1" %>
11
+ <% end %>
12
+ <% end %>
13
+ </nav>
14
+
15
+ <% if defined?(taggable) && taggable.present? %>
16
+ <div class="breadcrumb-tags">
17
+ <%= render 'rails_pulse/tags/tag_manager', taggable: taggable %>
18
+ </div>
19
+ <% end %>
20
+ </div>
@@ -1,4 +1,4 @@
1
- <%= render 'rails_pulse/components/breadcrumbs' %>
1
+ <%= render 'rails_pulse/components/page_header' %>
2
2
 
3
3
  <h1>Operation <%= @operation.id %></h1>
4
4
 
@@ -1,7 +1,8 @@
1
1
  <% columns = [
2
2
  { field: :normalized_sql, label: 'Query', class: 'w-auto' },
3
3
  { field: :avg_duration_sort, label: 'Average Query Time', class: 'w-44' },
4
- { field: :execution_count_sort, label: 'Executions', class: 'w-24' }
4
+ { field: :execution_count_sort, label: 'Executions', class: 'w-24' },
5
+ { field: nil, label: 'Tags', class: 'w-32' }
5
6
  ] %>
6
7
 
7
8
  <table class="table mbs-4" data-controller="rails-pulse--table-sort">
@@ -17,6 +18,7 @@
17
18
  </td>
18
19
  <td class="whitespace-nowrap"><%= summary.avg_duration.to_i %> ms</td>
19
20
  <td class="whitespace-nowrap"><%= number_with_delimiter summary.execution_count %></td>
21
+ <td class="whitespace-nowrap"><%= display_tag_badges(summary.tags) %></td>
20
22
  </tr>
21
23
  <% end %>
22
24
  </tbody>
@@ -1,4 +1,4 @@
1
- <%= render 'rails_pulse/components/breadcrumbs' %>
1
+ <%= render 'rails_pulse/components/page_header' %>
2
2
 
3
3
  <% unless turbo_frame_request? %>
4
4
  <div class="row">
@@ -13,13 +13,9 @@
13
13
  data-rails-pulse--index-chart-id-value="average_query_times_chart"
14
14
  >
15
15
  <%= render 'rails_pulse/components/panel', { title: 'Average Query Time', } do %>
16
- <%= search_form_for @ransack_query, url: queries_path, class: "flex items-center justify-between gap mb-4" do |form| %>
16
+ <%= search_form_for @ransack_query, url: queries_path, class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
17
17
  <div class="flex items-center grow gap">
18
- <%= form.select :period_start_range,
19
- RailsPulse::QueriesController::TIME_RANGE_OPTIONS,
20
- { selected: @selected_time_range },
21
- { class: "input" }
22
- %>
18
+ <%= time_range_selector(form, time_range_options: RailsPulse::QueriesController::TIME_RANGE_OPTIONS, selected_time_range: @selected_time_range) %>
23
19
  <%= form.select :avg_duration,
24
20
  duration_options(:query),
25
21
  { selected: @selected_response_range },
@@ -1,4 +1,4 @@
1
- <%= render 'rails_pulse/components/breadcrumbs' %>
1
+ <%= render 'rails_pulse/components/page_header', taggable: @query %>
2
2
 
3
3
  <% unless turbo_frame_request? %>
4
4
  <div class="row">
@@ -13,13 +13,9 @@
13
13
  data-rails-pulse--index-chart-id-value="query_responses_chart"
14
14
  >
15
15
  <%= render 'rails_pulse/components/panel', { title: 'Query Responses' } do %>
16
- <%= search_form_for @ransack_query, url: query_path(@query), class: "flex items-center justify-between gap mb-4" do |form| %>
16
+ <%= search_form_for @ransack_query, url: query_path(@query), class: "flex items-center justify-between gap mb-4", data: { controller: "rails-pulse--custom-range" } do |form| %>
17
17
  <div class="flex items-center grow gap">
18
- <%= form.select :period_start_range,
19
- RailsPulse::RoutesController::TIME_RANGE_OPTIONS,
20
- { selected: @selected_time_range },
21
- { class: "input" }
22
- %>
18
+ <%= time_range_selector(form, time_range_options: RailsPulse::RoutesController::TIME_RANGE_OPTIONS, selected_time_range: @selected_time_range) %>
23
19
  <%= form.select :duration,
24
20
  duration_options(:query),
25
21
  { selected: @selected_response_range },
@@ -2,7 +2,8 @@
2
2
  { field: :occurred_at, label: 'Timestamp', class: 'w-36' },
3
3
  { field: :route_path, label: 'Route', class: 'w-auto' },
4
4
  { field: :duration, label: 'Response Time', class: 'w-36' },
5
- { field: :status, label: 'Status', class: 'w-20' }
5
+ { field: :status, label: 'Status', class: 'w-20' },
6
+ { field: nil, label: 'Tags', class: 'w-32' }
6
7
  ] %>
7
8
 
8
9
  <table class="table mbs-4" data-controller="rails-pulse--table-sort">
@@ -34,6 +35,7 @@
34
35
  <td class="whitespace-nowrap">
35
36
  <span class="text-green-600"><%= request.status %></span>
36
37
  </td>
38
+ <td class="whitespace-nowrap"><%= display_tag_badges(request) %></td>
37
39
  </tr>
38
40
  <% end %>
39
41
  </tbody>