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.
- checksums.yaml +4 -4
- data/README.md +78 -0
- data/Rakefile +152 -3
- data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
- data/app/assets/stylesheets/rails_pulse/components/datepicker.css +191 -0
- data/app/assets/stylesheets/rails_pulse/components/switch.css +36 -0
- data/app/assets/stylesheets/rails_pulse/components/tags.css +98 -0
- data/app/assets/stylesheets/rails_pulse/components/utilities.css +26 -0
- data/app/controllers/concerns/response_range_concern.rb +15 -2
- data/app/controllers/concerns/tag_filter_concern.rb +26 -0
- data/app/controllers/concerns/time_range_concern.rb +27 -8
- data/app/controllers/rails_pulse/application_controller.rb +73 -0
- data/app/controllers/rails_pulse/queries_controller.rb +4 -1
- data/app/controllers/rails_pulse/requests_controller.rb +40 -8
- data/app/controllers/rails_pulse/routes_controller.rb +4 -2
- data/app/controllers/rails_pulse/tags_controller.rb +51 -0
- data/app/helpers/rails_pulse/application_helper.rb +2 -0
- data/app/helpers/rails_pulse/form_helper.rb +75 -0
- data/app/helpers/rails_pulse/tags_helper.rb +29 -0
- data/app/javascript/rails_pulse/application.js +6 -0
- data/app/javascript/rails_pulse/controllers/custom_range_controller.js +115 -0
- data/app/javascript/rails_pulse/controllers/datepicker_controller.js +48 -0
- data/app/javascript/rails_pulse/controllers/global_filters_controller.js +110 -0
- data/app/models/concerns/taggable.rb +61 -0
- data/app/models/rails_pulse/queries/tables/index.rb +10 -2
- data/app/models/rails_pulse/query.rb +2 -0
- data/app/models/rails_pulse/request.rb +9 -1
- data/app/models/rails_pulse/route.rb +2 -0
- data/app/models/rails_pulse/routes/tables/index.rb +10 -2
- data/app/services/rails_pulse/summary_service.rb +2 -0
- data/app/views/layouts/rails_pulse/_global_filters.html.erb +84 -0
- data/app/views/layouts/rails_pulse/_menu_items.html.erb +5 -5
- data/app/views/layouts/rails_pulse/application.html.erb +8 -5
- data/app/views/rails_pulse/components/_page_header.html.erb +20 -0
- data/app/views/rails_pulse/operations/show.html.erb +1 -1
- data/app/views/rails_pulse/queries/_table.html.erb +3 -1
- data/app/views/rails_pulse/queries/index.html.erb +3 -7
- data/app/views/rails_pulse/queries/show.html.erb +3 -7
- data/app/views/rails_pulse/requests/_table.html.erb +3 -1
- data/app/views/rails_pulse/requests/index.html.erb +44 -62
- data/app/views/rails_pulse/requests/show.html.erb +1 -1
- data/app/views/rails_pulse/routes/_requests_table.html.erb +3 -1
- data/app/views/rails_pulse/routes/_table.html.erb +3 -1
- data/app/views/rails_pulse/routes/index.html.erb +4 -8
- data/app/views/rails_pulse/routes/show.html.erb +3 -7
- data/app/views/rails_pulse/tags/_tag_manager.html.erb +73 -0
- data/config/routes.rb +5 -0
- data/db/rails_pulse_schema.rb +3 -0
- data/lib/generators/rails_pulse/install_generator.rb +21 -2
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +3 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +21 -0
- data/lib/generators/rails_pulse/upgrade_generator.rb +145 -29
- data/lib/rails_pulse/configuration.rb +16 -1
- data/lib/rails_pulse/version.rb +1 -1
- data/public/rails-pulse-assets/rails-pulse-icons.js +16 -15
- data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +73 -69
- data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
- metadata +17 -3
- 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 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
|
@@ -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"
|
57
|
-
<%=
|
58
|
-
|
59
|
-
|
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,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/
|
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
|
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/
|
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
|
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>
|