railwatch 0.9.3 → 1.0.0

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.
Files changed (454) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +37 -33
  3. data/CHANGELOG.md +66 -0
  4. data/README.md +51 -60
  5. data/config/routes.rb +0 -59
  6. data/docs/ai-and-mcp.md +9 -8
  7. data/docs/configuration.md +57 -71
  8. data/docs/faq.md +17 -25
  9. data/docs/getting-started.md +105 -117
  10. data/docs/replacing-nightwatch.md +8 -7
  11. data/docs/replacing-sentry.md +12 -16
  12. data/docs/security.md +14 -20
  13. data/docs/self-hosting.md +24 -22
  14. data/docs/testing.md +6 -14
  15. data/docs/troubleshooting.md +12 -46
  16. data/lib/generators/railwatch/install/install_generator.rb +12 -240
  17. data/lib/generators/railwatch/install/templates/initializer.rb.tt +10 -40
  18. data/lib/generators/railwatch/install/templates/post-deploy +0 -7
  19. data/lib/railwatch/authentication.rb +6 -5
  20. data/lib/railwatch/buffer.rb +9 -0
  21. data/lib/railwatch/configuration.rb +21 -151
  22. data/lib/railwatch/engine.rb +9 -118
  23. data/lib/railwatch/patches.rb +0 -11
  24. data/lib/railwatch/reporter.rb +92 -21
  25. data/lib/railwatch/sampler.rb +10 -2
  26. data/lib/railwatch/subscribers/process_info.rb +1 -2
  27. data/lib/railwatch/version.rb +1 -1
  28. data/lib/railwatch.rb +8 -64
  29. data/lib/tasks/railwatch_tasks.rake +3 -325
  30. data/llms.txt +23 -25
  31. metadata +3 -453
  32. data/app/channels/railwatch/environment_channel.rb +0 -62
  33. data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
  34. data/app/controllers/railwatch/alerts_controller.rb +0 -48
  35. data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
  36. data/app/controllers/railwatch/attachments_controller.rb +0 -22
  37. data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
  38. data/app/controllers/railwatch/cache_events_controller.rb +0 -30
  39. data/app/controllers/railwatch/commands_controller.rb +0 -16
  40. data/app/controllers/railwatch/comments_controller.rb +0 -11
  41. data/app/controllers/railwatch/dashboard_controller.rb +0 -70
  42. data/app/controllers/railwatch/deploys_controller.rb +0 -34
  43. data/app/controllers/railwatch/deprecations_controller.rb +0 -54
  44. data/app/controllers/railwatch/environment_scoped.rb +0 -81
  45. data/app/controllers/railwatch/exceptions_controller.rb +0 -52
  46. data/app/controllers/railwatch/executions_controller.rb +0 -13
  47. data/app/controllers/railwatch/issues_controller.rb +0 -258
  48. data/app/controllers/railwatch/jobs_controller.rb +0 -63
  49. data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
  50. data/app/controllers/railwatch/logs_controller.rb +0 -40
  51. data/app/controllers/railwatch/mails_controller.rb +0 -19
  52. data/app/controllers/railwatch/notifications_controller.rb +0 -11
  53. data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
  54. data/app/controllers/railwatch/overview_controller.rb +0 -38
  55. data/app/controllers/railwatch/people_controller.rb +0 -25
  56. data/app/controllers/railwatch/processes_controller.rb +0 -33
  57. data/app/controllers/railwatch/profiles_controller.rb +0 -65
  58. data/app/controllers/railwatch/queries_controller.rb +0 -59
  59. data/app/controllers/railwatch/releases_controller.rb +0 -75
  60. data/app/controllers/railwatch/requests_controller.rb +0 -37
  61. data/app/controllers/railwatch/saved_views_controller.rb +0 -53
  62. data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
  63. data/app/controllers/railwatch/spans_controller.rb +0 -47
  64. data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
  65. data/app/controllers/railwatch/tenants_controller.rb +0 -44
  66. data/app/controllers/railwatch/thresholds_controller.rb +0 -47
  67. data/app/controllers/railwatch/traces_controller.rb +0 -72
  68. data/app/controllers/railwatch/transactions_controller.rb +0 -21
  69. data/app/controllers/railwatch/view_renders_controller.rb +0 -28
  70. data/app/controllers/railwatch/visits_controller.rb +0 -53
  71. data/app/frontend/components/app-content.tsx +0 -26
  72. data/app/frontend/components/app-logo-icon.tsx +0 -23
  73. data/app/frontend/components/app-logo.tsx +0 -14
  74. data/app/frontend/components/app-shell.tsx +0 -38
  75. data/app/frontend/components/app-sidebar-header.tsx +0 -54
  76. data/app/frontend/components/app-sidebar.tsx +0 -94
  77. data/app/frontend/components/app-wordmark.tsx +0 -29
  78. data/app/frontend/components/breadcrumbs.tsx +0 -46
  79. data/app/frontend/components/icon.tsx +0 -16
  80. data/app/frontend/components/input-error.tsx +0 -18
  81. data/app/frontend/components/nav-footer.tsx +0 -46
  82. data/app/frontend/components/nav-main.tsx +0 -41
  83. data/app/frontend/components/nav-user.tsx +0 -50
  84. data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
  85. data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
  86. data/app/frontend/components/railwatch/code.tsx +0 -28
  87. data/app/frontend/components/railwatch/command-palette.tsx +0 -132
  88. data/app/frontend/components/railwatch/copy-block.tsx +0 -52
  89. data/app/frontend/components/railwatch/copy-id.tsx +0 -49
  90. data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
  91. data/app/frontend/components/railwatch/data-table.tsx +0 -195
  92. data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
  93. data/app/frontend/components/railwatch/empty-state.tsx +0 -159
  94. data/app/frontend/components/railwatch/env-nav.tsx +0 -130
  95. data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
  96. data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
  97. data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
  98. data/app/frontend/components/railwatch/frames.tsx +0 -295
  99. data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
  100. data/app/frontend/components/railwatch/live-dot.tsx +0 -78
  101. data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
  102. data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
  103. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
  104. data/app/frontend/components/railwatch/onboarding.tsx +0 -91
  105. data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
  106. data/app/frontend/components/railwatch/page-header.tsx +0 -42
  107. data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
  108. data/app/frontend/components/railwatch/relative-time.tsx +0 -18
  109. data/app/frontend/components/railwatch/saved-views.tsx +0 -232
  110. data/app/frontend/components/railwatch/segmented.tsx +0 -64
  111. data/app/frontend/components/railwatch/series-chart.tsx +0 -400
  112. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
  113. data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
  114. data/app/frontend/components/railwatch/sort-header.tsx +0 -37
  115. data/app/frontend/components/railwatch/source-link.tsx +0 -57
  116. data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
  117. data/app/frontend/components/railwatch/sql-block.tsx +0 -116
  118. data/app/frontend/components/railwatch/stat.tsx +0 -185
  119. data/app/frontend/components/railwatch/status-badge.tsx +0 -79
  120. data/app/frontend/components/railwatch/step-picker.tsx +0 -57
  121. data/app/frontend/components/railwatch/timeline.tsx +0 -405
  122. data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
  123. data/app/frontend/components/railwatch/window-picker.tsx +0 -147
  124. data/app/frontend/components/ui/avatar.tsx +0 -51
  125. data/app/frontend/components/ui/badge.tsx +0 -46
  126. data/app/frontend/components/ui/breadcrumb.tsx +0 -109
  127. data/app/frontend/components/ui/button.tsx +0 -60
  128. data/app/frontend/components/ui/card.tsx +0 -92
  129. data/app/frontend/components/ui/chart.tsx +0 -374
  130. data/app/frontend/components/ui/checkbox.tsx +0 -32
  131. data/app/frontend/components/ui/collapsible.tsx +0 -31
  132. data/app/frontend/components/ui/command.tsx +0 -182
  133. data/app/frontend/components/ui/dialog.tsx +0 -158
  134. data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
  135. data/app/frontend/components/ui/hover-card.tsx +0 -42
  136. data/app/frontend/components/ui/input.tsx +0 -21
  137. data/app/frontend/components/ui/label.tsx +0 -24
  138. data/app/frontend/components/ui/popover.tsx +0 -87
  139. data/app/frontend/components/ui/select.tsx +0 -185
  140. data/app/frontend/components/ui/separator.tsx +0 -28
  141. data/app/frontend/components/ui/sheet.tsx +0 -137
  142. data/app/frontend/components/ui/sidebar.tsx +0 -726
  143. data/app/frontend/components/ui/skeleton.tsx +0 -13
  144. data/app/frontend/components/ui/sonner.tsx +0 -41
  145. data/app/frontend/components/ui/table.tsx +0 -114
  146. data/app/frontend/components/ui/tabs.tsx +0 -91
  147. data/app/frontend/components/ui/textarea.tsx +0 -18
  148. data/app/frontend/components/ui/tooltip.tsx +0 -61
  149. data/app/frontend/components/user-info.tsx +0 -32
  150. data/app/frontend/components/user-menu-content.tsx +0 -94
  151. data/app/frontend/entrypoints/application.css +0 -244
  152. data/app/frontend/entrypoints/inertia.tsx +0 -92
  153. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  154. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  155. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  156. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  157. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  158. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  159. data/app/frontend/hooks/use-appearance.tsx +0 -67
  160. data/app/frontend/hooks/use-clipboard.ts +0 -32
  161. data/app/frontend/hooks/use-flash.tsx +0 -23
  162. data/app/frontend/hooks/use-hotkeys.ts +0 -63
  163. data/app/frontend/hooks/use-initials.tsx +0 -15
  164. data/app/frontend/hooks/use-live.ts +0 -205
  165. data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
  166. data/app/frontend/hooks/use-mobile.ts +0 -21
  167. data/app/frontend/hooks/use-percentile.ts +0 -24
  168. data/app/frontend/hooks/use-reduced-motion.ts +0 -25
  169. data/app/frontend/hooks/use-row-nav.ts +0 -106
  170. data/app/frontend/hooks/use-step.ts +0 -27
  171. data/app/frontend/hooks/use-ticker.ts +0 -12
  172. data/app/frontend/hooks/use-window.ts +0 -58
  173. data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
  174. data/app/frontend/layouts/app-layout.tsx +0 -21
  175. data/app/frontend/layouts/env-layout.tsx +0 -53
  176. data/app/frontend/layouts/persistent-layout.tsx +0 -18
  177. data/app/frontend/lib/cable.ts +0 -10
  178. data/app/frontend/lib/env-pages.ts +0 -236
  179. data/app/frontend/lib/execution-path.ts +0 -55
  180. data/app/frontend/lib/explain.ts +0 -50
  181. data/app/frontend/lib/filter.ts +0 -25
  182. data/app/frontend/lib/flamegraph.ts +0 -155
  183. data/app/frontend/lib/format.ts +0 -71
  184. data/app/frontend/lib/keyboard.ts +0 -18
  185. data/app/frontend/lib/mount.ts +0 -23
  186. data/app/frontend/lib/source-link.ts +0 -217
  187. data/app/frontend/lib/utils.ts +0 -6
  188. data/app/frontend/pages/alerts/index.tsx +0 -171
  189. data/app/frontend/pages/broadcasts/index.tsx +0 -171
  190. data/app/frontend/pages/cache_events/index.tsx +0 -127
  191. data/app/frontend/pages/commands/index.tsx +0 -143
  192. data/app/frontend/pages/deploys/index.tsx +0 -94
  193. data/app/frontend/pages/deploys/show.tsx +0 -309
  194. data/app/frontend/pages/deprecations/index.tsx +0 -176
  195. data/app/frontend/pages/exceptions/index.tsx +0 -271
  196. data/app/frontend/pages/executions/show.tsx +0 -821
  197. data/app/frontend/pages/issues/index.tsx +0 -399
  198. data/app/frontend/pages/issues/show.tsx +0 -1367
  199. data/app/frontend/pages/jobs/index.tsx +0 -381
  200. data/app/frontend/pages/jobs/klass.tsx +0 -215
  201. data/app/frontend/pages/llm_calls/index.tsx +0 -664
  202. data/app/frontend/pages/logs/index.tsx +0 -303
  203. data/app/frontend/pages/mails/index.tsx +0 -218
  204. data/app/frontend/pages/notifications/index.tsx +0 -161
  205. data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
  206. data/app/frontend/pages/overview/show.tsx +0 -333
  207. data/app/frontend/pages/people/index.tsx +0 -102
  208. data/app/frontend/pages/people/show.tsx +0 -149
  209. data/app/frontend/pages/processes/index.tsx +0 -335
  210. data/app/frontend/pages/profiles/index.tsx +0 -110
  211. data/app/frontend/pages/profiles/show.tsx +0 -105
  212. data/app/frontend/pages/queries/index.tsx +0 -492
  213. data/app/frontend/pages/queries/show.tsx +0 -276
  214. data/app/frontend/pages/releases/index.tsx +0 -190
  215. data/app/frontend/pages/releases/release-health.tsx +0 -68
  216. data/app/frontend/pages/releases/release-path.ts +0 -25
  217. data/app/frontend/pages/releases/show.tsx +0 -262
  218. data/app/frontend/pages/requests/index.tsx +0 -235
  219. data/app/frontend/pages/requests/route.tsx +0 -159
  220. data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
  221. data/app/frontend/pages/spans/index.tsx +0 -229
  222. data/app/frontend/pages/spans/show.tsx +0 -220
  223. data/app/frontend/pages/storage_ops/index.tsx +0 -150
  224. data/app/frontend/pages/tenants/index.tsx +0 -236
  225. data/app/frontend/pages/tenants/show.tsx +0 -477
  226. data/app/frontend/pages/tenants/tenant-path.ts +0 -26
  227. data/app/frontend/pages/thresholds/index.tsx +0 -414
  228. data/app/frontend/pages/traces/show.tsx +0 -208
  229. data/app/frontend/pages/transactions/index.tsx +0 -220
  230. data/app/frontend/pages/view_renders/index.tsx +0 -241
  231. data/app/frontend/pages/visits/index.tsx +0 -321
  232. data/app/frontend/public/icon.png +0 -0
  233. data/app/frontend/public/icon.svg +0 -5
  234. data/app/frontend/routes/index.d.ts +0 -2136
  235. data/app/frontend/routes/index.js +0 -2112
  236. data/app/frontend/types/globals.d.ts +0 -9
  237. data/app/frontend/types/index.ts +0 -288
  238. data/app/frontend/types/vite-env.d.ts +0 -9
  239. data/app/helpers/railwatch/assets_helper.rb +0 -52
  240. data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
  241. data/app/jobs/railwatch/application_job.rb +0 -7
  242. data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
  243. data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
  244. data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
  245. data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
  246. data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
  247. data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
  248. data/app/jobs/railwatch/performance_scan_job.rb +0 -11
  249. data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
  250. data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
  251. data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
  252. data/app/jobs/railwatch/rollup_job.rb +0 -132
  253. data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
  254. data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
  255. data/app/models/railwatch/alert.rb +0 -229
  256. data/app/models/railwatch/alert_rule.rb +0 -121
  257. data/app/models/railwatch/anomaly_rule.rb +0 -51
  258. data/app/models/railwatch/application.rb +0 -44
  259. data/app/models/railwatch/application_record.rb +0 -33
  260. data/app/models/railwatch/comment.rb +0 -36
  261. data/app/models/railwatch/deploy.rb +0 -67
  262. data/app/models/railwatch/environment.rb +0 -68
  263. data/app/models/railwatch/execution_presenter.rb +0 -185
  264. data/app/models/railwatch/filter_query.rb +0 -184
  265. data/app/models/railwatch/followup_receipt.rb +0 -27
  266. data/app/models/railwatch/ingest/batch.rb +0 -355
  267. data/app/models/railwatch/ingest/mapper.rb +0 -585
  268. data/app/models/railwatch/ingest/payload.rb +0 -96
  269. data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
  270. data/app/models/railwatch/ingest/writer.rb +0 -137
  271. data/app/models/railwatch/issue.rb +0 -277
  272. data/app/models/railwatch/issue_activity.rb +0 -23
  273. data/app/models/railwatch/issue_detection_presenter.rb +0 -309
  274. data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
  275. data/app/models/railwatch/maintenance_task.rb +0 -53
  276. data/app/models/railwatch/saved_view.rb +0 -63
  277. data/app/models/railwatch/telemetry/aggregations.rb +0 -251
  278. data/app/models/railwatch/telemetry/attachment.rb +0 -31
  279. data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
  280. data/app/models/railwatch/telemetry/broadcast.rb +0 -13
  281. data/app/models/railwatch/telemetry/cache_event.rb +0 -16
  282. data/app/models/railwatch/telemetry/child.rb +0 -53
  283. data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
  284. data/app/models/railwatch/telemetry/deprecation.rb +0 -13
  285. data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
  286. data/app/models/railwatch/telemetry/exception.rb +0 -28
  287. data/app/models/railwatch/telemetry/execution.rb +0 -152
  288. data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
  289. data/app/models/railwatch/telemetry/export_destination.rb +0 -66
  290. data/app/models/railwatch/telemetry/health_sample.rb +0 -72
  291. data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
  292. data/app/models/railwatch/telemetry/llm_call.rb +0 -37
  293. data/app/models/railwatch/telemetry/log.rb +0 -91
  294. data/app/models/railwatch/telemetry/mail.rb +0 -13
  295. data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
  296. data/app/models/railwatch/telemetry/notification.rb +0 -13
  297. data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
  298. data/app/models/railwatch/telemetry/person.rb +0 -50
  299. data/app/models/railwatch/telemetry/process.rb +0 -10
  300. data/app/models/railwatch/telemetry/profile.rb +0 -37
  301. data/app/models/railwatch/telemetry/query.rb +0 -57
  302. data/app/models/railwatch/telemetry/query_shape.rb +0 -45
  303. data/app/models/railwatch/telemetry/release_health.rb +0 -69
  304. data/app/models/railwatch/telemetry/rollup.rb +0 -157
  305. data/app/models/railwatch/telemetry/session.rb +0 -24
  306. data/app/models/railwatch/telemetry/span.rb +0 -19
  307. data/app/models/railwatch/telemetry/storage_op.rb +0 -13
  308. data/app/models/railwatch/telemetry/tenant.rb +0 -240
  309. data/app/models/railwatch/telemetry/transaction.rb +0 -13
  310. data/app/models/railwatch/telemetry/view_render.rb +0 -13
  311. data/app/models/railwatch/telemetry/visit.rb +0 -24
  312. data/app/models/railwatch/telemetry_record.rb +0 -107
  313. data/app/models/railwatch/threshold.rb +0 -66
  314. data/app/models/railwatch/user.rb +0 -47
  315. data/app/models/railwatch/viewer.rb +0 -13
  316. data/app/models/railwatch/window.rb +0 -66
  317. data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
  318. data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
  319. data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
  320. data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
  321. data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
  322. data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
  323. data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
  324. data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
  325. data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
  326. data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
  327. data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
  328. data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
  329. data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
  330. data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
  331. data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
  332. data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
  333. data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
  334. data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
  335. data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
  336. data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
  337. data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
  338. data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
  339. data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
  340. data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
  341. data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
  342. data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
  343. data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
  344. data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
  345. data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
  346. data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
  347. data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
  348. data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
  349. data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
  350. data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
  351. data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
  352. data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
  353. data/docs/embedded.md +0 -503
  354. data/lib/puma/plugin/railwatch.rb +0 -215
  355. data/lib/railwatch/dashboard_assets.rb +0 -45
  356. data/lib/railwatch/embedded.rb +0 -50
  357. data/lib/railwatch/export/client.rb +0 -124
  358. data/lib/railwatch/export/lease.rb +0 -56
  359. data/lib/railwatch/export/outbox.rb +0 -361
  360. data/lib/railwatch/export/policy.rb +0 -79
  361. data/lib/railwatch/export/sender.rb +0 -130
  362. data/lib/railwatch/ingest_request_body_limit.rb +0 -10
  363. data/lib/railwatch/maintenance.rb +0 -194
  364. data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
  365. data/lib/railwatch/transport/local.rb +0 -78
  366. data/lib/railwatch/transport/socket.rb +0 -183
  367. data/lib/railwatch/writer.rb +0 -370
  368. data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
  369. data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
  370. data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
  371. data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
  372. data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
  373. data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
  374. data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
  375. data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
  376. data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
  377. data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
  378. data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
  379. data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
  380. data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
  381. data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
  382. data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
  383. data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
  384. data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
  385. data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
  386. data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
  387. data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
  388. data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
  389. data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
  390. data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
  391. data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
  392. data/public/railwatch/assets/index--NQ7us-L.js +0 -1
  393. data/public/railwatch/assets/index-2kgYe_99.js +0 -1
  394. data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
  395. data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
  396. data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
  397. data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
  398. data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
  399. data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
  400. data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
  401. data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
  402. data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
  403. data/public/railwatch/assets/index-C2j50AL2.js +0 -1
  404. data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
  405. data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
  406. data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
  407. data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
  408. data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
  409. data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
  410. data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
  411. data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
  412. data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
  413. data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
  414. data/public/railwatch/assets/index-Deq14BxB.js +0 -1
  415. data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
  416. data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
  417. data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
  418. data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
  419. data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
  420. data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
  421. data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
  422. data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
  423. data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
  424. data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
  425. data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
  426. data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
  427. data/public/railwatch/assets/route-CWh58C7K.js +0 -1
  428. data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
  429. data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
  430. data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
  431. data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
  432. data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
  433. data/public/railwatch/assets/show-BYPB74t4.js +0 -2
  434. data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
  435. data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
  436. data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
  437. data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
  438. data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
  439. data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
  440. data/public/railwatch/assets/show-g__jnN36.js +0 -1
  441. data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
  442. data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
  443. data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
  444. data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
  445. data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
  446. data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
  447. data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
  448. data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
  449. data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
  450. data/public/railwatch/icon.png +0 -0
  451. data/public/railwatch/icon.svg +0 -5
  452. data/public/railwatch/manifest.json +0 -1245
  453. data/public/railwatch/rails-vite.json +0 -1
  454. /data/{app/frontend/lib → lib/generators/railwatch/install/templates}/railwatch.ts +0 -0
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class AnomalyRulesController < DashboardController
5
- def create
6
- rule = environment.anomaly_rules.new(anomaly_rule_params)
7
- if rule.save
8
- redirect_to application_environment_thresholds_path(application, environment), notice: "Anomaly rule added"
9
- else
10
- redirect_to application_environment_thresholds_path(application, environment), inertia: { errors: rule.errors }
11
- end
12
- end
13
-
14
- def update
15
- rule = environment.anomaly_rules.find(params[:id])
16
- rule.update(anomaly_rule_params)
17
- redirect_to application_environment_thresholds_path(application, environment), notice: "Anomaly rule updated"
18
- end
19
-
20
- def destroy
21
- environment.anomaly_rules.find(params[:id]).destroy
22
- redirect_to application_environment_thresholds_path(application, environment), notice: "Anomaly rule removed"
23
- end
24
-
25
- private
26
-
27
- def anomaly_rule_params
28
- params.permit(:target_kind, :target, :metric, :deviation, :window_minutes, :baseline_days, :enabled)
29
- end
30
- end
31
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Serves the bytes an app attached to an execution or exception. The row
4
- # only exists in its own environment's telemetry database, so an id from
5
- # another environment 404s here.
6
- module Railwatch
7
- class AttachmentsController < DashboardController
8
- def show
9
- attachment = telemetry { Telemetry::Attachment.find(params[:id]) }
10
- # Rendered as text/plain even for text/html or SVG: these bytes came
11
- # from a monitored app and must never execute on our origin.
12
- if params[:view] == "1" && attachment.viewable?
13
- send_data attachment.body, type: "text/plain; charset=utf-8", disposition: "inline"
14
- else
15
- send_data attachment.body, type: attachment.content_type.presence || "application/octet-stream",
16
- filename: attachment.name, disposition: "attachment"
17
- end
18
- rescue Telemetry::BoundedGzip::Error
19
- head :unprocessable_content
20
- end
21
- end
22
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class BroadcastsController < DashboardController
5
- def index
6
- from, to = window_range
7
- streams = telemetry do
8
- Telemetry::Broadcast.between(from, to).group(:group_hash, :kind).pluck(:group_hash, :kind, Arel.sql("MAX(COALESCE(stream, channel))"), Arel.sql("COUNT(*)"), Arel.sql("SUM(bytes)"), Arel.sql("AVG(duration)"))
9
- .map { |g, k, name, c, b, d| { group_hash: g, kind: k, name: name, count: c, bytes: b.to_i, avg: (d.to_f / 1000.0).round(3) } }.sort_by { |r| -r[:count] }.first(200)
10
- end
11
- recent = telemetry { Telemetry::Broadcast.between(from, to).recent.limit(100).map { |b| { id: b.id, kind: b.kind, stream: b.stream, channel: b.channel, action: b.action, bytes: b.bytes, failed: b.failed, duration: b.duration_ms.round(3), occurred_at: b.occurred_at, execution_id: b.execution_id, execution_preview: b.execution_preview } } }
12
- render inertia: { streams: streams, recent: recent }
13
- end
14
- end
15
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class CacheEventsController < DashboardController
5
- def index
6
- from, to = window_range
7
- fields = FilterQuery.parse(params[:q]).fetch(:fields)
8
- keys = telemetry do
9
- scope = Telemetry::Rollup.for_type("cache_event").between(from, to)
10
- if fields["store"].present?
11
- # Rollup rows have no store column; find the matching group_hashes
12
- # from the raw cache_events for this window and filter by those.
13
- hashes = Telemetry::CacheEvent.between(from, to).where(store: fields["store"]).distinct.pluck(:group_hash)
14
- scope = scope.where(group_hash: hashes)
15
- end
16
- rows = scope.to_a
17
- rows.group_by(&:group_hash).map do |group_hash, group_rows|
18
- count = group_rows.sum(&:count)
19
- hits = group_rows.sum { |r| r.extra["hits"].to_i }
20
- misses = group_rows.sum { |r| r.extra["misses"].to_i }
21
- duration_sum = group_rows.sum(&:duration_sum)
22
- { group_hash: group_hash, key: group_rows.max_by(&:bucket).name, count: count, hits: hits, misses: misses,
23
- hit_rate: (hits + misses).zero? ? nil : (hits * 100.0 / (hits + misses)).round(1),
24
- avg: count.zero? ? 0 : (duration_sum / count / 1000.0).round(3), sparkline: Telemetry::Aggregations.sparkline(group_rows, from, to) }
25
- end.sort_by { |r| -r[:count] }.first(200)
26
- end
27
- render inertia: { keys: keys, series: series("cache_event"), q: params[:q].to_s }
28
- end
29
- end
30
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class CommandsController < DashboardController
5
- def index
6
- rows = telemetry { Telemetry::Execution.commands.between(*window_range).recent.limit(200).to_a }
7
- render inertia: { commands: grouped("command", limit: 100),
8
- runs: rows.map(&:as_row) }
9
- end
10
-
11
- def show
12
- exe = telemetry { Telemetry::Execution.find_by!(execution_id: params[:id]) }
13
- render inertia: "executions/show", props: ExecutionPresenter.new(exe, environment).props
14
- end
15
- end
16
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class CommentsController < DashboardController
5
- def create
6
- issue = Issue.all.find(params[:issue_id])
7
- issue.comments.create!(user: Viewer.user, body: params.require(:body))
8
- redirect_to issue_path(issue), notice: "Comment added"
9
- end
10
- end
11
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- # Base for every dashboard page: the prebuilt Inertia bundle, the shared
5
- # props its layouts read, and per-controller Inertia config so a host that
6
- # also uses Inertia keeps its own.
7
- #
8
- # Inherits from Railwatch.config.base_controller_class (default
9
- # ActionController::Base) so a host can put its own admin gate in front of
10
- # every page the way Mission Control Jobs allows: point it at a controller
11
- # whose before_action requires an admin, and turn HTTP Basic off.
12
- class DashboardController < Railwatch.config.base_controller_class.constantize
13
- include Railwatch::AssetsHelper
14
- helper Railwatch::AssetsHelper
15
- include Railwatch::EnvironmentScoped
16
-
17
- layout "railwatch/dashboard"
18
- # The bundle's pages are named after the platform's controllers
19
- # (overview/show, requests/index); strip the engine namespace so
20
- # default_render finds them.
21
- inertia_config version: -> { Railwatch::AssetsHelper.digest }, layout: "railwatch/dashboard",
22
- use_script_element_for_initial_page: true, always_include_errors_hash: true, default_render: true,
23
- component_path_resolver: ->(path:, action:) { "#{path.delete_prefix('railwatch/')}/#{action}" }
24
- rescue_from Telemetry::CursorPage::InvalidCursor do |exception|
25
- render plain: exception.message, status: :unprocessable_content
26
- end
27
-
28
- # HTTP Basic, on and closed by default (Configuration#http_basic_auth_*).
29
- # Runs before anything reads telemetry; a host that authenticates in its
30
- # base controller or a routes constraint turns it off.
31
- before_action :authenticate_by_http_basic
32
- before_action { Viewer.user = Railwatch.config.resolve_dashboard_user(request) }
33
-
34
- inertia_share auth: -> { { user: Viewer.user.as_json, session: { id: "embedded", recently_authenticated: true } } },
35
- account: -> { Railwatch::Embedded::Account.as_json },
36
- accounts: -> { [ { id: 1, name: Railwatch::Embedded::Account.name } ] },
37
- applications: -> { [ { id: 1, name: Application.current.name, slug: Application.current.slug,
38
- issue_prefix: Application.current.issue_prefix,
39
- environments: [ { id: 1, name: Environment.current.name, slug: Environment.current.slug,
40
- last_seen_at: Environment.current.last_seen_at, paused: false } ] } ] },
41
- flash: -> { { alert: flash.alert, warning: flash[:warning], notice: flash.notice } },
42
- google_oauth: false,
43
- embedded: true
44
-
45
- private
46
-
47
- def authenticate_by_http_basic
48
- config = Railwatch.config
49
- return unless config.http_basic_auth_enabled
50
- return if config.http_basic_auth_waived?
51
-
52
- if config.http_basic_auth_configured?
53
- http_basic_authenticate_or_request_with(name: config.http_basic_auth_user, password: config.http_basic_auth_password,
54
- realm: "Railwatch")
55
- else
56
- # Closed, not open: same as Mission Control with no credentials. The
57
- # challenge header is still sent so a client or a monitor sees the
58
- # scheme, and the body says what to do, since a bare 401 looks like a
59
- # broken install rather than an unconfigured one.
60
- response.set_header("WWW-Authenticate", %(Basic realm="Railwatch"))
61
- render plain: "Railwatch: HTTP Basic authentication is on and no credentials are configured, so the " \
62
- "dashboard is closed. Run `bin/rails railwatch:authentication:configure` (writes " \
63
- "railwatch.http_basic_auth_user/_password to Rails credentials), or set " \
64
- "RAILWATCH_HTTP_BASIC_AUTH_USER and _PASSWORD, or turn Basic off " \
65
- "(c.http_basic_auth_enabled = false) once your own auth gates the mount. See docs/embedded.md.",
66
- status: :unauthorized
67
- end
68
- end
69
- end
70
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class DeploysController < DashboardController
5
- def index
6
- render inertia: { deploys: environment.deploys.recent.limit(100).map { |d| deploy_row(d) } }
7
- end
8
-
9
- # Before/after comparison: the hour before the deploy versus the hour after.
10
- def show
11
- deploy = environment.deploys.find(params[:id])
12
- before = [ deploy.deployed_at - 1.hour, deploy.deployed_at ]
13
- after = [ deploy.deployed_at, deploy.deployed_at + 1.hour ]
14
- compare, health = telemetry do
15
- [ %w[request job_attempt].to_h do |type|
16
- [ type, { before: Telemetry::Rollup.summarize(Telemetry::Rollup.for_type(type).where(bucket: before.first.beginning_of_hour..before.last)),
17
- after: Telemetry::Rollup.summarize(Telemetry::Rollup.for_type(type).where(bucket: after.first.beginning_of_hour..after.last)) } ]
18
- end, Telemetry::ReleaseHealth.for_deploy(deploy.deploy, *deploy.window) ]
19
- end
20
- new_issues = environment.issues.where(first_seen_at: after.first..after.last + 23.hours).recent.map { |i| { id: i.id, key: i.key, title: i.title, status: i.status } }
21
- resolved = environment.issues.where(resolved_in_deploy: deploy.deploy).map { |i| { id: i.id, key: i.key, title: i.title } }
22
- render inertia: { deploy: deploy_row(deploy).merge(commits: deploy.commits, previous_ref: deploy.previous_ref,
23
- repository_ref: deploy.ref, detail: deploy.detail),
24
- compare: compare, health: health, new_issues: new_issues, resolved_issues: resolved }
25
- end
26
-
27
- private
28
-
29
- def deploy_row(d)
30
- { id: d.id, deploy: d.deploy, ref: d.short_ref, name: d.name, url: d.url, server: d.server, deployed_at: d.deployed_at,
31
- commits_count: d.commits_count, performer: d.detail["performer"] }
32
- end
33
- end
34
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class DeprecationsController < DashboardController
5
- def index
6
- from, to = window_range
7
- parsed = FilterQuery.parse(params[:q])
8
- fields = parsed[:fields]
9
- data = telemetry do
10
- scope = Telemetry::Deprecation.between(from, to)
11
- scope = scope.where(gem_name: fields["gem"]) if fields["gem"].present?
12
- scope = scope.where("message LIKE ?", "%#{Telemetry::Deprecation.sanitize_sql_like(parsed[:text])}%") if parsed[:text].present?
13
- rows = scope.to_a
14
- hashes = rows.map(&:group_hash).uniq
15
- first_ever = Telemetry::Deprecation.where(group_hash: hashes).group(:group_hash).minimum(:occurred_at)
16
- groups = rows.group_by(&:group_hash)
17
- .map { |group_hash, group_rows| deprecation_row(group_hash, group_rows, first_ever[group_hash], from, to) }
18
- .sort_by { |r| -r[:count] }
19
- { groups: groups.first(200), distinct: groups.size, total_occurrences: groups.sum { |g| g[:count] },
20
- new_this_window: groups.count { |g| g[:first_seen_at] && g[:first_seen_at] >= from } }
21
- end
22
- render inertia: { deprecations: data[:groups], distinct: data[:distinct], total_occurrences: data[:total_occurrences],
23
- new_this_window: data[:new_this_window], q: params[:q].to_s }
24
- end
25
-
26
- private
27
-
28
- def deprecation_row(group_hash, group_rows, first_seen_at, from, to)
29
- sorted = group_rows.sort_by(&:occurred_at)
30
- { group_hash: group_hash, message: sorted.last.message, gem_name: sorted.last.gem_name, horizon: sorted.last.horizon,
31
- source: sorted.last.source, count: group_rows.size, first_seen_at: first_seen_at, last_seen_at: sorted.last.occurred_at,
32
- sparkline: hourly_sparkline(group_rows, from, to), occurrences: sorted.reverse.first(20).map { |r| occurrence_row(r) } }
33
- end
34
-
35
- def occurrence_row(r)
36
- { id: r.id, occurred_at: r.occurred_at, execution_id: r.execution_id, execution_source: r.execution_source,
37
- execution_preview: r.execution_preview }
38
- end
39
-
40
- # Hourly counts for raw (non-rollup) rows, zero-filled and coarsened to at
41
- # most 30 points for wide windows -- deprecations have no duration column
42
- # so they never land in Telemetry::Rollup and can't use
43
- # EnvironmentScoped#sparkline, which keys off a rollup row's bucket/count.
44
- def hourly_sparkline(rows, from, to)
45
- hours = [ ((to - from) / 1.hour).ceil, 1 ].max
46
- coarsen = (hours / 30.0).ceil
47
- base = from.beginning_of_hour
48
- buckets = Hash.new(0)
49
- rows.each { |r| buckets[((r.occurred_at - base) / 1.hour).to_i / coarsen] += 1 }
50
- bucket_count = (hours.to_f / coarsen).ceil
51
- (0...bucket_count).map { |i| buckets[i] }
52
- end
53
- end
54
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- # The platform's EnvironmentScoped concern with the account lookup replaced
5
- # by the embedded singleton. Everything below the seam is the platform's code.
6
- module EnvironmentScoped
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- before_action :set_environment
11
- inertia_share environment: -> { environment_props }
12
- inertia_share window: -> { window.key }
13
- inertia_share range: -> { window.to_h }
14
- inertia_share step: -> { step_key }
15
- inertia_share steps: -> { Telemetry::Aggregations.steps_for(*window_range) }
16
- inertia_share saved_views: -> { SavedView.props_for(environment, Viewer.user) }
17
- end
18
-
19
- private
20
-
21
- attr_reader :environment
22
-
23
- def set_environment
24
- @environment = Environment.find(params[:environment_id] || Environment::ID)
25
- end
26
-
27
- def window
28
- @window ||= Window.parse(window: params[:window], from: params[:from], to: params[:to])
29
- end
30
-
31
- def window_range = window.range
32
-
33
- # The chart bucket width: ?step= when it is one the window offers, else the
34
- # window's default (a minute for an hour, an hour for a day, and so on).
35
- def step_key
36
- return @step_key if defined?(@step_key)
37
- from, to = window_range
38
- offered = Telemetry::Aggregations.steps_for(from, to)
39
- @step_key = offered.include?(params[:step].to_s) ? params[:step].to_s : Telemetry::Aggregations.default_step(from, to)
40
- end
41
-
42
- def telemetry(&block) = environment.with_telemetry(&block)
43
-
44
- def telemetry_cursor_context(resource)
45
- from, to = window_range
46
- FilterQuery.cursor_context(environment_id: environment.id, resource: resource, query: params[:q], window: [ from.iso8601(6), to.iso8601(6) ])
47
- end
48
-
49
- def application = Application.current
50
-
51
- def environment_props
52
- { id: environment.id, name: environment.name, slug: environment.slug, application_id: application.id,
53
- application_name: application.name, issue_prefix: application.issue_prefix, last_seen_at: environment.last_seen_at,
54
- paused: false, token_prefix: environment.token_prefix,
55
- repository_url: application.repository_url, default_branch: application.default_branch }
56
- end
57
-
58
- def deploys_in_window
59
- environment.deploys.between(*window_range).recent.limit(50).map { |d| { deploy: d.deploy, ref: d.short_ref, at: d.deployed_at } }
60
- end
61
-
62
- # Time-bucketed series for charts, one point per step_key across the
63
- # window: [{t, count, errors, client_errors, avg, p50, p95, p99}]
64
- def series(record_type, group_hash: nil)
65
- from, to = window_range
66
- Telemetry::Aggregations.series(environment, record_type, from: from, to: to, group_hash: group_hash, step: step_key)
67
- end
68
-
69
- def grouped(record_type, limit: 100, order: nil, dir: nil)
70
- from, to = window_range
71
- Telemetry::Aggregations.grouped(environment, record_type, from: from, to: to, limit: limit, order: order, dir: dir)
72
- end
73
-
74
- def summary_with_delta(record_type, group_hash: nil)
75
- from, to = window_range
76
- previous_from, previous_to = window.previous.range
77
- Telemetry::Aggregations.summary_with_delta(environment, record_type, from: from, to: to,
78
- previous_from: previous_from, previous_to: previous_to, group_hash: group_hash)
79
- end
80
- end
81
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railwatch
4
- class ExceptionsController < DashboardController
5
- def index
6
- from, to = window_range
7
- page_data = nil
8
- page = lambda do
9
- page_data ||= telemetry do
10
- scope = FilterQuery.apply(Telemetry::Exception.all, resource: :exceptions, query: params[:q], from: from, to: to)
11
- recent, pagination = Telemetry::CursorPage.call(scope, cursor: params[:cursor], limit: params[:limit],
12
- context: telemetry_cursor_context(:exceptions))
13
- issues_by_group = environment.issues.where(group_hash: recent.map(&:group_hash).uniq).index_by(&:group_hash)
14
- [ recent.map { |e| exception_row(e, issues_by_group) }, pagination ]
15
- end
16
- end
17
- render inertia: {
18
- exceptions: InertiaRails.merge { page.call.first }, pagination: -> { page.call.last },
19
- classes: -> { telemetry { Telemetry::Exception.between(from, to).group(:class_name).count.sort_by { |_class, n| -n }.first(15) } },
20
- series: -> { telemetry { exception_series(from, to) } },
21
- total: -> { telemetry { Telemetry::Exception.between(from, to).count } },
22
- unhandled: -> { telemetry { Telemetry::Exception.between(from, to).unhandled.count } }, q: params[:q].to_s
23
- }
24
- end
25
-
26
- private
27
-
28
- def exception_row(e, issues_by_group)
29
- issue = issues_by_group[e.group_hash]
30
- { id: e.id, class_name: e.class_name, message: e.message.first(500), handled: e.handled, severity: e.severity, source: e.source,
31
- file: e.file, line: e.line, occurred_at: e.occurred_at, execution_id: e.execution_id, execution_source: e.execution_source,
32
- execution_preview: e.execution_preview, user_ref: e.user_ref, group_hash: e.group_hash, issue_id: issue&.id, issue_key: issue&.key }
33
- end
34
-
35
- # Telemetry::Rollup has no "exception" record type (exceptions carry no
36
- # duration to absorb), so the chart is a plain bucketed count over the raw
37
- # table instead of the usual EnvironmentScoped#series.
38
- # `errors` carries the unhandled count so the chart legend (handled /
39
- # unhandled) agrees with the Unhandled card above the table.
40
- def exception_series(from, to)
41
- step = Telemetry::Aggregations::STEPS.fetch(step_key)
42
- bucket = Telemetry::Aggregations.bucket_sql("occurred_at", step)
43
- buckets = Hash.new { |h, k| h[k] = { count: 0, errors: 0 } }
44
- Telemetry::Exception.between(from, to).group(bucket, :handled).count.each do |(b, handled), c|
45
- buckets[b][:count] += c
46
- buckets[b][:errors] += c unless handled
47
- end
48
- points = buckets.map { |b, c| { t: Time.at(b).utc, count: c[:count], errors: c[:errors], client_errors: 0, avg: nil, p50: nil, p95: nil, p99: nil } }
49
- Telemetry::Aggregations.fill(points, from, to, step)
50
- end
51
- end
52
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Generic detail route for execution kinds that do not have a dedicated index
4
- # yet (for example Action Cable channel actions), and for legacy child records
5
- # whose execution_source was absent.
6
- module Railwatch
7
- class ExecutionsController < DashboardController
8
- def show
9
- execution = telemetry { Telemetry::Execution.find_by!(execution_id: params[:id]) }
10
- render inertia: "executions/show", props: ExecutionPresenter.new(execution, environment).props
11
- end
12
- end
13
- end