rails_pulse 0.3.3 → 0.4.0.pre.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/CHANGELOG.md +299 -0
- data/README.md +44 -3
- data/app/assets/stylesheets/rails_pulse/application.css +21 -32
- data/app/assets/stylesheets/rails_pulse/components/backtrace.css +238 -0
- data/app/assets/stylesheets/rails_pulse/components/badge.css +32 -0
- data/app/assets/stylesheets/rails_pulse/components/button.css +2 -36
- data/app/assets/stylesheets/rails_pulse/components/card.css +8 -11
- data/app/assets/stylesheets/rails_pulse/components/chart.css +16 -3
- data/app/assets/stylesheets/rails_pulse/components/csp_safe_positioning.css +5 -39
- data/app/assets/stylesheets/rails_pulse/components/dashboard.css +0 -52
- data/app/assets/stylesheets/rails_pulse/components/deployments.css +39 -0
- data/app/assets/stylesheets/rails_pulse/components/dialog.css +0 -5
- data/app/assets/stylesheets/rails_pulse/components/input.css +0 -14
- data/app/assets/stylesheets/rails_pulse/components/layouts.css +0 -20
- data/app/assets/stylesheets/rails_pulse/components/menu.css +0 -16
- data/app/assets/stylesheets/rails_pulse/components/sidebar_menu.css +0 -16
- data/app/assets/stylesheets/rails_pulse/components/storage.css +187 -0
- data/app/assets/stylesheets/rails_pulse/components/tags.css +0 -14
- data/app/assets/stylesheets/rails_pulse/components/time_range_selector.css +5 -5
- data/app/assets/stylesheets/rails_pulse/components/utilities.css +0 -17
- data/app/controllers/concerns/chart_table_concern.rb +15 -3
- data/app/controllers/concerns/ransack_params_concern.rb +18 -0
- data/app/controllers/concerns/response_range_concern.rb +1 -1
- data/app/controllers/concerns/time_range_concern.rb +28 -14
- data/app/controllers/rails_pulse/application_controller.rb +104 -31
- data/app/controllers/rails_pulse/dashboard_controller.rb +9 -0
- data/app/controllers/rails_pulse/deployments_controller.rb +26 -5
- data/app/controllers/rails_pulse/exception_occurrences_controller.rb +19 -0
- data/app/controllers/rails_pulse/exceptions_controller.rb +119 -0
- data/app/controllers/rails_pulse/job_runs_controller.rb +1 -1
- data/app/controllers/rails_pulse/requests_controller.rb +1 -1
- data/app/controllers/rails_pulse/routes_controller.rb +7 -1
- data/app/controllers/rails_pulse/storage_controller.rb +7 -0
- data/app/controllers/rails_pulse/tags_controller.rb +2 -2
- data/app/helpers/rails_pulse/application_helper.rb +1 -0
- data/app/helpers/rails_pulse/backtrace_helper.rb +85 -0
- data/app/helpers/rails_pulse/breadcrumbs_helper.rb +4 -1
- data/app/helpers/rails_pulse/chart_helper.rb +0 -3
- data/app/helpers/rails_pulse/route_helper.rb +8 -16
- data/app/javascript/rails_pulse/application.js +29 -8
- data/app/javascript/rails_pulse/controllers/clipboard_controller.js +58 -0
- data/app/javascript/rails_pulse/controllers/index_controller.js +16 -10
- data/app/javascript/rails_pulse/controllers/pagination_controller.js +6 -2
- data/app/javascript/rails_pulse/controllers/popover_controller.js +4 -0
- data/app/models/concerns/rails_pulse/taggable.rb +11 -6
- data/app/models/rails_pulse/cards/base.rb +53 -0
- data/app/models/rails_pulse/dashboard/health_summary.rb +52 -0
- data/app/models/rails_pulse/dashboard/needs_attention.rb +73 -1
- data/app/models/rails_pulse/dashboard/storage_status.rb +490 -0
- data/app/models/rails_pulse/deployment.rb +47 -1
- data/app/models/rails_pulse/exception_group.rb +37 -0
- data/app/models/rails_pulse/exception_occurrence.rb +27 -0
- data/app/models/rails_pulse/exceptions/cards/exception_rate.rb +116 -0
- data/app/models/rails_pulse/exceptions/cards/open_groups.rb +49 -0
- data/app/models/rails_pulse/exceptions/cards/total_occurrences.rb +62 -0
- data/app/models/rails_pulse/exceptions/charts/occurrence_volume.rb +56 -0
- data/app/models/rails_pulse/exceptions/raw_data.rb +216 -0
- data/app/models/rails_pulse/jobs/cards/p95_duration.rb +8 -2
- data/app/models/rails_pulse/operation.rb +13 -4
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +8 -3
- data/app/models/rails_pulse/request.rb +5 -1
- data/app/models/rails_pulse/route.rb +71 -21
- data/app/models/rails_pulse/routes/cards/error_rates.rb +6 -3
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +8 -3
- data/app/models/rails_pulse/routes/tables/index.rb +8 -6
- data/app/models/rails_pulse/summary.rb +21 -10
- data/app/models/rails_pulse/tables/base.rb +4 -2
- data/app/services/rails_pulse/analysis/explain_plan_analyzer.rb +47 -12
- data/app/services/rails_pulse/exception_capture_service.rb +228 -0
- data/app/services/rails_pulse/exception_message_sanitizer.rb +152 -0
- data/app/services/rails_pulse/operations/change_point.rb +144 -0
- data/app/services/rails_pulse/operations/compare.rb +92 -0
- data/app/services/rails_pulse/operations/comparison.rb +126 -0
- data/app/services/rails_pulse/operations/metric.rb +63 -0
- data/app/services/rails_pulse/operations/series.rb +84 -0
- data/app/services/rails_pulse/operations/subject.rb +77 -0
- data/app/services/rails_pulse/route_controller_action_backfiller.rb +110 -0
- data/app/services/rails_pulse/route_merger.rb +99 -0
- data/app/services/rails_pulse/route_migrator.rb +139 -0
- data/app/services/rails_pulse/route_path_normalizer.rb +79 -0
- data/app/services/rails_pulse/route_recognizer.rb +47 -0
- data/app/services/rails_pulse/summary_service.rb +45 -0
- data/app/services/rails_pulse/tag_filter_service.rb +5 -3
- data/app/views/layouts/rails_pulse/_menu_items.html.erb +7 -0
- data/app/views/layouts/rails_pulse/_onboarding_banner.html.erb +8 -0
- data/app/views/layouts/rails_pulse/_setup_screen.html.erb +5 -5
- data/app/views/layouts/rails_pulse/application.html.erb +4 -4
- data/app/views/rails_pulse/components/_empty_state.html.erb +1 -1
- data/app/views/rails_pulse/components/_panel.html.erb +16 -6
- data/app/views/rails_pulse/components/_raw_data.html.erb +34 -0
- data/app/views/rails_pulse/dashboard/_chart_panel.html.erb +6 -8
- data/app/views/rails_pulse/dashboard/_deployments_panel.html.erb +64 -0
- data/app/views/rails_pulse/dashboard/_health_badge.html.erb +21 -10
- data/app/views/rails_pulse/dashboard/_storage_panel.html.erb +67 -0
- data/app/views/rails_pulse/dashboard/index.html.erb +22 -1
- data/app/views/rails_pulse/deployments/_status_badge.html.erb +5 -0
- data/app/views/rails_pulse/deployments/_table.html.erb +29 -0
- data/app/views/rails_pulse/deployments/index.html.erb +15 -0
- data/app/views/rails_pulse/deployments/show.html.erb +62 -0
- data/app/views/rails_pulse/exception_occurrences/show.html.erb +45 -0
- data/app/views/rails_pulse/exceptions/_backtrace.html.erb +33 -0
- data/app/views/rails_pulse/exceptions/_metric_cards.html.erb +6 -0
- data/app/views/rails_pulse/exceptions/_occurrences_table.html.erb +23 -0
- data/app/views/rails_pulse/exceptions/_status_badge.html.erb +6 -0
- data/app/views/rails_pulse/exceptions/_table.html.erb +33 -0
- data/app/views/rails_pulse/exceptions/index.html.erb +66 -0
- data/app/views/rails_pulse/exceptions/show.html.erb +52 -0
- data/app/views/rails_pulse/queries/_raw_data.html.erb +1 -23
- data/app/views/rails_pulse/requests/_table.html.erb +1 -1
- data/app/views/rails_pulse/routes/_requests_table.html.erb +5 -0
- data/app/views/rails_pulse/routes/_table.html.erb +22 -8
- data/app/views/rails_pulse/routes/index.html.erb +5 -1
- data/app/views/rails_pulse/routes/show.html.erb +5 -0
- data/app/views/rails_pulse/storage/show.html.erb +123 -0
- data/config/routes.rb +13 -4
- data/db/rails_pulse_migrate/20260506000001_create_rails_pulse_exceptions.rb +50 -0
- data/db/rails_pulse_migrate/20260507000001_add_actual_query_to_operations.rb +10 -5
- data/db/rails_pulse_migrate/20260610000001_add_controller_action_to_rails_pulse_routes.rb +8 -0
- data/db/rails_pulse_migrate/20260610000002_change_rails_pulse_routes_to_multi_verb_model.rb +185 -0
- data/db/rails_pulse_migrate/20260610000003_add_null_action_unique_index_to_routes.rb +32 -0
- data/db/rails_pulse_migrate/20260823000001_add_location_to_exception_groups.rb +12 -0
- data/db/rails_pulse_schema.rb +54 -6
- data/lib/generators/rails_pulse/base_methods.rb +2 -0
- data/lib/generators/rails_pulse/install_generator.rb +5 -0
- data/lib/generators/rails_pulse/schema_parser.rb +22 -2
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +54 -6
- data/lib/generators/rails_pulse/templates/migrations/upgrade_rails_pulse_tables.rb +1 -1
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +157 -16
- data/lib/generators/rails_pulse/upgrade_generator.rb +166 -38
- data/lib/rails_pulse/cleanup_service.rb +110 -15
- data/lib/rails_pulse/configuration.rb +125 -6
- data/lib/rails_pulse/engine.rb +32 -17
- data/lib/rails_pulse/installers/config_updater.rb +202 -0
- data/lib/rails_pulse/job_run_collector.rb +43 -16
- data/lib/rails_pulse/middleware/asset_server.rb +11 -0
- data/lib/rails_pulse/middleware/request_collector.rb +9 -4
- data/lib/rails_pulse/packaged_assets.rb +147 -0
- data/lib/rails_pulse/route_indexes.rb +66 -0
- data/lib/rails_pulse/subscribers/exception_subscriber.rb +36 -0
- data/lib/rails_pulse/tracker.rb +29 -10
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/rails_pulse.rb +1 -0
- data/lib/rails_pulse_server.ru +16 -47
- data/lib/tasks/rails_pulse.rake +83 -4
- data/lib/tasks/rails_pulse_assets.rake +15 -0
- data/lib/tasks/rails_pulse_tasks.rake +29 -0
- data/public/rails-pulse-assets/rails-pulse-icons.js +6 -2
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +18 -117
- metadata +78 -37
- data/Rakefile +0 -554
- data/app/javascript/rails_pulse/controllers/form_controller.js +0 -39
- data/app/javascript/rails_pulse/controllers/period_selector_controller.js +0 -19
- data/app/javascript/rails_pulse/controllers/timezone_controller.js +0 -89
- data/app/javascript/rails_pulse/theme.js +0 -416
- data/config/brakeman.ignore +0 -190
- data/config/brakeman.yml +0 -68
- data/config/initializers/rails_pulse.rb +0 -287
- data/public/rails-pulse-assets/csp-test.js +0 -120
- data/public/rails-pulse-assets/rails-pulse-icons.js.map +0 -13
- data/public/rails-pulse-assets/rails-pulse.css.map +0 -1
- data/public/rails-pulse-assets/rails-pulse.js.map +0 -7
- data/vendor/assets/javascripts/rails-pulse-icons.js +0 -93
- data/vendor/assets/javascripts/rails-pulse-icons.js.map +0 -13
- data/vendor/assets/javascripts/rails-pulse.js +0 -157
- data/vendor/assets/javascripts/rails-pulse.js.map +0 -7
- data/vendor/assets/stylesheets/rails-pulse.css +0 -1
- data/vendor/assets/stylesheets/rails-pulse.css.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="flex items-center justify-center pbs-12 pbe-12 pis-6 pie-6 mb-8">
|
|
2
2
|
<div class="flex items-center gap">
|
|
3
3
|
<div class="shrink-0">
|
|
4
|
-
<img src="<%= asset_path('search.svg') %>" class="w-48 h-48" alt="No data available" />
|
|
4
|
+
<img src="<%= rails_pulse.asset_path('search.svg') %>" class="w-48 h-48" alt="No data available" />
|
|
5
5
|
</div>
|
|
6
6
|
<div class="text-subtle pis-8">
|
|
7
7
|
<p class="text-lg font-semibold mbe-2"><%= title %></p>
|
|
@@ -63,12 +63,22 @@
|
|
|
63
63
|
<%= rails_pulse_icon action[:icon] || 'external-link', height: "20px" %>
|
|
64
64
|
</a>
|
|
65
65
|
<% elsif action[:method] %>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
<% if action[:label] %>
|
|
67
|
+
<%= button_to action[:label], action[:url],
|
|
68
|
+
method: action[:method],
|
|
69
|
+
title: action[:title] || "",
|
|
70
|
+
class: action[:class] || "",
|
|
71
|
+
params: action[:params] || {},
|
|
72
|
+
disabled: action[:disabled],
|
|
73
|
+
data: action[:data] || {} %>
|
|
74
|
+
<% else %>
|
|
75
|
+
<%= button_to action[:url],
|
|
76
|
+
method: action[:method],
|
|
77
|
+
title: action[:title] || "",
|
|
78
|
+
class: action[:class] || "",
|
|
79
|
+
data: action[:data] || {} do %>
|
|
80
|
+
<%= rails_pulse_icon action[:icon] || 'external-link', height: "20px" %>
|
|
81
|
+
<% end %>
|
|
72
82
|
<% end %>
|
|
73
83
|
<% else %>
|
|
74
84
|
<%= link_to action[:url],
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<%#
|
|
2
|
+
Collapsible block of copy-to-clipboard text, intended for pasting into an LLM.
|
|
3
|
+
|
|
4
|
+
Locals:
|
|
5
|
+
content — the text to render and copy (required)
|
|
6
|
+
title — heading above the block (default "Raw Data")
|
|
7
|
+
%>
|
|
8
|
+
<%
|
|
9
|
+
title ||= "Raw Data"
|
|
10
|
+
%>
|
|
11
|
+
|
|
12
|
+
<div
|
|
13
|
+
data-controller="rails-pulse--collapsible rails-pulse--clipboard"
|
|
14
|
+
data-rails-pulse--collapsible-collapsed-class="collapsed"
|
|
15
|
+
class="collapsible-code"
|
|
16
|
+
>
|
|
17
|
+
<h2 class="grow font-semibold leading-none mbe-1 uppercase text-xs">
|
|
18
|
+
<%= title %>
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
class="collapsible-toggle"
|
|
22
|
+
data-rails-pulse--collapsible-target="toggle"
|
|
23
|
+
data-action="click->rails-pulse--collapsible#toggle"
|
|
24
|
+
>show</button>
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
class="collapsible-toggle"
|
|
28
|
+
data-action="click->rails-pulse--clipboard#copy"
|
|
29
|
+
>copy</button>
|
|
30
|
+
</h2>
|
|
31
|
+
<div class="prose max-i-none" data-rails-pulse--collapsible-target="content">
|
|
32
|
+
<pre class="mbs-0" style="white-space: pre; margin-block-end: 0; margin-block-start: 0;"><code data-rails-pulse--clipboard-target="source"><%= html_escape(content) %></code></pre>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<div class="grid-item" style="<%= flex_style if defined?(flex_style) %>">
|
|
2
2
|
<div class="card b-full flex flex-col">
|
|
3
|
-
<div class="flex
|
|
4
|
-
<h2 class="font-semibold leading-none uppercase text-xs"><%= title %></h2>
|
|
5
|
-
<div class="flex items-center
|
|
3
|
+
<div class="flex mbe-2">
|
|
4
|
+
<h2 class="grow font-semibold leading-none uppercase text-xs"><%= title %></h2>
|
|
5
|
+
<div class="panel-icon-bar flex gap items-center">
|
|
6
|
+
<% if chart_data %>
|
|
7
|
+
<%= series_toggles %>
|
|
8
|
+
<% end %>
|
|
6
9
|
<div data-controller="rails-pulse--popover" data-rails-pulse--popover-placement-value="bottom-end">
|
|
7
10
|
<a href="#"
|
|
8
11
|
data-rails-pulse--popover-target="button"
|
|
@@ -23,11 +26,6 @@
|
|
|
23
26
|
<% end %>
|
|
24
27
|
</div>
|
|
25
28
|
</div>
|
|
26
|
-
<% if chart_data %>
|
|
27
|
-
<div class="panel-icon-bar flex items-center gap-half mbe-2">
|
|
28
|
-
<%= series_toggles %>
|
|
29
|
-
</div>
|
|
30
|
-
<% end %>
|
|
31
29
|
<div class="chart-container chart-container--slim relative pbs-2 grow">
|
|
32
30
|
<% if chart_data %>
|
|
33
31
|
<%= render_stimulus_chart(
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<div class="card b-full flex flex-col">
|
|
2
|
+
<div class="flex mbe-3">
|
|
3
|
+
<h2 class="grow font-semibold leading-none uppercase text-xs">Deployments</h2>
|
|
4
|
+
<div class="panel-icon-bar flex gap items-center">
|
|
5
|
+
<div data-controller="rails-pulse--popover" data-rails-pulse--popover-placement-value="bottom-end">
|
|
6
|
+
<a href="#"
|
|
7
|
+
data-rails-pulse--popover-target="button"
|
|
8
|
+
data-action="rails-pulse--popover#toggle"
|
|
9
|
+
data-popovertarget="popover"
|
|
10
|
+
class="block leading-none">
|
|
11
|
+
<%= rails_pulse_icon "message-circle-question", height: "16px", width: "16px" %>
|
|
12
|
+
</a>
|
|
13
|
+
<div popover class="popover card max-w-lg" data-rails-pulse--popover-target="menu">
|
|
14
|
+
<div class="flex flex-col">
|
|
15
|
+
<h3 class="font-semibold leading-none mbe-2 uppercase text-sm">Deployments</h3>
|
|
16
|
+
<p class="text-sm text-subtle">Releases your CI/CD pipeline recorded in the selected time range — the same ones drawn as markers on the charts above. Open the full page for every deploy and its metadata.</p>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<%= link_to deployments_path, class: "block leading-none", title: "View all deployments" do %>
|
|
21
|
+
<%= rails_pulse_icon "external-link", height: "16px", width: "16px" %>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="deployments-panel-stats">
|
|
27
|
+
<div>
|
|
28
|
+
<p class="font-semibold uppercase text-xs text-subtle mbe-1">Last deploy</p>
|
|
29
|
+
<p class="text-lg font-bold"><%= @last_deployment ? time_ago_in_words(@last_deployment.started_at) : "—" %></p>
|
|
30
|
+
<p class="text-xs text-subtle">
|
|
31
|
+
<%= @last_deployment ? @last_deployment.short_revision : "Nothing recorded yet" %>
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
<div>
|
|
35
|
+
<p class="font-semibold uppercase text-xs text-subtle mbe-1">In this range</p>
|
|
36
|
+
<p class="text-lg font-bold"><%= number_with_delimiter(@deployment_count) %></p>
|
|
37
|
+
<p class="text-xs text-subtle"><%= humanize_time_range(@selected_time_range) %></p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="deployments-panel-rows grow">
|
|
42
|
+
<% if @recent_deployments.any? %>
|
|
43
|
+
<% @recent_deployments.each do |deployment| %>
|
|
44
|
+
<div class="deployments-panel-row">
|
|
45
|
+
<span class="deployments-panel-row__revision truncate">
|
|
46
|
+
<%= link_to deployment_path(deployment) do %>
|
|
47
|
+
<code><%= deployment.short_revision %></code>
|
|
48
|
+
<% end %>
|
|
49
|
+
</span>
|
|
50
|
+
<%= render "rails_pulse/deployments/status_badge", deployment: deployment %>
|
|
51
|
+
<span class="deployments-panel-row__time text-xs text-subtle">
|
|
52
|
+
<%= human_readable_occurred_at(deployment.started_at) %>
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
<% end %>
|
|
56
|
+
<% else %>
|
|
57
|
+
<p class="text-sm text-subtle">
|
|
58
|
+
No deployments recorded in the <%= humanize_time_range(@selected_time_range) %>.
|
|
59
|
+
Post to the deployments endpoint from your deploy pipeline, or run
|
|
60
|
+
<code>rails_pulse:record_deployment</code>, and they will appear here and as markers on the charts.
|
|
61
|
+
</p>
|
|
62
|
+
<% end %>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
<%# Exceptions do not have a "slow" state — a group that fired at all is
|
|
2
|
+
firing — so the two non-critical labels are overridable. %>
|
|
3
|
+
<% healthy_label = local_assigns.fetch(:healthy_label, "healthy") %>
|
|
4
|
+
<% slow_label = local_assigns.fetch(:slow_label, "slow") %>
|
|
1
5
|
<% dot_color = health[:critical] > 0 ? "var(--color-negative)" : (health[:slow] > 0 ? "#eab308" : "var(--color-positive)") %>
|
|
2
6
|
<% border_color = health[:critical] > 0 ? "color-mix(in srgb, var(--color-negative) 40%, transparent)" : (health[:slow] > 0 ? "color-mix(in srgb, #eab308 40%, transparent)" : "color-mix(in srgb, var(--color-positive) 40%, transparent)") %>
|
|
3
|
-
|
|
4
|
-
<span class="
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
<% badge = capture do %>
|
|
8
|
+
<span class="badge flex items-center gap-half p-2" style="border-color: <%= border_color %>;">
|
|
9
|
+
<span class="shrink-0 rounded-full" style="width: 10px; height: 10px; background: <%= dot_color %>;"></span>
|
|
10
|
+
<span class="text-xs font-semibold"><%= name %></span>
|
|
11
|
+
<% if health[:critical] > 0 %>
|
|
12
|
+
<span class="text-xs text-subtle"><%= health[:healthy] %> <%= healthy_label %> · <%= health[:slow] %> <%= slow_label %> ·</span>
|
|
13
|
+
<span class="text-xs font-semibold text-negative"><%= health[:critical] %> <%= critical_label %></span>
|
|
14
|
+
<% else %>
|
|
15
|
+
<span class="text-xs text-subtle"><%= health[:healthy] %> <%= healthy_label %> · <%= health[:slow] %> <%= slow_label %> · 0 <%= critical_label %></span>
|
|
16
|
+
<% end %>
|
|
17
|
+
</span>
|
|
18
|
+
<% end %>
|
|
19
|
+
<% if local_assigns[:link_url].present? %>
|
|
20
|
+
<%= link_to link_url, class: "health-badge-link" do %><%= badge %><% end %>
|
|
21
|
+
<% else %>
|
|
22
|
+
<%= badge %>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<div class="card">
|
|
2
|
+
<div class="flex mbe-3">
|
|
3
|
+
<h2 class="grow font-semibold leading-none uppercase text-xs">Storage</h2>
|
|
4
|
+
<div class="panel-icon-bar flex gap items-center">
|
|
5
|
+
<div data-controller="rails-pulse--popover" data-rails-pulse--popover-placement-value="bottom-end">
|
|
6
|
+
<a href="#"
|
|
7
|
+
data-rails-pulse--popover-target="button"
|
|
8
|
+
data-action="rails-pulse--popover#toggle"
|
|
9
|
+
data-popovertarget="popover"
|
|
10
|
+
class="block leading-none">
|
|
11
|
+
<%= rails_pulse_icon "message-circle-question", height: "16px", width: "16px" %>
|
|
12
|
+
</a>
|
|
13
|
+
<div popover class="popover card max-w-lg" data-rails-pulse--popover-target="menu">
|
|
14
|
+
<div class="flex flex-col">
|
|
15
|
+
<h3 class="font-semibold leading-none mbe-2 uppercase text-sm">Storage</h3>
|
|
16
|
+
<p class="text-sm text-subtle">How full Rails Pulse tables are relative to <code>max_table_records</code>, plus cleanup health. Open the full page for every table, retention details, and database size.</p>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<%= link_to storage_path, class: "block leading-none", title: "View storage details" do %>
|
|
21
|
+
<%= rails_pulse_icon "external-link", height: "16px", width: "16px" %>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="storage-panel-stats">
|
|
27
|
+
<div>
|
|
28
|
+
<p class="font-semibold uppercase text-xs text-subtle mbe-1">Highest fill</p>
|
|
29
|
+
<p class="text-lg font-bold"><% if @storage_status.overview[:hottest_percent] %><%= @storage_status.overview[:hottest_percent].round %>%<% else %>--<% end %></p>
|
|
30
|
+
<p class="text-xs text-subtle"><%= @storage_status.overview[:hottest_label] %></p>
|
|
31
|
+
</div>
|
|
32
|
+
<div>
|
|
33
|
+
<p class="font-semibold uppercase text-xs text-subtle mbe-1">Records</p>
|
|
34
|
+
<p class="text-lg font-bold"><%= number_with_delimiter(@storage_status.overview[:total_records]) %></p>
|
|
35
|
+
<p class="text-xs text-subtle">Across <%= @storage_status.tables.size %> tables</p>
|
|
36
|
+
</div>
|
|
37
|
+
<div>
|
|
38
|
+
<p class="font-semibold uppercase text-xs text-subtle mbe-1">Disk used</p>
|
|
39
|
+
<p class="text-lg font-bold"><%= @storage_status.overview[:display_bytes] ? human_readable_bytes(@storage_status.overview[:display_bytes]) : "—" %></p>
|
|
40
|
+
<p class="text-xs text-subtle"><%= @storage_status.database[:adapter] %></p>
|
|
41
|
+
</div>
|
|
42
|
+
<div>
|
|
43
|
+
<p class="font-semibold uppercase text-xs text-subtle mbe-1">Cleanup</p>
|
|
44
|
+
<p class="text-lg font-bold storage-health storage-health--<%= @storage_status.overview[:cleanup_health] %>"><%= @storage_status.overview[:cleanup_label] %></p>
|
|
45
|
+
<p class="text-xs text-subtle">Retain <%= @storage_status.cleanup[:retention_label] %></p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="storage-panel-tables">
|
|
50
|
+
<% @storage_status.dashboard_tables.each do |table| %>
|
|
51
|
+
<div class="storage-panel-row">
|
|
52
|
+
<span class="storage-panel-row__label font-semibold text-sm"><%= table[:label] %></span>
|
|
53
|
+
<div class="storage-fill storage-fill--compact" aria-hidden="true">
|
|
54
|
+
<div class="storage-fill__bar storage-fill__bar--<%= table[:severity] %>" style="width: <%= table[:bar_width] %>%"></div>
|
|
55
|
+
</div>
|
|
56
|
+
<span class="storage-panel-row__count text-xs text-subtle">
|
|
57
|
+
<% if table[:limit] %>
|
|
58
|
+
<%= number_with_delimiter(table[:count]) %> / <%= number_with_delimiter(table[:limit]) %>
|
|
59
|
+
· <%= table[:percent_label] %>
|
|
60
|
+
<% else %>
|
|
61
|
+
<%= number_with_delimiter(table[:count]) %>
|
|
62
|
+
<% end %>
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
<% end %>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
@@ -15,10 +15,22 @@
|
|
|
15
15
|
health: @health_summary[:jobs],
|
|
16
16
|
critical_label: "failing" %>
|
|
17
17
|
<% end %>
|
|
18
|
+
<%# Nil when exception tracking is off, or when the tables have not been
|
|
19
|
+
migrated yet — which is also when exceptions_path does not exist. %>
|
|
20
|
+
<% if @health_summary[:exceptions] %>
|
|
21
|
+
<%= render "rails_pulse/dashboard/health_badge",
|
|
22
|
+
name: "Exceptions",
|
|
23
|
+
health: @health_summary[:exceptions],
|
|
24
|
+
healthy_label: "quiet",
|
|
25
|
+
slow_label: "firing",
|
|
26
|
+
critical_label: "critical",
|
|
27
|
+
link_url: exceptions_path %>
|
|
28
|
+
<% end %>
|
|
18
29
|
<%= render "rails_pulse/dashboard/health_badge",
|
|
19
30
|
name: "Storage",
|
|
20
31
|
health: @health_summary[:storage],
|
|
21
|
-
critical_label: "critical"
|
|
32
|
+
critical_label: "critical",
|
|
33
|
+
link_url: storage_path %>
|
|
22
34
|
</div>
|
|
23
35
|
</div>
|
|
24
36
|
</div>
|
|
@@ -186,3 +198,12 @@
|
|
|
186
198
|
</div>
|
|
187
199
|
<% end %>
|
|
188
200
|
</div>
|
|
201
|
+
|
|
202
|
+
<div class="row">
|
|
203
|
+
<div class="grid-item" style="flex: 1.5;">
|
|
204
|
+
<%= render "rails_pulse/dashboard/storage_panel" %>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="grid-item">
|
|
207
|
+
<%= render "rails_pulse/dashboard/deployments_panel" %>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<% columns = [
|
|
2
|
+
{ field: :revision, label: 'Revision', cell_class: 'truncate-cell' },
|
|
3
|
+
{ field: :status, label: 'Status', class: 'w-28', sortable: false },
|
|
4
|
+
{ field: :started_at, label: 'Started', class: 'w-56' },
|
|
5
|
+
{ field: :finished_at, label: 'Finished', class: 'w-56' }
|
|
6
|
+
] %>
|
|
7
|
+
|
|
8
|
+
<table class="table table-fixed mbs-4">
|
|
9
|
+
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
10
|
+
|
|
11
|
+
<tbody>
|
|
12
|
+
<% @table_data.each do |deployment| %>
|
|
13
|
+
<tr>
|
|
14
|
+
<td class="truncate-cell">
|
|
15
|
+
<%= link_to deployment_path(deployment) do %>
|
|
16
|
+
<code><%= deployment.revision %></code>
|
|
17
|
+
<% end %>
|
|
18
|
+
</td>
|
|
19
|
+
<td><%= render 'rails_pulse/deployments/status_badge', deployment: deployment %></td>
|
|
20
|
+
<td class="whitespace-nowrap"><%= human_readable_occurred_at(deployment.started_at) %></td>
|
|
21
|
+
<td class="whitespace-nowrap">
|
|
22
|
+
<%= deployment.finished_at ? human_readable_occurred_at(deployment.finished_at) : content_tag(:span, "—", class: "text-subtle") %>
|
|
23
|
+
</td>
|
|
24
|
+
</tr>
|
|
25
|
+
<% end %>
|
|
26
|
+
</tbody>
|
|
27
|
+
</table>
|
|
28
|
+
|
|
29
|
+
<%= render "rails_pulse/components/table_pagination" %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%= render 'rails_pulse/components/page_header' %>
|
|
2
|
+
|
|
3
|
+
<%= render 'rails_pulse/components/panel', {
|
|
4
|
+
title: 'Deployments',
|
|
5
|
+
help_heading: 'Deployments',
|
|
6
|
+
help_text: 'Deployments recorded by your CI/CD pipeline via the deployments API or the rails_pulse:record_deployment rake task. They appear as markers on charts and are correlated against detected regressions.'
|
|
7
|
+
} do %>
|
|
8
|
+
<% if @table_data.any? %>
|
|
9
|
+
<%= render 'rails_pulse/deployments/table' %>
|
|
10
|
+
<% else %>
|
|
11
|
+
<%= render 'rails_pulse/components/empty_state',
|
|
12
|
+
title: 'No deployments recorded.',
|
|
13
|
+
description: 'Post to the deployments endpoint from your deploy pipeline, or run rails_pulse:record_deployment, and they will appear here.' %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<%= render 'rails_pulse/components/page_header' %>
|
|
2
|
+
|
|
3
|
+
<div class="row">
|
|
4
|
+
<div class="grid-item">
|
|
5
|
+
<%= render 'rails_pulse/components/panel',
|
|
6
|
+
title: 'Deployment Details',
|
|
7
|
+
help_heading: 'Deployment Details',
|
|
8
|
+
help_text: 'What your CI/CD pipeline reported for this deploy. The start time is what places the marker on charts, so you can line a regression up against the release that caused it.' do %>
|
|
9
|
+
<dl class="descriptive-list mbs-4">
|
|
10
|
+
<dt>Revision</dt>
|
|
11
|
+
<dd><code><%= @deployment.revision %></code></dd>
|
|
12
|
+
<dt>Status</dt>
|
|
13
|
+
<dd><%= render 'rails_pulse/deployments/status_badge', deployment: @deployment %></dd>
|
|
14
|
+
<dt>Started</dt>
|
|
15
|
+
<dd>
|
|
16
|
+
<%= human_readable_occurred_at(@deployment.started_at) %>
|
|
17
|
+
<span class="text-subtle">(<%= time_ago_in_words(@deployment.started_at) %>)</span>
|
|
18
|
+
</dd>
|
|
19
|
+
<dt>Finished</dt>
|
|
20
|
+
<dd>
|
|
21
|
+
<% if @deployment.in_progress? %>
|
|
22
|
+
<span class="text-subtle">—</span>
|
|
23
|
+
<% else %>
|
|
24
|
+
<%= human_readable_occurred_at(@deployment.finished_at) %>
|
|
25
|
+
<span class="text-subtle">(<%= time_ago_in_words(@deployment.finished_at) %>)</span>
|
|
26
|
+
<% end %>
|
|
27
|
+
</dd>
|
|
28
|
+
<dt>Duration</dt>
|
|
29
|
+
<dd>
|
|
30
|
+
<% if @deployment.duration %>
|
|
31
|
+
<%= distance_of_time_in_words(@deployment.started_at, @deployment.finished_at, include_seconds: true) %>
|
|
32
|
+
<% else %>
|
|
33
|
+
<span class="text-subtle">—</span>
|
|
34
|
+
<% end %>
|
|
35
|
+
</dd>
|
|
36
|
+
<dt>Recorded</dt>
|
|
37
|
+
<dd><%= human_readable_occurred_at(@deployment.created_at) %></dd>
|
|
38
|
+
</dl>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="grid-item">
|
|
43
|
+
<%= render 'rails_pulse/components/panel',
|
|
44
|
+
title: 'Metadata',
|
|
45
|
+
help_heading: 'Metadata',
|
|
46
|
+
help_text: 'Free-form key/value pairs sent with the deployment payload — typically environment, branch, and whoever or whatever triggered the deploy.' do %>
|
|
47
|
+
<% if @deployment.metadata_hash.any? %>
|
|
48
|
+
<dl class="descriptive-list mbs-4">
|
|
49
|
+
<% @deployment.metadata_hash.each do |key, value| %>
|
|
50
|
+
<dt><%= key.to_s.humanize %></dt>
|
|
51
|
+
<dd><%= value.is_a?(String) ? value : value.inspect %></dd>
|
|
52
|
+
<% end %>
|
|
53
|
+
</dl>
|
|
54
|
+
<% else %>
|
|
55
|
+
<p class="text-sm text-subtle mbs-4">
|
|
56
|
+
No metadata recorded. Send a metadata object with the deployment payload to
|
|
57
|
+
capture the environment, branch, or trigger for this deploy.
|
|
58
|
+
</p>
|
|
59
|
+
<% end %>
|
|
60
|
+
<% end %>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<%= render 'rails_pulse/components/page_header' %>
|
|
2
|
+
|
|
3
|
+
<%= render 'rails_pulse/components/panel', title: 'Occurrence Details' do %>
|
|
4
|
+
<dl class="descriptive-list mbs-4">
|
|
5
|
+
<dt>Exception</dt>
|
|
6
|
+
<dd><code><%= @occurrence.exception_class %></code></dd>
|
|
7
|
+
<dt>Message</dt>
|
|
8
|
+
<dd><%= @occurrence.message.presence || "—" %></dd>
|
|
9
|
+
<dt>Occurred At</dt>
|
|
10
|
+
<dd><%= human_readable_occurred_at(@occurrence.occurred_at) %></dd>
|
|
11
|
+
<% if @occurrence.request_url.present? %>
|
|
12
|
+
<dt>Request</dt>
|
|
13
|
+
<dd><%= @occurrence.request_method %> <%= @occurrence.request_url %></dd>
|
|
14
|
+
<% end %>
|
|
15
|
+
<dt>Environment</dt>
|
|
16
|
+
<dd><%= @occurrence.environment.presence || "—" %></dd>
|
|
17
|
+
<% if @occurrence.deploy_sha.present? %>
|
|
18
|
+
<dt>Deploy SHA</dt>
|
|
19
|
+
<dd><code><%= @occurrence.deploy_sha %></code></dd>
|
|
20
|
+
<% end %>
|
|
21
|
+
</dl>
|
|
22
|
+
<% end %>
|
|
23
|
+
|
|
24
|
+
<% if @occurrence.request_params.present? %>
|
|
25
|
+
<%= render 'rails_pulse/components/panel', title: 'Request Params' do %>
|
|
26
|
+
<div class="exception-params mbs-4">
|
|
27
|
+
<% @occurrence.request_params.each do |key, value| %>
|
|
28
|
+
<div class="exception-params__row">
|
|
29
|
+
<span class="exception-params__key"><%= key %></span>
|
|
30
|
+
<span class="exception-params__value"><%= value.inspect %></span>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
|
|
37
|
+
<% if @occurrence.backtrace.present? %>
|
|
38
|
+
<%= render 'rails_pulse/components/panel', title: 'Backtrace' do %>
|
|
39
|
+
<%= render 'rails_pulse/exceptions/backtrace', backtrace: @occurrence.backtrace %>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
|
|
43
|
+
<%= render 'rails_pulse/components/raw_data',
|
|
44
|
+
title: 'Raw Data',
|
|
45
|
+
content: RailsPulse::Exceptions::RawData.new(@exception_group, occurrence: @occurrence, aggregates: false).to_s %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<ol class="backtrace mbs-4">
|
|
2
|
+
<% backtrace.each do |frame| %>
|
|
3
|
+
<% is_app = app_frame?(frame) %>
|
|
4
|
+
<% source_lines = frame_source_lines(frame) %>
|
|
5
|
+
<li class="backtrace__frame <%= is_app ? 'backtrace__frame--app' : 'backtrace__frame--gem' %> <%= source_lines ? 'backtrace__frame--expandable' : '' %>">
|
|
6
|
+
<div class="backtrace__row">
|
|
7
|
+
<% if source_lines %>
|
|
8
|
+
<details class="backtrace__source-details">
|
|
9
|
+
<summary class="backtrace__toggle" title="Show source"></summary>
|
|
10
|
+
</details>
|
|
11
|
+
<% else %>
|
|
12
|
+
<div class="backtrace__toggle-spacer"></div>
|
|
13
|
+
<% end %>
|
|
14
|
+
<div class="backtrace__location">
|
|
15
|
+
<span class="backtrace__path"><%= frame_dirname(frame) %></span><span class="backtrace__file"><%= frame_basename(frame) %>:<%= frame["line"] %></span>
|
|
16
|
+
</div>
|
|
17
|
+
<% if frame["method"].present? %>
|
|
18
|
+
<div class="backtrace__method">in <span><%= frame["method"] %></span></div>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
21
|
+
<% if source_lines %>
|
|
22
|
+
<div class="backtrace__source backtrace__source--hidden">
|
|
23
|
+
<% source_lines.each do |lineno, content| %>
|
|
24
|
+
<div class="backtrace__source-line <%= lineno == frame["line"].to_i ? 'backtrace__source-line--current' : '' %>">
|
|
25
|
+
<span class="backtrace__source-lineno"><%= lineno %></span>
|
|
26
|
+
<span class="backtrace__source-code"><%= content %></span>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
<% end %>
|
|
31
|
+
</li>
|
|
32
|
+
<% end %>
|
|
33
|
+
</ol>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<% columns = [
|
|
2
|
+
{ field: :occurred_at, label: 'Occurred At', class: 'w-48', sortable: false },
|
|
3
|
+
{ field: :request_url, label: 'URL', cell_class: 'truncate-cell', sortable: false },
|
|
4
|
+
{ field: :request_method, label: 'Method', class: 'w-24', sortable: false },
|
|
5
|
+
{ field: :environment, label: 'Environment', class: 'w-32', sortable: false }
|
|
6
|
+
] %>
|
|
7
|
+
|
|
8
|
+
<table class="table table-fixed mbs-4">
|
|
9
|
+
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
10
|
+
|
|
11
|
+
<tbody>
|
|
12
|
+
<% @occurrences.each do |occurrence| %>
|
|
13
|
+
<tr>
|
|
14
|
+
<td class="whitespace-nowrap"><%= link_to human_readable_occurred_at(occurrence.occurred_at), exception_occurrence_path(@exception_group, occurrence) %></td>
|
|
15
|
+
<td class="truncate-cell"><%= occurrence.request_url.presence || "—" %></td>
|
|
16
|
+
<td class="whitespace-nowrap"><%= occurrence.request_method.presence || "—" %></td>
|
|
17
|
+
<td class="whitespace-nowrap"><%= occurrence.environment.presence || "—" %></td>
|
|
18
|
+
</tr>
|
|
19
|
+
<% end %>
|
|
20
|
+
</tbody>
|
|
21
|
+
</table>
|
|
22
|
+
|
|
23
|
+
<%= render "rails_pulse/components/table_pagination" %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<% columns = [
|
|
2
|
+
{ field: :exception_class, label: 'Exception', cell_class: 'truncate-cell' },
|
|
3
|
+
{ field: :status, label: 'Status', class: 'w-28', sortable: false },
|
|
4
|
+
{ field: :last_seen_at, label: 'Last Seen', class: 'w-48' },
|
|
5
|
+
{ field: :first_seen_at, label: 'First Seen', class: 'w-48' },
|
|
6
|
+
{ field: :occurrence_count, label: 'Count', class: 'w-24' }
|
|
7
|
+
] %>
|
|
8
|
+
|
|
9
|
+
<table class="table table-fixed mbs-4">
|
|
10
|
+
<%= render "rails_pulse/components/table_head", columns: columns %>
|
|
11
|
+
|
|
12
|
+
<tbody>
|
|
13
|
+
<% @table_data.each do |group| %>
|
|
14
|
+
<tr>
|
|
15
|
+
<td class="truncate-cell">
|
|
16
|
+
<%= link_to group.exception_class, exception_path(group) %>
|
|
17
|
+
<% if group.location.present? %>
|
|
18
|
+
<div class="text-subtle text-xs truncate"><code><%= group.location %></code></div>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% if group.message.present? %>
|
|
21
|
+
<div class="text-subtle text-xs truncate"><%= group.message %></div>
|
|
22
|
+
<% end %>
|
|
23
|
+
</td>
|
|
24
|
+
<td><%= render 'rails_pulse/exceptions/status_badge', group: group %></td>
|
|
25
|
+
<td class="whitespace-nowrap"><%= human_readable_occurred_at(group.last_seen_at) %></td>
|
|
26
|
+
<td class="whitespace-nowrap"><%= human_readable_occurred_at(group.first_seen_at) %></td>
|
|
27
|
+
<td class="whitespace-nowrap"><%= number_with_delimiter(group.occurrence_count) %></td>
|
|
28
|
+
</tr>
|
|
29
|
+
<% end %>
|
|
30
|
+
</tbody>
|
|
31
|
+
</table>
|
|
32
|
+
|
|
33
|
+
<%= render "rails_pulse/components/table_pagination" %>
|