rails_pulse 0.1.2 → 0.1.3
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 +10 -4
- 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 +46 -1
- data/app/controllers/rails_pulse/requests_controller.rb +14 -1
- data/app/controllers/rails_pulse/routes_controller.rb +40 -1
- data/app/helpers/rails_pulse/chart_helper.rb +15 -7
- 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 +241 -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/queries/cards/average_query_times.rb +19 -19
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +13 -8
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +13 -8
- data/app/models/rails_pulse/query.rb +46 -0
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +17 -19
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +13 -8
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +13 -8
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +13 -8
- 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 +146 -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 +27 -4
- 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 +1 -1
- 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 +87 -0
- data/app/views/rails_pulse/queries/_analysis_section.html.erb +39 -0
- data/app/views/rails_pulse/queries/_show_table.html.erb +1 -1
- data/app/views/rails_pulse/queries/_table.html.erb +1 -1
- 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 +3 -1
- data/app/views/rails_pulse/requests/index.html.erb +48 -51
- data/app/views/rails_pulse/routes/_table.html.erb +1 -1
- data/app/views/rails_pulse/routes/index.html.erb +49 -52
- data/app/views/rails_pulse/routes/show.html.erb +4 -4
- data/config/routes.rb +5 -1
- data/db/migrate/20250916031656_add_analysis_to_rails_pulse_queries.rb +13 -0
- data/db/rails_pulse_schema.rb +9 -0
- data/lib/generators/rails_pulse/convert_to_migrations_generator.rb +65 -0
- data/lib/generators/rails_pulse/install_generator.rb +71 -18
- data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +22 -0
- data/lib/generators/rails_pulse/templates/migrations/upgrade_rails_pulse_tables.rb +19 -0
- data/lib/generators/rails_pulse/upgrade_generator.rb +225 -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 +23 -5
- 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
@@ -0,0 +1,87 @@
|
|
1
|
+
<div class="row">
|
2
|
+
<div>
|
3
|
+
<!-- Query Characteristics -->
|
4
|
+
<% if query.query_stats.present? %>
|
5
|
+
<h5>Query Characteristics</h5>
|
6
|
+
<dl class="descriptive-list mbs-4">
|
7
|
+
<dt>Last Analyzed</dt>
|
8
|
+
<dd><%= time_ago_in_words(query.analyzed_at) %></dd>
|
9
|
+
<dt>Query Type</dt>
|
10
|
+
<dd><%= query.query_stats['query_type'] %></dd>
|
11
|
+
<dt>Tables</dt>
|
12
|
+
<dd><%= query.query_stats['table_count'] || 0 %></dd>
|
13
|
+
<dt>Joins</dt>
|
14
|
+
<dd><%= query.query_stats['join_count'] || 0 %></dd>
|
15
|
+
<dt>Complexity Score</dt>
|
16
|
+
<dd><%= query.query_stats['estimated_complexity'] || 0 %></dd>
|
17
|
+
<dt>Has LIMIT</dt>
|
18
|
+
<dd><%= query.query_stats['has_limit'] ? 'Yes' : 'No' %></dd>
|
19
|
+
<dt>Has ORDER BY</dt>
|
20
|
+
<dd><%= query.query_stats['has_order_by'] ? 'Yes' : 'No' %></dd>
|
21
|
+
<dt>Has GROUP BY</dt>
|
22
|
+
<dd><%= query.query_stats['has_group_by'] ? 'Yes' : 'No' %></dd>
|
23
|
+
<dt>Has Subqueries</dt>
|
24
|
+
<dd><%= query.query_stats['has_subqueries'] ? 'Yes' : 'No' %></dd>
|
25
|
+
</dl>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div>
|
30
|
+
<!-- Execution Analysis -->
|
31
|
+
<% if query.backtrace_analysis.present? %>
|
32
|
+
<h5>Execution Analysis</h5>
|
33
|
+
<dl class="descriptive-list mbs-4">
|
34
|
+
<dt>Total Executions</dt>
|
35
|
+
<dd><%= query.backtrace_analysis['total_executions'] || 0 %></dd>
|
36
|
+
<dt>Unique Locations</dt>
|
37
|
+
<dd><%= query.backtrace_analysis['unique_locations'] || 0 %></dd>
|
38
|
+
<dt>Execution Frequency</dt>
|
39
|
+
<dd><%= query.backtrace_analysis['execution_frequency'] || 0 %>/hour</dd>
|
40
|
+
<dt>Most Common Location</dt>
|
41
|
+
<dd><code><%= query.backtrace_analysis['most_common_location']['location'] || 'N/A' %></code></dd>
|
42
|
+
<dt>N+1 Pattern Detected</dt>
|
43
|
+
<dd>
|
44
|
+
<% if query.backtrace_analysis['potential_n_plus_one'] %>
|
45
|
+
Yes
|
46
|
+
<% else %>
|
47
|
+
No
|
48
|
+
<% end %>
|
49
|
+
</dd>
|
50
|
+
</dl>
|
51
|
+
<% end %>
|
52
|
+
</div>
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<!-- Issues Summary -->
|
56
|
+
<% if query.issues.present? && query.issues.any? %>
|
57
|
+
<div class="panel panel--danger mb-4">
|
58
|
+
<h3 class="text-lg bold mb-3">Issues Detected</h3>
|
59
|
+
<ul>
|
60
|
+
<% query.issues.each do |issue| %>
|
61
|
+
<li><%= issue['description'] %></li>
|
62
|
+
<% end %>
|
63
|
+
</ul>
|
64
|
+
</div>
|
65
|
+
<% end %>
|
66
|
+
|
67
|
+
<!-- Suggestions -->
|
68
|
+
<% if query.suggestions.present? && query.suggestions.any? %>
|
69
|
+
<div class="panel panel--info mb-4">
|
70
|
+
<h3 class="text-lg bold mb-3">Optimization Suggestions</h3>
|
71
|
+
<ul>
|
72
|
+
<% query.suggestions.each do |suggestion| %>
|
73
|
+
<li>
|
74
|
+
<%= suggestion['action'] %>.
|
75
|
+
<%= suggestion['benefit'] if suggestion['benefit'].present? %>
|
76
|
+
</li>
|
77
|
+
<% end %>
|
78
|
+
</ul>
|
79
|
+
</div>
|
80
|
+
<% end %>
|
81
|
+
|
82
|
+
<!-- EXPLAIN Plan -->
|
83
|
+
<% if query.explain_plan.present? %>
|
84
|
+
<%= render 'rails_pulse/components/panel', title: 'Execution Plan' do %>
|
85
|
+
<pre class="text-sm"><%= query.explain_plan %></pre>
|
86
|
+
<% end %>
|
87
|
+
<% end %>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<%
|
2
|
+
analyze_actions = []
|
3
|
+
analyze_actions << {
|
4
|
+
url: analyze_query_path(query),
|
5
|
+
title: query.analyzed? ? "Re-analyze query" : "Analyze query performance",
|
6
|
+
icon: "refresh-cw",
|
7
|
+
data: {
|
8
|
+
turbo_method: "post",
|
9
|
+
turbo_frame: "query_analysis"
|
10
|
+
}
|
11
|
+
}
|
12
|
+
%>
|
13
|
+
|
14
|
+
<%= render 'rails_pulse/components/panel', {
|
15
|
+
title: 'Query Analysis',
|
16
|
+
actions: analyze_actions
|
17
|
+
} do %>
|
18
|
+
<% if local_assigns[:error_message] %>
|
19
|
+
<%= render 'rails_pulse/queries/analysis_error', error_message: error_message, query: query %>
|
20
|
+
<% elsif query.analyzed? %>
|
21
|
+
<%= render 'rails_pulse/queries/analysis_results', query: query %>
|
22
|
+
<% else %>
|
23
|
+
<div class="flex items-center justify-center pbs-12 pbe-12 pis-6 pie-6 mb-8">
|
24
|
+
<div class="flex items-center gap">
|
25
|
+
<div class="shrink-0">
|
26
|
+
<img src="<%= asset_path('search.svg') %>" class="w-48 h-48" alt="No data available" />
|
27
|
+
</div>
|
28
|
+
<div class="text-subtle pis-8">
|
29
|
+
<p class="text-lg font-semibold mbe-2">Analyze Query Performance</p>
|
30
|
+
<% if query.has_recent_operations? %>
|
31
|
+
<p class="text-sm mbe-2"> Get detailed insights about this query's performance, potential issues, and optimization suggestions. </p>
|
32
|
+
<% else %>
|
33
|
+
<p class="text-sm mbe-2">No recent data available for analysis</p>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
39
|
+
<% end %>
|
@@ -6,7 +6,7 @@
|
|
6
6
|
{ field: :performance_status, label: 'Status', class: 'w-16', sortable: false }
|
7
7
|
] %>
|
8
8
|
|
9
|
-
<table class="table mbs-4">
|
9
|
+
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
10
10
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
11
11
|
|
12
12
|
<tbody>
|
@@ -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 %>
|
@@ -3,13 +3,14 @@
|
|
3
3
|
columns << { field: :route_path, label: 'Route', class: 'w-auto' } if @route.blank?
|
4
4
|
|
5
5
|
columns += [
|
6
|
+
{ field: :occurred_at, label: 'Timestamp', class: 'w-36' },
|
6
7
|
{ field: :duration, label: 'Response Time', class: 'w-24' },
|
7
8
|
{ field: :status, label: 'HTTP Status', class: 'w-20' },
|
8
9
|
{ field: :status_indicator, label: 'Status', class: 'w-16' }
|
9
10
|
]
|
10
11
|
%>
|
11
12
|
|
12
|
-
<table class="table mbs-4">
|
13
|
+
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
13
14
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
14
15
|
|
15
16
|
<tbody>
|
@@ -18,6 +19,7 @@
|
|
18
19
|
<% if @route.blank? %>
|
19
20
|
<td class="whitespace-nowrap"><%= link_to route_request.route.path_and_method, request_path(route_request), data: { turbo_frame: '_top' } %></td>
|
20
21
|
<% end %>
|
22
|
+
<td class="whitespace-nowrap"><%= link_to human_readable_occurred_at(route_request.occurred_at), request_path(route_request), data: { turbo_frame: '_top' } %></td>
|
21
23
|
<td class="whitespace-nowrap"><%= route_request.duration.round(2) %> ms</td>
|
22
24
|
<td class="whitespace-nowrap"><%= route_request.status %></td>
|
23
25
|
<td class="whitespace-nowrap text-center"><%= request_status_indicator(route_request.duration) %></td>
|
@@ -10,63 +10,60 @@
|
|
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
|
+
) %>
|
33
57
|
</div>
|
34
58
|
<% end %>
|
35
59
|
|
36
|
-
|
37
|
-
|
38
|
-
<div
|
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 %>
|
61
|
-
|
62
|
-
<%= turbo_frame_tag :requests_index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
63
|
-
<%= render 'rails_pulse/requests/table' %>
|
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.' %>
|
60
|
+
<%= turbo_frame_tag :index_table, data: { rails_pulse__index_target: "indexTable" } do %>
|
61
|
+
<%= render 'rails_pulse/requests/table' %>
|
69
62
|
<% end %>
|
63
|
+
<% else %>
|
64
|
+
<%= render 'rails_pulse/components/empty_state',
|
65
|
+
title: 'No request data found for the selected filters.',
|
66
|
+
description: 'Try adjusting your time range or filters to see results.' %>
|
70
67
|
<% end %>
|
71
|
-
|
68
|
+
<% end %>
|
72
69
|
</div>
|
@@ -8,7 +8,7 @@
|
|
8
8
|
{ field: :status_indicator, label: 'Status', class: 'w-16', sortable: false }
|
9
9
|
] %>
|
10
10
|
|
11
|
-
<table class="table mbs-4">
|
11
|
+
<table class="table mbs-4" data-controller="rails-pulse--table-sort">
|
12
12
|
<%= render "rails_pulse/components/table_head", columns: columns %>
|
13
13
|
|
14
14
|
<tbody>
|