rails_pulse 0.1.2 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +66 -20
- data/Rakefile +169 -86
- data/app/assets/images/rails_pulse/dashboard.png +0 -0
- data/app/assets/images/rails_pulse/request.png +0 -0
- data/app/assets/stylesheets/rails_pulse/application.css +28 -5
- data/app/assets/stylesheets/rails_pulse/components/badge.css +13 -0
- data/app/assets/stylesheets/rails_pulse/components/base.css +12 -2
- data/app/assets/stylesheets/rails_pulse/components/collapsible.css +30 -0
- data/app/assets/stylesheets/rails_pulse/components/popover.css +0 -1
- data/app/assets/stylesheets/rails_pulse/components/row.css +55 -3
- data/app/assets/stylesheets/rails_pulse/components/sidebar_menu.css +23 -0
- data/app/controllers/concerns/zoom_range_concern.rb +31 -0
- data/app/controllers/rails_pulse/application_controller.rb +5 -1
- data/app/controllers/rails_pulse/queries_controller.rb +49 -10
- data/app/controllers/rails_pulse/requests_controller.rb +46 -20
- data/app/controllers/rails_pulse/routes_controller.rb +40 -1
- data/app/helpers/rails_pulse/breadcrumbs_helper.rb +1 -1
- data/app/helpers/rails_pulse/chart_helper.rb +16 -8
- data/app/helpers/rails_pulse/formatting_helper.rb +21 -2
- data/app/javascript/rails_pulse/application.js +34 -3
- data/app/javascript/rails_pulse/controllers/collapsible_controller.js +32 -0
- data/app/javascript/rails_pulse/controllers/color_scheme_controller.js +2 -1
- data/app/javascript/rails_pulse/controllers/expandable_rows_controller.js +58 -0
- data/app/javascript/rails_pulse/controllers/index_controller.js +249 -11
- data/app/javascript/rails_pulse/controllers/popover_controller.js +28 -4
- data/app/javascript/rails_pulse/controllers/table_sort_controller.js +14 -0
- data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +1 -1
- data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +1 -1
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +20 -20
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +58 -14
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +14 -9
- data/app/models/rails_pulse/queries/charts/average_query_times.rb +3 -7
- data/app/models/rails_pulse/query.rb +46 -0
- data/app/models/rails_pulse/request.rb +1 -1
- data/app/models/rails_pulse/requests/charts/average_response_times.rb +2 -2
- data/app/models/rails_pulse/requests/tables/index.rb +77 -0
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +18 -20
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +14 -9
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +14 -9
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +29 -13
- data/app/models/rails_pulse/routes/tables/index.rb +4 -2
- data/app/models/rails_pulse/summary.rb +7 -7
- data/app/services/rails_pulse/analysis/backtrace_analyzer.rb +256 -0
- data/app/services/rails_pulse/analysis/base_analyzer.rb +67 -0
- data/app/services/rails_pulse/analysis/explain_plan_analyzer.rb +206 -0
- data/app/services/rails_pulse/analysis/index_recommendation_engine.rb +326 -0
- data/app/services/rails_pulse/analysis/n_plus_one_detector.rb +241 -0
- data/app/services/rails_pulse/analysis/query_characteristics_analyzer.rb +154 -0
- data/app/services/rails_pulse/analysis/suggestion_generator.rb +217 -0
- data/app/services/rails_pulse/query_analysis_service.rb +125 -0
- data/app/views/layouts/rails_pulse/_sidebar_menu.html.erb +0 -1
- data/app/views/layouts/rails_pulse/application.html.erb +0 -2
- data/app/views/rails_pulse/components/_breadcrumbs.html.erb +1 -1
- data/app/views/rails_pulse/components/_code_panel.html.erb +17 -3
- data/app/views/rails_pulse/components/_empty_state.html.erb +1 -1
- data/app/views/rails_pulse/components/_metric_card.html.erb +28 -5
- data/app/views/rails_pulse/components/_operation_details_popover.html.erb +1 -1
- data/app/views/rails_pulse/components/_panel.html.erb +1 -1
- data/app/views/rails_pulse/components/_sparkline_stats.html.erb +5 -7
- data/app/views/rails_pulse/components/_table_head.html.erb +6 -1
- data/app/views/rails_pulse/dashboard/index.html.erb +2 -2
- data/app/views/rails_pulse/operations/show.html.erb +17 -15
- data/app/views/rails_pulse/queries/_analysis_error.html.erb +15 -0
- data/app/views/rails_pulse/queries/_analysis_prompt.html.erb +27 -0
- data/app/views/rails_pulse/queries/_analysis_results.html.erb +117 -0
- data/app/views/rails_pulse/queries/_analysis_section.html.erb +39 -0
- data/app/views/rails_pulse/queries/_show_table.html.erb +34 -6
- data/app/views/rails_pulse/queries/_table.html.erb +4 -8
- data/app/views/rails_pulse/queries/index.html.erb +48 -51
- data/app/views/rails_pulse/queries/show.html.erb +56 -52
- data/app/views/rails_pulse/requests/_operations.html.erb +30 -43
- data/app/views/rails_pulse/requests/_table.html.erb +31 -18
- data/app/views/rails_pulse/requests/index.html.erb +55 -50
- data/app/views/rails_pulse/requests/show.html.erb +0 -2
- data/app/views/rails_pulse/routes/_requests_table.html.erb +39 -0
- data/app/views/rails_pulse/routes/_table.html.erb +4 -10
- data/app/views/rails_pulse/routes/index.html.erb +49 -52
- data/app/views/rails_pulse/routes/show.html.erb +6 -8
- data/config/initializers/rails_charts_csp_patch.rb +32 -40
- data/config/routes.rb +5 -1
- data/db/migrate/20250930105043_install_rails_pulse_tables.rb +23 -0
- data/db/rails_pulse_schema.rb +10 -1
- data/lib/generators/rails_pulse/convert_to_migrations_generator.rb +81 -0
- data/lib/generators/rails_pulse/install_generator.rb +75 -18
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +72 -2
- data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +23 -0
- data/lib/generators/rails_pulse/templates/migrations/upgrade_rails_pulse_tables.rb +19 -0
- data/lib/generators/rails_pulse/upgrade_generator.rb +226 -0
- data/lib/rails_pulse/engine.rb +21 -0
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/tasks/rails_pulse.rake +27 -8
- 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 +53 -53
- data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
- metadata +25 -6
- data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
- data/app/assets/images/rails_pulse/routes.png +0 -0
- data/app/javascript/rails_pulse/controllers/expandable_row_controller.js +0 -67
- data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +0 -54
@@ -9,63 +9,60 @@
|
|
9
9
|
<% end %>
|
10
10
|
|
11
11
|
<div
|
12
|
-
class="row"
|
13
12
|
data-controller="rails-pulse--index"
|
14
13
|
data-rails-pulse--index-chart-id-value="average_query_times_chart"
|
15
14
|
>
|
16
|
-
|
17
|
-
<%=
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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| %>
|
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
|
+
%>
|
23
|
+
<%= form.select :avg_duration,
|
24
|
+
duration_options(:query),
|
25
|
+
{ selected: @selected_response_range },
|
26
|
+
{ class: "input" }
|
27
|
+
%>
|
28
|
+
<%= link_to "Reset", queries_path, class: "btn btn--borderless show@md" if params.has_key?(:q) %>
|
29
|
+
<%= form.submit "Search", class: "btn show@sm" %>
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
|
33
|
+
<% if @has_data %>
|
34
|
+
<% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
|
35
|
+
<div
|
36
|
+
class="chart-container chart-container--slim"
|
37
|
+
data-rails-pulse--index-target="chart"
|
38
|
+
>
|
39
|
+
<%= bar_chart(
|
40
|
+
@chart_data,
|
41
|
+
code: false,
|
42
|
+
id: "average_query_times_chart",
|
43
|
+
height: "100%",
|
44
|
+
options: bar_chart_options(
|
45
|
+
units: "ms",
|
46
|
+
zoom: true,
|
47
|
+
chart_start: 0,
|
48
|
+
chart_end: @chart_data.length - 1,
|
49
|
+
xaxis_formatter: @xaxis_formatter,
|
50
|
+
tooltip_formatter: @tooltip_formatter,
|
51
|
+
zoom_start: @zoom_start,
|
52
|
+
zoom_end: @zoom_end,
|
53
|
+
chart_data: @chart_data
|
54
|
+
)
|
55
|
+
) %>
|
32
56
|
</div>
|
33
57
|
<% end %>
|
34
58
|
|
35
|
-
|
36
|
-
|
37
|
-
<div
|
38
|
-
class="chart-container chart-container--slim"
|
39
|
-
data-rails-pulse--index-target="chart"
|
40
|
-
>
|
41
|
-
<%= bar_chart(
|
42
|
-
@chart_data,
|
43
|
-
code: false,
|
44
|
-
id: "average_query_times_chart",
|
45
|
-
height: "100%",
|
46
|
-
options: bar_chart_options(
|
47
|
-
units: "ms",
|
48
|
-
zoom: true,
|
49
|
-
chart_start: 0,
|
50
|
-
chart_end: @chart_data.length - 1,
|
51
|
-
xaxis_formatter: @xaxis_formatter,
|
52
|
-
tooltip_formatter: @tooltip_formatter,
|
53
|
-
zoom_start: @zoom_start,
|
54
|
-
zoom_end: @zoom_end,
|
55
|
-
chart_data: @chart_data
|
56
|
-
)
|
57
|
-
) %>
|
58
|
-
</div>
|
59
|
-
<% end %>
|
60
|
-
|
61
|
-
<%= turbo_frame_tag :queries_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
62
|
-
<%= render 'rails_pulse/queries/table' %>
|
63
|
-
<% end %>
|
64
|
-
<% else %>
|
65
|
-
<%= render 'rails_pulse/components/empty_state',
|
66
|
-
title: 'No query data found for the selected filters.',
|
67
|
-
description: 'Try adjusting your time range or filters to see results.' %>
|
59
|
+
<%= turbo_frame_tag :index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
60
|
+
<%= render 'rails_pulse/queries/table' %>
|
68
61
|
<% end %>
|
62
|
+
<% else %>
|
63
|
+
<%= render 'rails_pulse/components/empty_state',
|
64
|
+
title: 'No query data found for the selected filters.',
|
65
|
+
description: 'Try adjusting your time range or filters to see results.' %>
|
69
66
|
<% end %>
|
70
|
-
|
67
|
+
<% end %>
|
71
68
|
</div>
|
@@ -8,69 +8,73 @@
|
|
8
8
|
</div>
|
9
9
|
<% end %>
|
10
10
|
|
11
|
-
<div class="mb-4">
|
12
|
-
<%= render 'rails_pulse/components/code_panel', { title: 'Normalized SQL', value: @query.normalized_sql } %>
|
13
|
-
</div>
|
14
|
-
|
15
11
|
<div
|
16
|
-
class="row"
|
17
12
|
data-controller="rails-pulse--index"
|
18
13
|
data-rails-pulse--index-chart-id-value="query_responses_chart"
|
19
14
|
>
|
20
|
-
|
21
|
-
<%=
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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| %>
|
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
|
+
%>
|
23
|
+
<%= form.select :duration,
|
24
|
+
duration_options(:query),
|
25
|
+
{ selected: @selected_response_range },
|
26
|
+
{ class: "input" }
|
27
|
+
%>
|
28
|
+
<%= link_to "Reset", query_path(@query), class: "btn btn--borderless show@md" if params.has_key?(:q) %>
|
29
|
+
<%= form.submit "Search", class: "btn show@sm" %>
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
|
33
|
+
<% if @has_data %>
|
34
|
+
<% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
|
35
|
+
<div
|
36
|
+
class="chart-container chart-container--slim"
|
37
|
+
data-rails-pulse--index-target="chart"
|
38
|
+
>
|
39
|
+
<%= bar_chart(
|
40
|
+
@chart_data,
|
41
|
+
code: false,
|
42
|
+
id: "query_responses_chart",
|
43
|
+
height: "100%",
|
44
|
+
options: bar_chart_options(
|
45
|
+
units: "ms",
|
46
|
+
zoom: true,
|
47
|
+
chart_start: 0,
|
48
|
+
chart_end: @chart_data.length - 1,
|
49
|
+
xaxis_formatter: @xaxis_formatter,
|
50
|
+
tooltip_formatter: @tooltip_formatter,
|
51
|
+
zoom_start: @zoom_start,
|
52
|
+
zoom_end: @zoom_end,
|
53
|
+
chart_data: @chart_data
|
54
|
+
)
|
55
|
+
) %>
|
36
56
|
</div>
|
37
57
|
<% end %>
|
38
58
|
|
39
|
-
|
40
|
-
|
41
|
-
<div
|
42
|
-
class="chart-container chart-container--slim"
|
43
|
-
data-rails-pulse--index-target="chart"
|
44
|
-
>
|
45
|
-
<%= bar_chart(
|
46
|
-
@chart_data,
|
47
|
-
code: false,
|
48
|
-
id: "query_responses_chart",
|
49
|
-
height: "100%",
|
50
|
-
options: bar_chart_options(
|
51
|
-
units: "ms",
|
52
|
-
zoom: true,
|
53
|
-
chart_start: 0,
|
54
|
-
chart_end: @chart_data.length - 1,
|
55
|
-
xaxis_formatter: @xaxis_formatter,
|
56
|
-
tooltip_formatter: @tooltip_formatter
|
57
|
-
)
|
58
|
-
) %>
|
59
|
-
</div>
|
60
|
-
<% end %>
|
61
|
-
|
62
|
-
<%= turbo_frame_tag :index_table do %>
|
63
|
-
<%= render 'rails_pulse/queries/show_table' %>
|
64
|
-
<% end %>
|
65
|
-
<% else %>
|
66
|
-
<%= render 'rails_pulse/components/empty_state',
|
67
|
-
title: 'No query responses found for the selected filters.',
|
68
|
-
description: 'Try adjusting your time range or filters to see results.' %>
|
59
|
+
<%= turbo_frame_tag :index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
60
|
+
<%= render 'rails_pulse/queries/show_table' %>
|
69
61
|
<% end %>
|
62
|
+
<% else %>
|
63
|
+
<%= render 'rails_pulse/components/empty_state',
|
64
|
+
title: 'No query responses found for the selected filters.',
|
65
|
+
description: 'Try adjusting your time range or filters to see results.' %>
|
70
66
|
<% end %>
|
71
|
-
|
67
|
+
<% end %>
|
68
|
+
</div>
|
69
|
+
|
70
|
+
<div class="mb-4">
|
71
|
+
<%= render 'rails_pulse/components/code_panel', { title: 'Normalized Query', value: @query.normalized_sql } %>
|
72
72
|
</div>
|
73
73
|
|
74
|
+
<%= turbo_frame_tag "query_analysis", class: "mb-4" do %>
|
75
|
+
<%= render 'rails_pulse/queries/analysis_section', query: @query %>
|
76
|
+
<% end %>
|
77
|
+
|
74
78
|
<%= render 'rails_pulse/components/panel', { title: 'Query Locations' } do %>
|
75
79
|
<table class="table">
|
76
80
|
<thead>
|
@@ -1,33 +1,24 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
<
|
6
|
-
<
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
<% @operation_timeline.bars.each_with_index do |bar, index| %>
|
20
|
-
<tbody data-controller="rails-pulse--expandable-row">
|
21
|
-
<!-- Main operation row -->
|
22
|
-
<tr
|
23
|
-
class="<%= cycle('bg-shade', 'bg-white') %> operation-row"
|
24
|
-
data-rails-pulse--expandable-row-target="trigger"
|
25
|
-
data-operation-id="<%= bar.operation.id %>"
|
26
|
-
>
|
1
|
+
<%= render 'rails_pulse/components/panel', { title: 'Event Sequence ' } do %>
|
2
|
+
<% if operations.any? %>
|
3
|
+
<table class="table operations-table">
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th class="w-8"></th>
|
7
|
+
<th class="operations-label-cell">Operation</th>
|
8
|
+
<th class="operations-duration-cell">Duration</th>
|
9
|
+
<th class="w-20">Impact</th>
|
10
|
+
<th class="w-16"></th>
|
11
|
+
<th class="operations-event-cell">Timeline</th>
|
12
|
+
<th class="w-16">Actions</th>
|
13
|
+
</tr>
|
14
|
+
</thead>
|
15
|
+
<tbody data-controller="rails-pulse--expandable-rows" data-action="click->rails-pulse--expandable-rows#toggle">
|
16
|
+
<% total_request_duration = @request.duration.to_f %>
|
17
|
+
<% @operation_timeline.bars.each_with_index do |bar, index| %>
|
18
|
+
<tr data-operation-id="<%= bar.operation.id %>">
|
27
19
|
<!-- Toggle chevron column -->
|
28
|
-
<td class="text-center cursor-pointer"
|
29
|
-
|
30
|
-
<div data-rails-pulse--expandable-row-target="chevron">
|
20
|
+
<td class="text-center cursor-pointer">
|
21
|
+
<div class="chevron mbs-2">
|
31
22
|
<%= rails_pulse_icon('chevron-right', width: '16', class: 'transition-transform duration-200') %>
|
32
23
|
</div>
|
33
24
|
</td>
|
@@ -69,23 +60,19 @@
|
|
69
60
|
<!-- Expandable details row -->
|
70
61
|
<tr
|
71
62
|
class="operation-details-row hidden"
|
72
|
-
data-rails-pulse--expandable-row-target="details"
|
73
63
|
data-operation-id="<%= bar.operation.id %>"
|
74
64
|
>
|
75
|
-
<td colspan="7" class="
|
65
|
+
<td colspan="7" class="pi-8 pb-4">
|
76
66
|
<%= turbo_frame_tag "operation_#{bar.operation.id}_details", data: { "operation-url": operation_path(bar.operation) } do %>
|
77
67
|
<% end %>
|
78
68
|
</td>
|
79
69
|
</tr>
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
<% end %>
|
90
|
-
</div>
|
91
|
-
</div>
|
70
|
+
<% end %>
|
71
|
+
</tbody>
|
72
|
+
</table>
|
73
|
+
<% else %>
|
74
|
+
<%= render 'rails_pulse/components/empty_state',
|
75
|
+
title: 'No operations found for this request.',
|
76
|
+
description: 'This request may not have had any tracked operations.' %>
|
77
|
+
<% end %>
|
78
|
+
<% end %>
|
@@ -1,26 +1,39 @@
|
|
1
|
-
<%
|
2
|
-
|
3
|
-
|
1
|
+
<% columns = [
|
2
|
+
{ field: :occurred_at, label: 'Timestamp', class: 'w-36' },
|
3
|
+
{ field: :route_path, label: 'Route', class: 'w-auto' },
|
4
|
+
{ field: :duration, label: 'Response Time', class: 'w-36' },
|
5
|
+
{ field: :status, label: 'Status', class: 'w-20' }
|
6
|
+
] %>
|
4
7
|
|
5
|
-
|
6
|
-
{ field: :duration, label: 'Response Time', class: 'w-24' },
|
7
|
-
{ field: :status, label: 'HTTP Status', class: 'w-20' },
|
8
|
-
{ field: :status_indicator, label: 'Status', class: 'w-16' }
|
9
|
-
]
|
10
|
-
%>
|
11
|
-
|
12
|
-
<table class="table mbs-4">
|
8
|
+
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
13
9
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
14
10
|
|
15
11
|
<tbody>
|
16
|
-
<% @table_data.each do |
|
12
|
+
<% @table_data.each do |request| %>
|
13
|
+
<%
|
14
|
+
# Determine performance class based on request duration
|
15
|
+
performance_class = case request.duration
|
16
|
+
when 0..100 then "text-green-600"
|
17
|
+
when 100..300 then "text-yellow-600"
|
18
|
+
when 300..1000 then "text-orange-600"
|
19
|
+
else "text-red-600"
|
20
|
+
end
|
21
|
+
%>
|
17
22
|
<tr>
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
<td class="whitespace-nowrap"
|
22
|
-
|
23
|
-
|
23
|
+
<td class="whitespace-nowrap">
|
24
|
+
<%= link_to human_readable_occurred_at(request.occurred_at), request_path(request), data: { turbo_frame: '_top' } %>
|
25
|
+
</td>
|
26
|
+
<td class="whitespace-nowrap">
|
27
|
+
<%= link_to "#{request.route.path} #{request.route.method}", route_path(request.route), data: { turbo_frame: '_top' } %>
|
28
|
+
</td>
|
29
|
+
<td class="whitespace-nowrap">
|
30
|
+
<span class="<%= performance_class %> font-medium">
|
31
|
+
<%= request.duration.round(2) %> ms
|
32
|
+
</span>
|
33
|
+
</td>
|
34
|
+
<td class="whitespace-nowrap">
|
35
|
+
<span class="text-green-600"><%= request.status %></span>
|
36
|
+
</td>
|
24
37
|
</tr>
|
25
38
|
<% end %>
|
26
39
|
</tbody>
|
@@ -10,63 +10,68 @@
|
|
10
10
|
<% end %>
|
11
11
|
|
12
12
|
<div
|
13
|
-
class="row"
|
14
13
|
data-controller="rails-pulse--index"
|
15
14
|
data-rails-pulse--index-chart-id-value="average_response_times_chart"
|
16
15
|
>
|
17
|
-
|
18
|
-
<%=
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
16
|
+
<%= render 'rails_pulse/components/panel', { title: 'Average Response Time', } do %>
|
17
|
+
<%= search_form_for @ransack_query, url: requests_path, class: "flex items-center justify-between gap mb-4" do |form| %>
|
18
|
+
<div class="flex items-center grow gap">
|
19
|
+
<%= form.select :period_start_range,
|
20
|
+
RailsPulse::RequestsController::TIME_RANGE_OPTIONS,
|
21
|
+
{ selected: @selected_time_range },
|
22
|
+
{ class: "input" }
|
23
|
+
%>
|
24
|
+
<%= form.select :avg_duration,
|
25
|
+
duration_options(:request),
|
26
|
+
{ selected: @selected_response_range },
|
27
|
+
{ class: "input" }
|
28
|
+
%>
|
29
|
+
<%= link_to "Reset", requests_path, class: "btn btn--borderless show@md" if params.has_key?(:q) %>
|
30
|
+
<%= form.submit "Search", class: "btn show@sm" %>
|
31
|
+
</div>
|
32
|
+
<% end %>
|
33
|
+
|
34
|
+
<% if @has_data %>
|
35
|
+
<% if @chart_data && @chart_data.values.any? { |v| v > 0 } %>
|
36
|
+
<div
|
37
|
+
class="chart-container chart-container--slim"
|
38
|
+
data-rails-pulse--index-target="chart"
|
39
|
+
>
|
40
|
+
<%= bar_chart(
|
41
|
+
@chart_data,
|
42
|
+
code: false,
|
43
|
+
id: "average_response_times_chart",
|
44
|
+
height: "100%",
|
45
|
+
options: bar_chart_options(
|
46
|
+
units: "ms",
|
47
|
+
zoom: true,
|
48
|
+
chart_start: 0,
|
49
|
+
chart_end: @chart_data.length - 1,
|
50
|
+
xaxis_formatter: @xaxis_formatter,
|
51
|
+
tooltip_formatter: @tooltip_formatter,
|
52
|
+
zoom_start: @zoom_start,
|
53
|
+
zoom_end: @zoom_end,
|
54
|
+
chart_data: @chart_data
|
55
|
+
)
|
56
|
+
) %>
|
57
|
+
</div>
|
58
|
+
<% else %>
|
59
|
+
<div class="p-4 text-center text-muted">
|
60
|
+
No response time data available for the selected filters.
|
33
61
|
</div>
|
34
62
|
<% end %>
|
35
63
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
class="chart-container chart-container--slim"
|
40
|
-
data-rails-pulse--index-target="chart"
|
41
|
-
>
|
42
|
-
<%= bar_chart(
|
43
|
-
@chart_data,
|
44
|
-
code: false,
|
45
|
-
id: "average_response_times_chart",
|
46
|
-
height: "100%",
|
47
|
-
options: bar_chart_options(
|
48
|
-
units: "ms",
|
49
|
-
zoom: true,
|
50
|
-
chart_start: 0,
|
51
|
-
chart_end: @chart_data.length - 1,
|
52
|
-
xaxis_formatter: @xaxis_formatter,
|
53
|
-
tooltip_formatter: @tooltip_formatter,
|
54
|
-
zoom_start: @zoom_start,
|
55
|
-
zoom_end: @zoom_end,
|
56
|
-
chart_data: @chart_data
|
57
|
-
)
|
58
|
-
) %>
|
59
|
-
</div>
|
60
|
-
<% end %>
|
64
|
+
<div class="alert flex items-start mbs-8" role="alert">
|
65
|
+
<p>The chart above shows aggregated average response times grouped by time periods while the table below shows specific request details.</p>
|
66
|
+
</div>
|
61
67
|
|
62
|
-
|
63
|
-
|
64
|
-
<% end %>
|
65
|
-
<% else %>
|
66
|
-
<%= render 'rails_pulse/components/empty_state',
|
67
|
-
title: 'No request data found for the selected filters.',
|
68
|
-
description: 'Try adjusting your time range or filters to see results.' %>
|
68
|
+
<%= turbo_frame_tag :index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
69
|
+
<%= render 'rails_pulse/requests/table' %>
|
69
70
|
<% end %>
|
71
|
+
<% else %>
|
72
|
+
<%= render 'rails_pulse/components/empty_state',
|
73
|
+
title: 'No request data found for the selected filters.',
|
74
|
+
description: 'Try adjusting your time range or filters to see results.' %>
|
70
75
|
<% end %>
|
71
|
-
|
76
|
+
<% end %>
|
72
77
|
</div>
|
@@ -8,8 +8,6 @@
|
|
8
8
|
<dd><%= link_to @request.route.path_and_method, route_path(@request.route) %></dd>
|
9
9
|
<dt>Timestamp</dt>
|
10
10
|
<dd><%= human_readable_occurred_at(@request.occurred_at) %></dd>
|
11
|
-
<dt>Request UUID</dt>
|
12
|
-
<dd><code><%= @request.request_uuid %></code></dd>
|
13
11
|
<dt>Duration</dt>
|
14
12
|
<dd><%= @request.duration.round(2) %> ms</dd>
|
15
13
|
<dt>Status</dt>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<% columns = [
|
2
|
+
{ field: :occurred_at, label: 'Timestamp', class: 'w-auto' },
|
3
|
+
{ field: :duration, label: 'Response Time', class: 'w-36' },
|
4
|
+
{ field: :status, label: 'Status', class: 'w-20' }
|
5
|
+
] %>
|
6
|
+
|
7
|
+
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
8
|
+
<%= render "rails_pulse/components/table_head", columns: columns %>
|
9
|
+
|
10
|
+
<tbody>
|
11
|
+
<% @table_data.each do |request| %>
|
12
|
+
<tr>
|
13
|
+
<td class="whitespace-nowrap">
|
14
|
+
<%= link_to human_readable_occurred_at(request.occurred_at), request_path(request), data: { turbo_frame: '_top' } %>
|
15
|
+
</td>
|
16
|
+
<td class="whitespace-nowrap">
|
17
|
+
<% performance_class = case request.duration
|
18
|
+
when 0..100 then "text-green-600"
|
19
|
+
when 100..300 then "text-yellow-600"
|
20
|
+
when 300..1000 then "text-orange-600"
|
21
|
+
else "text-red-600"
|
22
|
+
end %>
|
23
|
+
<span class="<%= performance_class %> font-medium">
|
24
|
+
<%= request.duration.round(2) %> ms
|
25
|
+
</span>
|
26
|
+
</td>
|
27
|
+
<td class="whitespace-nowrap">
|
28
|
+
<% if request.is_error? %>
|
29
|
+
<span class="text-red-600">Error (<%= request.status %>)</span>
|
30
|
+
<% else %>
|
31
|
+
<span class="text-green-600"><%= request.status %></span>
|
32
|
+
<% end %>
|
33
|
+
</td>
|
34
|
+
</tr>
|
35
|
+
<% end %>
|
36
|
+
</tbody>
|
37
|
+
</table>
|
38
|
+
|
39
|
+
<%= render "rails_pulse/components/table_pagination" %>
|
@@ -1,14 +1,11 @@
|
|
1
1
|
<% columns = [
|
2
2
|
{ field: :route_path, label: 'Route', class: 'w-auto' },
|
3
|
-
{ field: :avg_duration_sort, label: 'Average Response Time', class: 'w-
|
4
|
-
{ field: :max_duration_sort, label: 'Max Response Time', class: 'w-
|
5
|
-
{ field: :count_sort, label: 'Requests', class: 'w-24' }
|
6
|
-
{ field: :requests_per_minute, label: 'Requests Per Minute', class: 'w-28' },
|
7
|
-
{ field: :error_rate_percentage, label: 'Error Rate (%)', class: 'w-20' },
|
8
|
-
{ field: :status_indicator, label: 'Status', class: 'w-16', sortable: false }
|
3
|
+
{ field: :avg_duration_sort, label: 'Average Response Time', class: 'w-48' },
|
4
|
+
{ field: :max_duration_sort, label: 'Max Response Time', class: 'w-44' },
|
5
|
+
{ field: :count_sort, label: 'Requests', class: 'w-24' }
|
9
6
|
] %>
|
10
7
|
|
11
|
-
<table class="table mbs-4">
|
8
|
+
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
12
9
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
13
10
|
|
14
11
|
<tbody>
|
@@ -18,9 +15,6 @@
|
|
18
15
|
<td class="whitespace-nowrap"><%= summary.avg_duration.to_i %> ms</td>
|
19
16
|
<td class="whitespace-nowrap"><%= summary.max_duration.to_i %> ms</td>
|
20
17
|
<td class="whitespace-nowrap"><%= number_with_delimiter summary.count %></td>
|
21
|
-
<td class="whitespace-nowrap"><%= summary.count < 1 ? '< 1' : (summary.count / 60.0).round(2) %></td>
|
22
|
-
<td class="whitespace-nowrap"><%= ((summary.error_count.to_f / summary.count) * 100).round(2) %>%</td>
|
23
|
-
<td class="whitespace-nowrap text-center"><%= route_status_indicator(summary.avg_duration >= 500 ? 1 : 0) %></td>
|
24
18
|
</tr>
|
25
19
|
<% end %>
|
26
20
|
</tbody>
|