rails_pulse 0.4.2 → 0.5.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 +58 -1
- data/README.md +15 -4
- data/app/assets/stylesheets/rails_pulse/components/chart.css +11 -0
- data/app/assets/stylesheets/rails_pulse/components/descriptive_list.css +19 -1
- data/app/assets/stylesheets/rails_pulse/components/row.css +3 -17
- data/app/controllers/concerns/chart_table_concern.rb +28 -69
- data/app/controllers/concerns/deployment_markers_concern.rb +3 -1
- data/app/controllers/concerns/metric_card_concern.rb +6 -7
- data/app/controllers/rails_pulse/api/v1/base_controller.rb +133 -0
- data/app/controllers/rails_pulse/api/v1/capabilities_controller.rb +27 -0
- data/app/controllers/rails_pulse/api/v1/coverage_controller.rb +158 -0
- data/app/controllers/rails_pulse/api/v1/deployments_controller.rb +40 -0
- data/app/controllers/rails_pulse/api/v1/exceptions_controller.rb +77 -0
- data/app/controllers/rails_pulse/api/v1/insights_controller.rb +62 -0
- data/app/controllers/rails_pulse/api/v1/job_runs_controller.rb +34 -0
- data/app/controllers/rails_pulse/api/v1/jobs_controller.rb +172 -0
- data/app/controllers/rails_pulse/api/v1/queries_controller.rb +123 -0
- data/app/controllers/rails_pulse/api/v1/requests_controller.rb +74 -0
- data/app/controllers/rails_pulse/api/v1/routes_controller.rb +103 -0
- data/app/controllers/rails_pulse/application_controller.rb +1 -1
- data/app/controllers/rails_pulse/dashboard_controller.rb +25 -21
- data/app/controllers/rails_pulse/deployments_controller.rb +23 -13
- data/app/controllers/rails_pulse/exceptions_controller.rb +10 -12
- data/app/controllers/rails_pulse/jobs_controller.rb +1 -2
- data/app/controllers/rails_pulse/queries_controller.rb +6 -5
- data/app/controllers/rails_pulse/requests_controller.rb +15 -14
- data/app/controllers/rails_pulse/routes_controller.rb +4 -8
- data/app/helpers/rails_pulse/application_helper.rb +15 -0
- data/app/helpers/rails_pulse/chart_helper.rb +9 -2
- data/app/helpers/rails_pulse/formatting_helper.rb +11 -12
- data/app/javascript/rails_pulse/controllers/chart_controller.js +80 -43
- data/app/jobs/rails_pulse/backfill_summaries_job.rb +4 -2
- data/app/jobs/rails_pulse/cloud_health_job.rb +11 -0
- data/app/jobs/rails_pulse/cloud_sync_job.rb +11 -0
- data/app/jobs/rails_pulse/summary_job.rb +13 -0
- data/app/models/concerns/rails_pulse/has_metadata.rb +15 -0
- data/app/models/concerns/rails_pulse/taggable.rb +5 -12
- data/app/models/rails_pulse/cards/base.rb +20 -11
- data/app/models/rails_pulse/charts/base.rb +5 -18
- data/app/models/rails_pulse/charts/percentile_chart_base.rb +3 -5
- data/app/models/rails_pulse/cleanup_run.rb +32 -0
- data/app/models/rails_pulse/cloud/buffered_batch.rb +55 -0
- data/app/models/rails_pulse/cloud/installation.rb +55 -0
- data/app/models/rails_pulse/dashboard/charts/response_time_percentiles.rb +4 -5
- data/app/models/rails_pulse/dashboard/charts/throughput_and_errors.rb +4 -5
- data/app/models/rails_pulse/dashboard/concerns/attention_classification.rb +104 -0
- data/app/models/rails_pulse/dashboard/concerns/time_range_helper.rb +25 -2
- data/app/models/rails_pulse/dashboard/health_summary.rb +38 -13
- data/app/models/rails_pulse/dashboard/needs_attention.rb +31 -98
- data/app/models/rails_pulse/dashboard/storage_pressure.rb +39 -63
- data/app/models/rails_pulse/dashboard/storage_status.rb +130 -39
- data/app/models/rails_pulse/deployment.rb +2 -7
- data/app/models/rails_pulse/event.rb +41 -0
- data/app/models/rails_pulse/exception_occurrence.rb +1 -1
- data/app/models/rails_pulse/exceptions/cards/exception_rate.rb +3 -6
- data/app/models/rails_pulse/exceptions/cards/open_groups.rb +1 -4
- data/app/models/rails_pulse/exceptions/cards/total_occurrences.rb +3 -6
- data/app/models/rails_pulse/exceptions/charts/occurrence_volume.rb +4 -7
- data/app/models/rails_pulse/job_run.rb +1 -1
- data/app/models/rails_pulse/jobs/cards/failure_rate.rb +8 -14
- data/app/models/rails_pulse/jobs/cards/p95_duration.rb +8 -14
- data/app/models/rails_pulse/jobs/cards/total_runs.rb +5 -11
- data/app/models/rails_pulse/jobs/charts/duration.rb +1 -2
- data/app/models/rails_pulse/jobs/charts/execution_volume.rb +1 -1
- data/app/models/rails_pulse/jobs/charts/failure_rate.rb +1 -2
- data/app/models/rails_pulse/like_pattern.rb +35 -0
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +6 -12
- data/app/models/rails_pulse/queries/cards/database_load.rb +0 -9
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +5 -11
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +6 -12
- data/app/models/rails_pulse/queries/charts/database_load.rb +5 -8
- data/app/models/rails_pulse/queries/charts/execution_volume.rb +1 -1
- data/app/models/rails_pulse/request.rb +1 -1
- data/app/models/rails_pulse/routes/cards/error_rates.rb +5 -15
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +5 -17
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +5 -17
- data/app/models/rails_pulse/routes/charts/error_rate.rb +1 -1
- data/app/models/rails_pulse/routes/charts/request_volume.rb +1 -1
- data/app/models/rails_pulse/summary.rb +5 -0
- data/app/models/rails_pulse/tables/base.rb +5 -6
- data/app/models/rails_pulse/time_range.rb +309 -0
- data/app/models/rails_pulse/time_window.rb +15 -0
- data/app/models/rails_pulse/writer_heartbeat.rb +90 -0
- data/app/serializers/rails_pulse/api/v1/deployment_serializer.rb +23 -0
- data/app/serializers/rails_pulse/api/v1/exception_group_serializer.rb +23 -0
- data/app/serializers/rails_pulse/api/v1/exception_occurrence_serializer.rb +22 -0
- data/app/serializers/rails_pulse/api/v1/job_run_serializer.rb +28 -0
- data/app/serializers/rails_pulse/api/v1/job_serializer.rb +26 -0
- data/app/serializers/rails_pulse/api/v1/query_serializer.rb +25 -0
- data/app/serializers/rails_pulse/api/v1/request_serializer.rb +22 -0
- data/app/serializers/rails_pulse/api/v1/route_serializer.rb +19 -0
- data/app/services/rails_pulse/cloud/batch.rb +83 -0
- data/app/services/rails_pulse/cloud/client.rb +68 -0
- data/app/services/rails_pulse/cloud/combined_metrics.rb +49 -0
- data/app/services/rails_pulse/cloud/health_item.rb +77 -0
- data/app/services/rails_pulse/cloud/path_prefix.rb +50 -0
- data/app/services/rails_pulse/cloud/preview.rb +83 -0
- data/app/services/rails_pulse/cloud/query_shape.rb +195 -0
- data/app/services/rails_pulse/cloud/record_items.rb +60 -0
- data/app/services/rails_pulse/cloud/route_pattern.rb +111 -0
- data/app/services/rails_pulse/cloud/summary_items.rb +188 -0
- data/app/services/rails_pulse/cloud/sync.rb +275 -0
- data/app/services/rails_pulse/config_recommendations.rb +132 -0
- data/app/services/rails_pulse/deployment_comparison.rb +177 -0
- data/app/services/rails_pulse/exception_capture_service.rb +1 -1
- data/app/services/rails_pulse/operations/change_point.rb +1 -1
- data/app/services/rails_pulse/operations/comparison.rb +1 -1
- data/app/services/rails_pulse/period_insights.rb +131 -0
- data/app/services/rails_pulse/sql_query_normalizer.rb +222 -122
- data/app/services/rails_pulse/summary_service/from_child_periods.rb +161 -0
- data/app/services/rails_pulse/summary_service/from_raw_rows.rb +115 -0
- data/app/services/rails_pulse/summary_service/metrics.rb +38 -0
- data/app/services/rails_pulse/summary_service.rb +121 -133
- data/app/services/rails_pulse/tag_filter_service.rb +5 -5
- data/app/views/layouts/rails_pulse/_time_range_selector.html.erb +4 -1
- data/app/views/rails_pulse/components/_operation_details_popover.html.erb +1 -1
- data/app/views/rails_pulse/components/_table_head.html.erb +2 -2
- data/app/views/rails_pulse/dashboard/_chart_panel.html.erb +6 -1
- data/app/views/rails_pulse/dashboard/_deployments_panel.html.erb +2 -2
- data/app/views/rails_pulse/dashboard/_health_badge.html.erb +29 -20
- data/app/views/rails_pulse/dashboard/_storage_panel.html.erb +13 -3
- data/app/views/rails_pulse/dashboard/index.html.erb +16 -2
- data/app/views/rails_pulse/jobs/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/jobs/index.html.erb +1 -1
- data/app/views/rails_pulse/queries/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/queries/index.html.erb +1 -1
- data/app/views/rails_pulse/queries/show.html.erb +1 -1
- data/app/views/rails_pulse/requests/index.html.erb +1 -1
- data/app/views/rails_pulse/routes/_chart_tabs.html.erb +1 -0
- data/app/views/rails_pulse/routes/index.html.erb +1 -1
- data/app/views/rails_pulse/routes/show.html.erb +1 -1
- data/app/views/rails_pulse/shared/_aggregation_zone_badge.html.erb +4 -0
- data/app/views/rails_pulse/storage/show.html.erb +39 -2
- data/config/routes.rb +23 -0
- data/db/rails_pulse_cloud_schema.rb +58 -0
- data/db/rails_pulse_migrate/20260506000001_create_rails_pulse_exceptions.rb +1 -1
- data/db/rails_pulse_migrate/20260921000001_create_rails_pulse_events.rb +21 -0
- data/db/rails_pulse_schema.rb +20 -2
- data/exe/rails-pulse +7 -0
- data/lib/generators/rails_pulse/base_methods.rb +19 -0
- data/lib/generators/rails_pulse/install_cloud_generator.rb +84 -0
- data/lib/generators/rails_pulse/templates/db/rails_pulse_cloud_schema.rb +58 -0
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +20 -2
- data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_cloud_tables.rb +30 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +71 -19
- data/lib/generators/rails_pulse/upgrade_generator.rb +9 -16
- data/lib/rails_pulse/cleanup_service.rb +37 -20
- data/lib/rails_pulse/cli/agent_files/agents.md +89 -0
- data/lib/rails_pulse/cli/agent_files/claude_skill.md +182 -0
- data/lib/rails_pulse/cli/base_command.rb +37 -0
- data/lib/rails_pulse/cli/client.rb +64 -0
- data/lib/rails_pulse/cli/config.rb +87 -0
- data/lib/rails_pulse/cli/configure.rb +75 -0
- data/lib/rails_pulse/cli/coverage.rb +91 -0
- data/lib/rails_pulse/cli/deployments.rb +54 -0
- data/lib/rails_pulse/cli/exceptions.rb +116 -0
- data/lib/rails_pulse/cli/formatter.rb +31 -0
- data/lib/rails_pulse/cli/insights.rb +70 -0
- data/lib/rails_pulse/cli/install.rb +145 -0
- data/lib/rails_pulse/cli/job_runs.rb +53 -0
- data/lib/rails_pulse/cli/jobs.rb +71 -0
- data/lib/rails_pulse/cli/main.rb +76 -0
- data/lib/rails_pulse/cli/mcp.rb +47 -0
- data/lib/rails_pulse/cli/queries.rb +67 -0
- data/lib/rails_pulse/cli/requests.rb +57 -0
- data/lib/rails_pulse/cli/routes.rb +73 -0
- data/lib/rails_pulse/configuration.rb +100 -2
- data/lib/rails_pulse/current.rb +11 -0
- data/lib/rails_pulse/engine.rb +12 -3
- data/lib/rails_pulse/installers/config_updater.rb +9 -2
- data/lib/rails_pulse/job_run_collector.rb +15 -15
- data/lib/rails_pulse/mcp/server.rb +78 -0
- data/lib/rails_pulse/mcp/tools/coverage.rb +96 -0
- data/lib/rails_pulse/mcp/tools/deployments.rb +117 -0
- data/lib/rails_pulse/mcp/tools/endpoint.rb +167 -0
- data/lib/rails_pulse/mcp/tools/errors.rb +104 -0
- data/lib/rails_pulse/mcp/tools/exception_detail.rb +118 -0
- data/lib/rails_pulse/mcp/tools/exceptions.rb +128 -0
- data/lib/rails_pulse/mcp/tools/helpers.rb +162 -0
- data/lib/rails_pulse/mcp/tools/insights.rb +99 -0
- data/lib/rails_pulse/mcp/tools/jobs.rb +155 -0
- data/lib/rails_pulse/mcp/tools/queries.rb +142 -0
- data/lib/rails_pulse/mcp/tools/routes.rb +108 -0
- data/lib/rails_pulse/mcp/tools/slow_requests.rb +118 -0
- data/lib/rails_pulse/middleware/request_collector.rb +9 -9
- data/lib/rails_pulse/scoped_inflector.rb +35 -0
- data/lib/rails_pulse/statistics.rb +27 -0
- data/lib/rails_pulse/subscribers/exception_subscriber.rb +2 -2
- data/lib/rails_pulse/subscribers/operation_subscriber.rb +15 -29
- data/lib/rails_pulse/tasks/status_reporter.rb +201 -0
- data/lib/rails_pulse/tracker.rb +76 -14
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/rails_pulse.rb +0 -4
- data/lib/tasks/rails_pulse.rake +6 -0
- data/lib/tasks/rails_pulse_tasks.rake +7 -0
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +13 -13
- metadata +123 -24
- data/app/controllers/concerns/page_timings.rb +0 -10
- data/app/controllers/concerns/response_range_concern.rb +0 -45
- data/app/controllers/concerns/time_range_concern.rb +0 -152
- data/app/controllers/concerns/zoom_range_concern.rb +0 -86
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
<div class="flex flex-col">
|
|
15
15
|
<h3 class="font-semibold leading-none mbe-2 uppercase text-sm">Storage</h3>
|
|
16
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
|
+
<% if @storage_status.estimated? %>
|
|
18
|
+
<p class="text-sm text-subtle mbs-2">Counting rows exactly means scanning every table, so the dashboard uses the database's own row estimates<% unless @storage_status.counts_available? %>, which SQLite does not keep<% end %>. The Storage page counts exactly.</p>
|
|
19
|
+
<% end %>
|
|
17
20
|
</div>
|
|
18
21
|
</div>
|
|
19
22
|
</div>
|
|
@@ -23,7 +26,9 @@
|
|
|
23
26
|
</div>
|
|
24
27
|
</div>
|
|
25
28
|
|
|
29
|
+
<% approx = @storage_status.estimated? ? "~" : "" %>
|
|
26
30
|
<div class="storage-panel-stats">
|
|
31
|
+
<% if @storage_status.counts_available? %>
|
|
27
32
|
<div>
|
|
28
33
|
<p class="font-semibold uppercase text-xs text-subtle mbe-1">Highest fill</p>
|
|
29
34
|
<p class="text-lg font-bold"><% if @storage_status.overview[:hottest_percent] %><%= @storage_status.overview[:hottest_percent].round %>%<% else %>--<% end %></p>
|
|
@@ -31,9 +36,10 @@
|
|
|
31
36
|
</div>
|
|
32
37
|
<div>
|
|
33
38
|
<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>
|
|
39
|
+
<p class="text-lg font-bold"><%= approx %><%= number_with_delimiter(@storage_status.overview[:total_records]) %></p>
|
|
35
40
|
<p class="text-xs text-subtle">Across <%= @storage_status.tables.size %> tables</p>
|
|
36
41
|
</div>
|
|
42
|
+
<% end %>
|
|
37
43
|
<div>
|
|
38
44
|
<p class="font-semibold uppercase text-xs text-subtle mbe-1">Disk used</p>
|
|
39
45
|
<p class="text-lg font-bold"><%= @storage_status.overview[:display_bytes] ? human_readable_bytes(@storage_status.overview[:display_bytes]) : "—" %></p>
|
|
@@ -46,6 +52,7 @@
|
|
|
46
52
|
</div>
|
|
47
53
|
</div>
|
|
48
54
|
|
|
55
|
+
<% if @storage_status.counts_available? %>
|
|
49
56
|
<div class="storage-panel-tables">
|
|
50
57
|
<% @storage_status.dashboard_tables.each do |table| %>
|
|
51
58
|
<div class="storage-panel-row">
|
|
@@ -55,13 +62,16 @@
|
|
|
55
62
|
</div>
|
|
56
63
|
<span class="storage-panel-row__count text-xs text-subtle">
|
|
57
64
|
<% if table[:limit] %>
|
|
58
|
-
<%= number_with_delimiter(table[:count]) %> / <%= number_with_delimiter(table[:limit]) %>
|
|
65
|
+
<%= approx %><%= number_with_delimiter(table[:count]) %> / <%= number_with_delimiter(table[:limit]) %>
|
|
59
66
|
· <%= table[:percent_label] %>
|
|
60
67
|
<% else %>
|
|
61
|
-
<%= number_with_delimiter(table[:count]) %>
|
|
68
|
+
<%= approx %><%= number_with_delimiter(table[:count]) %>
|
|
62
69
|
<% end %>
|
|
63
70
|
</span>
|
|
64
71
|
</div>
|
|
65
72
|
<% end %>
|
|
66
73
|
</div>
|
|
74
|
+
<% else %>
|
|
75
|
+
<p class="text-sm text-subtle">Record counts need a full scan of each table on SQLite, so the dashboard leaves them out. <%= link_to "Open Storage", storage_path %> to count them.</p>
|
|
76
|
+
<% end %>
|
|
67
77
|
</div>
|
|
@@ -29,10 +29,24 @@
|
|
|
29
29
|
critical_label: "critical",
|
|
30
30
|
link_url: exceptions_path %>
|
|
31
31
|
<% end %>
|
|
32
|
+
<%# Nil until a background writer has reported a heartbeat. Shown only
|
|
33
|
+
when a writer is backlogged or dropping requests. %>
|
|
34
|
+
<% if @health_summary[:tracking] %>
|
|
35
|
+
<%= render "rails_pulse/dashboard/health_badge",
|
|
36
|
+
name: "Tracking",
|
|
37
|
+
health: @health_summary[:tracking],
|
|
38
|
+
healthy_label: "writing",
|
|
39
|
+
slow_label: "backlogged",
|
|
40
|
+
critical_label: "dropping",
|
|
41
|
+
hide_when_healthy: true,
|
|
42
|
+
link_url: storage_path %>
|
|
43
|
+
<% end %>
|
|
44
|
+
<%# Shown only when storage is under warning or critical pressure. %>
|
|
32
45
|
<%= render "rails_pulse/dashboard/health_badge",
|
|
33
46
|
name: "Storage",
|
|
34
47
|
health: @health_summary[:storage],
|
|
35
48
|
critical_label: "critical",
|
|
49
|
+
hide_when_healthy: true,
|
|
36
50
|
link_url: storage_path %>
|
|
37
51
|
</div>
|
|
38
52
|
</div>
|
|
@@ -130,7 +144,7 @@
|
|
|
130
144
|
</span>
|
|
131
145
|
<% end %>
|
|
132
146
|
<span class="text-xs text-subtle mis-2">Ranked by severity ·
|
|
133
|
-
<%= humanize_time_range(@selected_time_range) %></span>
|
|
147
|
+
<%= humanize_time_range(@time_range.selected_time_range) %></span>
|
|
134
148
|
</div>
|
|
135
149
|
<div
|
|
136
150
|
data-controller="rails-pulse--popover"
|
|
@@ -179,7 +193,7 @@
|
|
|
179
193
|
</span>
|
|
180
194
|
<p class="font-semibold text-sm text-positive">All clear</p>
|
|
181
195
|
<p class="text-xs text-subtle">No routes, queries, or jobs need attention in the
|
|
182
|
-
<%= humanize_time_range(@selected_time_range) %>.</p>
|
|
196
|
+
<%= humanize_time_range(@time_range.selected_time_range) %>.</p>
|
|
183
197
|
</div>
|
|
184
198
|
<% else %>
|
|
185
199
|
<div class="flex flex-col">
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
>
|
|
25
25
|
<%= form.select :duration,
|
|
26
26
|
duration_options(:query),
|
|
27
|
-
{ selected: @selected_response_range },
|
|
27
|
+
{ selected: @time_range&.selected_response_range },
|
|
28
28
|
{ class: "input" }
|
|
29
29
|
%>
|
|
30
30
|
<%= link_to "Reset", query_path(@query), class: "btn btn--plain", style: "text-decoration: none; color: var(--color-text-muted);" if params.has_key?(:q) %>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<%# Zone every chart on the page is bucketed and labelled in. The short
|
|
2
|
+
label is always visible; the title carries the full zone name and, when
|
|
3
|
+
a controller resolved one, the exact window being shown. %>
|
|
4
|
+
<span class="zone-badge" title="<%= time_range_zone_tooltip %>"><%= RailsPulse::TimeRange.aggregation_zone_short_label %></span>
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
<%= render "rails_pulse/components/panel",
|
|
76
76
|
title: "Cleanup",
|
|
77
77
|
help_heading: "How cleanup works",
|
|
78
|
-
help_text: "Time-based cleanup deletes records older than full_retention_period
|
|
78
|
+
help_text: "Time-based cleanup deletes records older than full_retention_period. Count-based cleanup then trims each table down to max_table_records, taking requests and operations only from periods already summarised. Schedule RailsPulse::CleanupJob daily." do %>
|
|
79
79
|
<dl class="descriptive-list mbs-4">
|
|
80
80
|
<dt>Automatic cleanup</dt>
|
|
81
81
|
<dd><%= @status.cleanup[:enabled] ? "Enabled" : "Disabled (archiving_enabled = false)" %></dd>
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
<%= human_readable_occurred_at(@status.cleanup[:last_summary_at]) %>
|
|
88
88
|
<span class="text-subtle">(<%= time_ago_in_words(@status.cleanup[:last_summary_at]) %>)</span>
|
|
89
89
|
<% else %>
|
|
90
|
-
Never — cleanup will not
|
|
90
|
+
Never — count-based cleanup will not trim unsummarised requests
|
|
91
91
|
<% end %>
|
|
92
92
|
</dd>
|
|
93
93
|
<dt>Count caps</dt>
|
|
@@ -120,4 +120,41 @@
|
|
|
120
120
|
<% end %>
|
|
121
121
|
</dl>
|
|
122
122
|
<% end %>
|
|
123
|
+
|
|
124
|
+
<%= render "rails_pulse/components/panel",
|
|
125
|
+
title: "Tracking writers",
|
|
126
|
+
help_heading: "The background writer",
|
|
127
|
+
help_text: "Each process queues tracked requests for one writer thread, which reports a heartbeat every minute. When a queue is full the newest request is dropped rather than slowing the app; dropped requests mean the charts undercount traffic. Raise async_queue_size or fix database latency." do %>
|
|
128
|
+
<% tracking = @status.tracking %>
|
|
129
|
+
<dl class="descriptive-list mbs-4">
|
|
130
|
+
<dt>Live writers</dt>
|
|
131
|
+
<dd><%= tracking[:live_count] %></dd>
|
|
132
|
+
<dt>Queue depth</dt>
|
|
133
|
+
<dd><%= number_with_delimiter(tracking[:queue_depth]) %><% if tracking[:queue_size].to_i.positive? %> <span class="text-subtle">/ <%= number_with_delimiter(tracking[:queue_size]) %> per process</span><% end %></dd>
|
|
134
|
+
<dt>Dropped in the last hour</dt>
|
|
135
|
+
<dd class="<%= tracking[:dropped].to_i.positive? ? "text-negative font-semibold" : "" %>"><%= number_with_delimiter(tracking[:dropped]) %></dd>
|
|
136
|
+
<dt>Last heartbeat</dt>
|
|
137
|
+
<dd>
|
|
138
|
+
<% if tracking[:last_sampled_at] %>
|
|
139
|
+
<%= human_readable_occurred_at(tracking[:last_sampled_at]) %>
|
|
140
|
+
<span class="text-subtle">(<%= time_ago_in_words(tracking[:last_sampled_at]) %>)</span>
|
|
141
|
+
<% else %>
|
|
142
|
+
Never — heartbeats start when a process with <code>async</code> on tracks its first request
|
|
143
|
+
<% end %>
|
|
144
|
+
</dd>
|
|
145
|
+
<% tracking[:processes].each do |process| %>
|
|
146
|
+
<dt><code><%= process[:label] %></code></dt>
|
|
147
|
+
<dd>
|
|
148
|
+
<%= number_with_delimiter(process[:queue_depth]) %> / <%= number_with_delimiter(process[:queue_size]) %> queued
|
|
149
|
+
<% if process[:dropped_last_hour].positive? %>
|
|
150
|
+
<span class="badge storage-table__percent storage-table__percent--critical"><%= number_with_delimiter(process[:dropped_last_hour]) %> dropped</span>
|
|
151
|
+
<% else %>
|
|
152
|
+
<span class="badge storage-table__percent storage-table__percent--uncapped">0 dropped</span>
|
|
153
|
+
<% end %>
|
|
154
|
+
<br>
|
|
155
|
+
<span class="text-subtle">last heartbeat <%= time_ago_in_words(process[:last_seen_at]) %></span>
|
|
156
|
+
</dd>
|
|
157
|
+
<% end %>
|
|
158
|
+
</dl>
|
|
159
|
+
<% end %>
|
|
123
160
|
</div>
|
data/config/routes.rb
CHANGED
|
@@ -37,6 +37,29 @@ RailsPulse::Engine.routes.draw do
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Read-only JSON API for the rails-pulse CLI, the MCP server and CI, all
|
|
41
|
+
# authenticated by config.api_token (app/controllers/rails_pulse/api/v1).
|
|
42
|
+
scope path: "api/v1", module: "api/v1", as: :api_v1 do
|
|
43
|
+
resources :routes, only: :index
|
|
44
|
+
resources :requests, only: :index
|
|
45
|
+
resources :queries, only: :index
|
|
46
|
+
resources :jobs, only: :index
|
|
47
|
+
resources :job_runs, only: :index
|
|
48
|
+
resources :deployments, only: :index
|
|
49
|
+
resources :exceptions, only: %i[index show]
|
|
50
|
+
|
|
51
|
+
# Whether the data is there, rather than a question about the data.
|
|
52
|
+
get "coverage", to: "coverage#show", as: :coverage
|
|
53
|
+
|
|
54
|
+
# What needs attention over one summary period, and whether the
|
|
55
|
+
# thresholds fit it.
|
|
56
|
+
get "insights", to: "insights#show", as: :insights
|
|
57
|
+
|
|
58
|
+
# Which application, environment and version answered, so results gathered
|
|
59
|
+
# against staging are not read as production.
|
|
60
|
+
get "capabilities", to: "capabilities#show", as: :capabilities
|
|
61
|
+
end
|
|
62
|
+
|
|
40
63
|
# CSP compliance testing (development/test only)
|
|
41
64
|
if Rails.env.local?
|
|
42
65
|
get "csp_test", to: "csp_test#show", as: :csp_test
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Rails Pulse Cloud Database Schema
|
|
2
|
+
# The tables Rails Pulse Cloud's sync needs, kept apart from
|
|
3
|
+
# db/rails_pulse_schema.rb so an install that does not use Cloud never has
|
|
4
|
+
# them. Installed by `rails generate rails_pulse:install_cloud`; loaded by its
|
|
5
|
+
# migration, and by db:prepare on a separate Rails Pulse database.
|
|
6
|
+
|
|
7
|
+
RailsPulse::CloudSchema = lambda do |connection|
|
|
8
|
+
required_tables = [ :rails_pulse_cloud_installations, :rails_pulse_cloud_batches ]
|
|
9
|
+
|
|
10
|
+
missing_tables = required_tables.reject { |table| connection.table_exists?(table) }
|
|
11
|
+
if missing_tables.empty?
|
|
12
|
+
puts "[RailsPulse::CloudSchema] All Rails Pulse Cloud tables already exist. Skipping schema load."
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
puts "[RailsPulse::CloudSchema] Creating missing tables: #{missing_tables.join(', ')}"
|
|
17
|
+
|
|
18
|
+
unless connection.table_exists?(:rails_pulse_cloud_installations)
|
|
19
|
+
connection.create_table :rails_pulse_cloud_installations do |t|
|
|
20
|
+
t.string :installation_id, limit: 36, null: false, comment: "UUID naming this Rails Pulse database to Rails Pulse Cloud, generated once"
|
|
21
|
+
t.datetime :last_hour_sent_at, comment: "Start of the newest hour whose summaries were queued for Cloud"
|
|
22
|
+
t.datetime :exception_groups_sent_through, comment: "Exception groups updated before this were queued for Cloud"
|
|
23
|
+
t.datetime :deployments_sent_through, comment: "Deployments updated before this were queued for Cloud"
|
|
24
|
+
t.datetime :last_success_at, comment: "When Cloud last accepted a batch or health update"
|
|
25
|
+
t.datetime :last_health_at, comment: "When the last health update was sent"
|
|
26
|
+
t.text :last_error, comment: "The most recent failure, for rails_pulse:status"
|
|
27
|
+
t.datetime :last_error_at
|
|
28
|
+
t.datetime :paused_until, comment: "No sending before this, after Cloud refused the key, plan, application or contract"
|
|
29
|
+
t.text :pause_reason, comment: "Cloud's message for the pause"
|
|
30
|
+
t.string :contract_deprecated_on, comment: "Date from Cloud's Rails-Pulse-Contract-Deprecated header"
|
|
31
|
+
t.timestamps
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
connection.add_index :rails_pulse_cloud_installations, :installation_id, unique: true,
|
|
35
|
+
name: "index_rp_cloud_installations_on_installation_id"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
unless connection.table_exists?(:rails_pulse_cloud_batches)
|
|
39
|
+
connection.create_table :rails_pulse_cloud_batches do |t|
|
|
40
|
+
t.string :batch_id, limit: 36, null: false, comment: "The batch's UUID, resent unchanged on every retry"
|
|
41
|
+
t.binary :payload, limit: 16.megabytes, null: false, comment: "The batch as gzipped JSON"
|
|
42
|
+
t.integer :byte_size, null: false, comment: "Size of the gzipped payload, for the buffer limit"
|
|
43
|
+
t.integer :item_count, null: false
|
|
44
|
+
t.integer :attempts, null: false, default: 0
|
|
45
|
+
t.datetime :next_attempt_at, null: false, comment: "Not sent before this; set by backoff and Retry-After"
|
|
46
|
+
t.text :last_error
|
|
47
|
+
t.timestamps
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
connection.add_index :rails_pulse_cloud_batches, :batch_id, unique: true, name: "index_rp_cloud_batches_on_batch_id"
|
|
51
|
+
connection.add_index :rails_pulse_cloud_batches, :next_attempt_at, name: "index_rp_cloud_batches_on_next_attempt_at"
|
|
52
|
+
connection.add_index :rails_pulse_cloud_batches, :created_at, name: "index_rp_cloud_batches_on_created_at"
|
|
53
|
+
end
|
|
54
|
+
end unless defined?(RailsPulse::CloudSchema)
|
|
55
|
+
|
|
56
|
+
if defined?(RailsPulse::ApplicationRecord)
|
|
57
|
+
RailsPulse::CloudSchema.call(RailsPulse::ApplicationRecord.connection)
|
|
58
|
+
end
|
|
@@ -32,7 +32,7 @@ class CreateRailsPulseExceptions < ActiveRecord::Migration[7.0]
|
|
|
32
32
|
t.string :request_url, comment: "Nullable — web requests only"
|
|
33
33
|
t.string :request_method, comment: "GET, POST, etc."
|
|
34
34
|
t.string :environment, comment: "production, staging, etc."
|
|
35
|
-
t.string :deploy_sha, comment: "Captured
|
|
35
|
+
t.string :deploy_sha, comment: "Captured at occurrence time — cannot backfill later"
|
|
36
36
|
t.text :request_params, comment: "JSON hash of filtered request params — web requests only"
|
|
37
37
|
t.datetime :occurred_at, null: false
|
|
38
38
|
t.timestamps
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CreateRailsPulseEvents < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
return if table_exists?(:rails_pulse_events)
|
|
4
|
+
|
|
5
|
+
create_table :rails_pulse_events do |t|
|
|
6
|
+
t.string :kind, null: false, comment: "What noticed it, e.g. writer_heartbeat"
|
|
7
|
+
t.string :subject, comment: "Who it is about: host:pid, rule name, job name, exception class"
|
|
8
|
+
t.string :outcome, null: false, comment: "sampled, triggered, clean, insufficient_data, ran"
|
|
9
|
+
t.decimal :value, precision: 15, scale: 6, comment: "The number behind it: requests dropped since the last sample, or the triggered metric value"
|
|
10
|
+
t.datetime :occurred_at, null: false
|
|
11
|
+
t.text :message
|
|
12
|
+
t.text :metadata, comment: "JSON with the kind-specific detail"
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :rails_pulse_events, [ :kind, :occurred_at ],
|
|
17
|
+
name: "index_rp_events_on_kind_and_occurred_at"
|
|
18
|
+
add_index :rails_pulse_events, [ :kind, :subject, :occurred_at ],
|
|
19
|
+
name: "index_rp_events_on_kind_subject_and_occurred_at"
|
|
20
|
+
end
|
|
21
|
+
end
|
data/db/rails_pulse_schema.rb
CHANGED
|
@@ -7,7 +7,7 @@ require "rails_pulse/route_indexes" unless defined?(RailsPulse::RouteIndexes)
|
|
|
7
7
|
RailsPulse::Schema = lambda do |connection|
|
|
8
8
|
adapter = connection.adapter_name.downcase
|
|
9
9
|
# Skip if all tables already exist to prevent conflicts
|
|
10
|
-
required_tables = [ :rails_pulse_routes, :rails_pulse_queries, :rails_pulse_requests, :rails_pulse_operations, :rails_pulse_jobs, :rails_pulse_job_runs, :rails_pulse_summaries, :rails_pulse_deployments, :rails_pulse_exception_groups, :rails_pulse_exception_occurrences ]
|
|
10
|
+
required_tables = [ :rails_pulse_routes, :rails_pulse_queries, :rails_pulse_requests, :rails_pulse_operations, :rails_pulse_jobs, :rails_pulse_job_runs, :rails_pulse_summaries, :rails_pulse_deployments, :rails_pulse_exception_groups, :rails_pulse_exception_occurrences, :rails_pulse_events ]
|
|
11
11
|
|
|
12
12
|
# Check which tables already exist
|
|
13
13
|
existing_tables = required_tables.select { |table| connection.table_exists?(table) }
|
|
@@ -249,7 +249,7 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
249
249
|
t.string :request_url, comment: "Nullable — web requests only"
|
|
250
250
|
t.string :request_method, comment: "GET, POST, etc."
|
|
251
251
|
t.string :environment, comment: "production, staging, etc."
|
|
252
|
-
t.string :deploy_sha, comment: "Captured
|
|
252
|
+
t.string :deploy_sha, comment: "Captured at occurrence time — cannot backfill later"
|
|
253
253
|
t.text :request_params, comment: "JSON hash of filtered request params — web requests only"
|
|
254
254
|
t.datetime :occurred_at, null: false
|
|
255
255
|
t.timestamps
|
|
@@ -259,6 +259,24 @@ RailsPulse::Schema = lambda do |connection|
|
|
|
259
259
|
connection.add_index :rails_pulse_exception_occurrences, :exception_group_id, name: "index_rp_exception_occurrences_on_group_id"
|
|
260
260
|
end
|
|
261
261
|
|
|
262
|
+
unless connection.table_exists?(:rails_pulse_events)
|
|
263
|
+
connection.create_table :rails_pulse_events do |t|
|
|
264
|
+
t.string :kind, null: false, comment: "What noticed it, e.g. writer_heartbeat"
|
|
265
|
+
t.string :subject, comment: "Who it is about: host:pid, rule name, job name, exception class"
|
|
266
|
+
t.string :outcome, null: false, comment: "sampled, triggered, clean, insufficient_data, ran"
|
|
267
|
+
t.decimal :value, precision: 15, scale: 6, comment: "The number behind it: requests dropped since the last sample, or the triggered metric value"
|
|
268
|
+
t.datetime :occurred_at, null: false
|
|
269
|
+
t.text :message
|
|
270
|
+
t.text :metadata, comment: "JSON with the kind-specific detail"
|
|
271
|
+
t.timestamps
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
connection.add_index :rails_pulse_events, [ :kind, :occurred_at ],
|
|
275
|
+
name: "index_rp_events_on_kind_and_occurred_at"
|
|
276
|
+
connection.add_index :rails_pulse_events, [ :kind, :subject, :occurred_at ],
|
|
277
|
+
name: "index_rp_events_on_kind_subject_and_occurred_at"
|
|
278
|
+
end
|
|
279
|
+
|
|
262
280
|
# Add indexes to existing tables for efficient aggregation
|
|
263
281
|
unless connection.index_exists?(:rails_pulse_requests, [ :created_at, :route_id ], name: "idx_requests_for_aggregation")
|
|
264
282
|
connection.add_index :rails_pulse_requests, [ :created_at, :route_id ], name: "idx_requests_for_aggregation"
|
data/exe/rails-pulse
ADDED
|
@@ -15,6 +15,7 @@ module RailsPulse
|
|
|
15
15
|
rails_pulse_deployments
|
|
16
16
|
rails_pulse_exception_groups
|
|
17
17
|
rails_pulse_exception_occurrences
|
|
18
|
+
rails_pulse_events
|
|
18
19
|
].freeze
|
|
19
20
|
|
|
20
21
|
# Generate next migration number for timestamped migrations
|
|
@@ -45,6 +46,24 @@ module RailsPulse
|
|
|
45
46
|
false
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
# True when config/database.yml names a rails_pulse database in any
|
|
50
|
+
# environment.
|
|
51
|
+
def has_separate_database_config?
|
|
52
|
+
config_path = File.join(root_path, "config/database.yml")
|
|
53
|
+
return false unless File.exist?(config_path)
|
|
54
|
+
|
|
55
|
+
require "yaml"
|
|
56
|
+
require "erb"
|
|
57
|
+
# Process ERB before YAML parsing — database.yml files commonly use ERB
|
|
58
|
+
# for environment-specific values. YAML.safe_load alone raises SyntaxError
|
|
59
|
+
# on ERB tags.
|
|
60
|
+
yaml_content = ERB.new(File.read(config_path)).result
|
|
61
|
+
db_config = YAML.safe_load(yaml_content, aliases: true)
|
|
62
|
+
db_config.values.any? { |env| env.is_a?(Hash) && env.key?("rails_pulse") }
|
|
63
|
+
rescue
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
|
|
48
67
|
# Get root path (destination_root in tests, Rails.root in production)
|
|
49
68
|
def root_path
|
|
50
69
|
respond_to?(:destination_root) ? destination_root : Rails.root
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require_relative "base_methods"
|
|
2
|
+
|
|
3
|
+
module RailsPulse
|
|
4
|
+
module Generators
|
|
5
|
+
# Adds the tables Rails Pulse Cloud's sync needs to an existing Rails
|
|
6
|
+
# Pulse install. Kept out of rails_pulse:install so an app that never
|
|
7
|
+
# uses Cloud never has them: like the main install, it copies a schema
|
|
8
|
+
# file (db/rails_pulse_cloud_schema.rb) and a migration that loads it.
|
|
9
|
+
class InstallCloudGenerator < Rails::Generators::Base
|
|
10
|
+
include Rails::Generators::Migration
|
|
11
|
+
include BaseMethods
|
|
12
|
+
|
|
13
|
+
source_root File.expand_path("templates", __dir__)
|
|
14
|
+
|
|
15
|
+
desc "Install the Rails Pulse Cloud tables into an existing Rails Pulse setup"
|
|
16
|
+
|
|
17
|
+
class_option :database, type: :string, default: "detect",
|
|
18
|
+
desc: "Database setup: 'single', 'separate', or 'detect' (default)"
|
|
19
|
+
|
|
20
|
+
def check_rails_pulse_installed
|
|
21
|
+
return if File.exist?(File.join(root_path, "db/rails_pulse_schema.rb"))
|
|
22
|
+
|
|
23
|
+
say "Rails Pulse not detected. Run 'rails generate rails_pulse:install' first.", :red
|
|
24
|
+
exit 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def copy_schema
|
|
28
|
+
copy_file "db/rails_pulse_cloud_schema.rb", "db/rails_pulse_cloud_schema.rb"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The migration goes where Rails Pulse's own migrations run. On a
|
|
32
|
+
# separate database that is db/rails_pulse_migrate: the database
|
|
33
|
+
# already exists, so db:migrate:rails_pulse has to create the tables,
|
|
34
|
+
# and a fresh one built by db:prepare loads the schema file instead.
|
|
35
|
+
def copy_migration
|
|
36
|
+
migration_template(
|
|
37
|
+
"migrations/install_rails_pulse_cloud_tables.rb",
|
|
38
|
+
"#{migration_dir}/install_rails_pulse_cloud_tables.rb"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def display_post_install_message
|
|
43
|
+
say <<~MESSAGE
|
|
44
|
+
|
|
45
|
+
Rails Pulse Cloud tables ready to install (#{separate_database? ? 'separate' : 'single'} database setup).
|
|
46
|
+
|
|
47
|
+
Next steps:
|
|
48
|
+
1. Run: #{separate_database? ? 'rails db:migrate:rails_pulse' : 'rails db:migrate'}
|
|
49
|
+
2. Set these in config/initializers/rails_pulse.rb (the key comes from your
|
|
50
|
+
Application's settings in Rails Pulse Cloud; application is its slug):
|
|
51
|
+
|
|
52
|
+
config.cloud.api_key = ENV["RAILS_PULSE_CLOUD_API_KEY"]
|
|
53
|
+
config.cloud.application = "your-app"
|
|
54
|
+
|
|
55
|
+
3. Schedule the health update every minute, for example in config/recurring.yml:
|
|
56
|
+
|
|
57
|
+
rails_pulse_cloud_health:
|
|
58
|
+
class: RailsPulse::CloudHealthJob
|
|
59
|
+
schedule: every minute
|
|
60
|
+
|
|
61
|
+
The hourly sync needs no schedule: RailsPulse::SummaryJob starts it.
|
|
62
|
+
4. Check what will be sent: rails rails_pulse:cloud:preview
|
|
63
|
+
5. Restart your Rails server and workers
|
|
64
|
+
|
|
65
|
+
The schema file db/rails_pulse_cloud_schema.rb is the source of truth for
|
|
66
|
+
these tables, and rails_pulse:upgrade keeps it current.
|
|
67
|
+
|
|
68
|
+
MESSAGE
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def separate_database?
|
|
74
|
+
return options[:database] == "separate" unless options[:database] == "detect"
|
|
75
|
+
|
|
76
|
+
has_separate_database_config?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def migration_dir
|
|
80
|
+
separate_database? ? "db/rails_pulse_migrate" : "db/migrate"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Rails Pulse Cloud Database Schema
|
|
2
|
+
# The tables Rails Pulse Cloud's sync needs, kept apart from
|
|
3
|
+
# db/rails_pulse_schema.rb so an install that does not use Cloud never has
|
|
4
|
+
# them. Installed by `rails generate rails_pulse:install_cloud`; loaded by its
|
|
5
|
+
# migration, and by db:prepare on a separate Rails Pulse database.
|
|
6
|
+
|
|
7
|
+
RailsPulse::CloudSchema = lambda do |connection|
|
|
8
|
+
required_tables = [ :rails_pulse_cloud_installations, :rails_pulse_cloud_batches ]
|
|
9
|
+
|
|
10
|
+
missing_tables = required_tables.reject { |table| connection.table_exists?(table) }
|
|
11
|
+
if missing_tables.empty?
|
|
12
|
+
puts "[RailsPulse::CloudSchema] All Rails Pulse Cloud tables already exist. Skipping schema load."
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
puts "[RailsPulse::CloudSchema] Creating missing tables: #{missing_tables.join(', ')}"
|
|
17
|
+
|
|
18
|
+
unless connection.table_exists?(:rails_pulse_cloud_installations)
|
|
19
|
+
connection.create_table :rails_pulse_cloud_installations do |t|
|
|
20
|
+
t.string :installation_id, limit: 36, null: false, comment: "UUID naming this Rails Pulse database to Rails Pulse Cloud, generated once"
|
|
21
|
+
t.datetime :last_hour_sent_at, comment: "Start of the newest hour whose summaries were queued for Cloud"
|
|
22
|
+
t.datetime :exception_groups_sent_through, comment: "Exception groups updated before this were queued for Cloud"
|
|
23
|
+
t.datetime :deployments_sent_through, comment: "Deployments updated before this were queued for Cloud"
|
|
24
|
+
t.datetime :last_success_at, comment: "When Cloud last accepted a batch or health update"
|
|
25
|
+
t.datetime :last_health_at, comment: "When the last health update was sent"
|
|
26
|
+
t.text :last_error, comment: "The most recent failure, for rails_pulse:status"
|
|
27
|
+
t.datetime :last_error_at
|
|
28
|
+
t.datetime :paused_until, comment: "No sending before this, after Cloud refused the key, plan, application or contract"
|
|
29
|
+
t.text :pause_reason, comment: "Cloud's message for the pause"
|
|
30
|
+
t.string :contract_deprecated_on, comment: "Date from Cloud's Rails-Pulse-Contract-Deprecated header"
|
|
31
|
+
t.timestamps
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
connection.add_index :rails_pulse_cloud_installations, :installation_id, unique: true,
|
|
35
|
+
name: "index_rp_cloud_installations_on_installation_id"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
unless connection.table_exists?(:rails_pulse_cloud_batches)
|
|
39
|
+
connection.create_table :rails_pulse_cloud_batches do |t|
|
|
40
|
+
t.string :batch_id, limit: 36, null: false, comment: "The batch's UUID, resent unchanged on every retry"
|
|
41
|
+
t.binary :payload, limit: 16.megabytes, null: false, comment: "The batch as gzipped JSON"
|
|
42
|
+
t.integer :byte_size, null: false, comment: "Size of the gzipped payload, for the buffer limit"
|
|
43
|
+
t.integer :item_count, null: false
|
|
44
|
+
t.integer :attempts, null: false, default: 0
|
|
45
|
+
t.datetime :next_attempt_at, null: false, comment: "Not sent before this; set by backoff and Retry-After"
|
|
46
|
+
t.text :last_error
|
|
47
|
+
t.timestamps
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
connection.add_index :rails_pulse_cloud_batches, :batch_id, unique: true, name: "index_rp_cloud_batches_on_batch_id"
|
|
51
|
+
connection.add_index :rails_pulse_cloud_batches, :next_attempt_at, name: "index_rp_cloud_batches_on_next_attempt_at"
|
|
52
|
+
connection.add_index :rails_pulse_cloud_batches, :created_at, name: "index_rp_cloud_batches_on_created_at"
|
|
53
|
+
end
|
|
54
|
+
end unless defined?(RailsPulse::CloudSchema)
|
|
55
|
+
|
|
56
|
+
if defined?(RailsPulse::ApplicationRecord)
|
|
57
|
+
RailsPulse::CloudSchema.call(RailsPulse::ApplicationRecord.connection)
|
|
58
|
+
end
|