railwatch 0.9.4 → 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 (452) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +37 -33
  3. data/CHANGELOG.md +50 -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 +35 -66
  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/configuration.rb +21 -151
  21. data/lib/railwatch/engine.rb +9 -118
  22. data/lib/railwatch/patches.rb +0 -11
  23. data/lib/railwatch/reporter.rb +3 -19
  24. data/lib/railwatch/subscribers/process_info.rb +1 -2
  25. data/lib/railwatch/version.rb +1 -1
  26. data/lib/railwatch.rb +4 -62
  27. data/lib/tasks/railwatch_tasks.rake +3 -325
  28. data/llms.txt +23 -25
  29. metadata +3 -453
  30. data/app/channels/railwatch/environment_channel.rb +0 -62
  31. data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
  32. data/app/controllers/railwatch/alerts_controller.rb +0 -48
  33. data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
  34. data/app/controllers/railwatch/attachments_controller.rb +0 -22
  35. data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
  36. data/app/controllers/railwatch/cache_events_controller.rb +0 -30
  37. data/app/controllers/railwatch/commands_controller.rb +0 -16
  38. data/app/controllers/railwatch/comments_controller.rb +0 -11
  39. data/app/controllers/railwatch/dashboard_controller.rb +0 -70
  40. data/app/controllers/railwatch/deploys_controller.rb +0 -34
  41. data/app/controllers/railwatch/deprecations_controller.rb +0 -54
  42. data/app/controllers/railwatch/environment_scoped.rb +0 -81
  43. data/app/controllers/railwatch/exceptions_controller.rb +0 -52
  44. data/app/controllers/railwatch/executions_controller.rb +0 -13
  45. data/app/controllers/railwatch/issues_controller.rb +0 -258
  46. data/app/controllers/railwatch/jobs_controller.rb +0 -63
  47. data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
  48. data/app/controllers/railwatch/logs_controller.rb +0 -40
  49. data/app/controllers/railwatch/mails_controller.rb +0 -19
  50. data/app/controllers/railwatch/notifications_controller.rb +0 -11
  51. data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
  52. data/app/controllers/railwatch/overview_controller.rb +0 -38
  53. data/app/controllers/railwatch/people_controller.rb +0 -25
  54. data/app/controllers/railwatch/processes_controller.rb +0 -33
  55. data/app/controllers/railwatch/profiles_controller.rb +0 -65
  56. data/app/controllers/railwatch/queries_controller.rb +0 -59
  57. data/app/controllers/railwatch/releases_controller.rb +0 -75
  58. data/app/controllers/railwatch/requests_controller.rb +0 -37
  59. data/app/controllers/railwatch/saved_views_controller.rb +0 -53
  60. data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
  61. data/app/controllers/railwatch/spans_controller.rb +0 -47
  62. data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
  63. data/app/controllers/railwatch/tenants_controller.rb +0 -44
  64. data/app/controllers/railwatch/thresholds_controller.rb +0 -47
  65. data/app/controllers/railwatch/traces_controller.rb +0 -72
  66. data/app/controllers/railwatch/transactions_controller.rb +0 -21
  67. data/app/controllers/railwatch/view_renders_controller.rb +0 -28
  68. data/app/controllers/railwatch/visits_controller.rb +0 -53
  69. data/app/frontend/components/app-content.tsx +0 -26
  70. data/app/frontend/components/app-logo-icon.tsx +0 -23
  71. data/app/frontend/components/app-logo.tsx +0 -14
  72. data/app/frontend/components/app-shell.tsx +0 -38
  73. data/app/frontend/components/app-sidebar-header.tsx +0 -54
  74. data/app/frontend/components/app-sidebar.tsx +0 -94
  75. data/app/frontend/components/app-wordmark.tsx +0 -29
  76. data/app/frontend/components/breadcrumbs.tsx +0 -46
  77. data/app/frontend/components/icon.tsx +0 -16
  78. data/app/frontend/components/input-error.tsx +0 -18
  79. data/app/frontend/components/nav-footer.tsx +0 -46
  80. data/app/frontend/components/nav-main.tsx +0 -41
  81. data/app/frontend/components/nav-user.tsx +0 -50
  82. data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
  83. data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
  84. data/app/frontend/components/railwatch/code.tsx +0 -28
  85. data/app/frontend/components/railwatch/command-palette.tsx +0 -132
  86. data/app/frontend/components/railwatch/copy-block.tsx +0 -52
  87. data/app/frontend/components/railwatch/copy-id.tsx +0 -49
  88. data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
  89. data/app/frontend/components/railwatch/data-table.tsx +0 -195
  90. data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
  91. data/app/frontend/components/railwatch/empty-state.tsx +0 -159
  92. data/app/frontend/components/railwatch/env-nav.tsx +0 -130
  93. data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
  94. data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
  95. data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
  96. data/app/frontend/components/railwatch/frames.tsx +0 -295
  97. data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
  98. data/app/frontend/components/railwatch/live-dot.tsx +0 -78
  99. data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
  100. data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
  101. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
  102. data/app/frontend/components/railwatch/onboarding.tsx +0 -91
  103. data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
  104. data/app/frontend/components/railwatch/page-header.tsx +0 -42
  105. data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
  106. data/app/frontend/components/railwatch/relative-time.tsx +0 -18
  107. data/app/frontend/components/railwatch/saved-views.tsx +0 -232
  108. data/app/frontend/components/railwatch/segmented.tsx +0 -64
  109. data/app/frontend/components/railwatch/series-chart.tsx +0 -400
  110. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
  111. data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
  112. data/app/frontend/components/railwatch/sort-header.tsx +0 -37
  113. data/app/frontend/components/railwatch/source-link.tsx +0 -57
  114. data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
  115. data/app/frontend/components/railwatch/sql-block.tsx +0 -116
  116. data/app/frontend/components/railwatch/stat.tsx +0 -185
  117. data/app/frontend/components/railwatch/status-badge.tsx +0 -79
  118. data/app/frontend/components/railwatch/step-picker.tsx +0 -57
  119. data/app/frontend/components/railwatch/timeline.tsx +0 -405
  120. data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
  121. data/app/frontend/components/railwatch/window-picker.tsx +0 -147
  122. data/app/frontend/components/ui/avatar.tsx +0 -51
  123. data/app/frontend/components/ui/badge.tsx +0 -46
  124. data/app/frontend/components/ui/breadcrumb.tsx +0 -109
  125. data/app/frontend/components/ui/button.tsx +0 -60
  126. data/app/frontend/components/ui/card.tsx +0 -92
  127. data/app/frontend/components/ui/chart.tsx +0 -374
  128. data/app/frontend/components/ui/checkbox.tsx +0 -32
  129. data/app/frontend/components/ui/collapsible.tsx +0 -31
  130. data/app/frontend/components/ui/command.tsx +0 -182
  131. data/app/frontend/components/ui/dialog.tsx +0 -158
  132. data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
  133. data/app/frontend/components/ui/hover-card.tsx +0 -42
  134. data/app/frontend/components/ui/input.tsx +0 -21
  135. data/app/frontend/components/ui/label.tsx +0 -24
  136. data/app/frontend/components/ui/popover.tsx +0 -87
  137. data/app/frontend/components/ui/select.tsx +0 -185
  138. data/app/frontend/components/ui/separator.tsx +0 -28
  139. data/app/frontend/components/ui/sheet.tsx +0 -137
  140. data/app/frontend/components/ui/sidebar.tsx +0 -726
  141. data/app/frontend/components/ui/skeleton.tsx +0 -13
  142. data/app/frontend/components/ui/sonner.tsx +0 -41
  143. data/app/frontend/components/ui/table.tsx +0 -114
  144. data/app/frontend/components/ui/tabs.tsx +0 -91
  145. data/app/frontend/components/ui/textarea.tsx +0 -18
  146. data/app/frontend/components/ui/tooltip.tsx +0 -61
  147. data/app/frontend/components/user-info.tsx +0 -32
  148. data/app/frontend/components/user-menu-content.tsx +0 -94
  149. data/app/frontend/entrypoints/application.css +0 -244
  150. data/app/frontend/entrypoints/inertia.tsx +0 -92
  151. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  152. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  153. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  154. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  155. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  156. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  157. data/app/frontend/hooks/use-appearance.tsx +0 -67
  158. data/app/frontend/hooks/use-clipboard.ts +0 -32
  159. data/app/frontend/hooks/use-flash.tsx +0 -23
  160. data/app/frontend/hooks/use-hotkeys.ts +0 -63
  161. data/app/frontend/hooks/use-initials.tsx +0 -15
  162. data/app/frontend/hooks/use-live.ts +0 -205
  163. data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
  164. data/app/frontend/hooks/use-mobile.ts +0 -21
  165. data/app/frontend/hooks/use-percentile.ts +0 -24
  166. data/app/frontend/hooks/use-reduced-motion.ts +0 -25
  167. data/app/frontend/hooks/use-row-nav.ts +0 -106
  168. data/app/frontend/hooks/use-step.ts +0 -27
  169. data/app/frontend/hooks/use-ticker.ts +0 -12
  170. data/app/frontend/hooks/use-window.ts +0 -58
  171. data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
  172. data/app/frontend/layouts/app-layout.tsx +0 -21
  173. data/app/frontend/layouts/env-layout.tsx +0 -53
  174. data/app/frontend/layouts/persistent-layout.tsx +0 -18
  175. data/app/frontend/lib/cable.ts +0 -10
  176. data/app/frontend/lib/env-pages.ts +0 -236
  177. data/app/frontend/lib/execution-path.ts +0 -55
  178. data/app/frontend/lib/explain.ts +0 -50
  179. data/app/frontend/lib/filter.ts +0 -25
  180. data/app/frontend/lib/flamegraph.ts +0 -155
  181. data/app/frontend/lib/format.ts +0 -71
  182. data/app/frontend/lib/keyboard.ts +0 -18
  183. data/app/frontend/lib/mount.ts +0 -23
  184. data/app/frontend/lib/source-link.ts +0 -217
  185. data/app/frontend/lib/utils.ts +0 -6
  186. data/app/frontend/pages/alerts/index.tsx +0 -171
  187. data/app/frontend/pages/broadcasts/index.tsx +0 -171
  188. data/app/frontend/pages/cache_events/index.tsx +0 -127
  189. data/app/frontend/pages/commands/index.tsx +0 -143
  190. data/app/frontend/pages/deploys/index.tsx +0 -94
  191. data/app/frontend/pages/deploys/show.tsx +0 -309
  192. data/app/frontend/pages/deprecations/index.tsx +0 -176
  193. data/app/frontend/pages/exceptions/index.tsx +0 -271
  194. data/app/frontend/pages/executions/show.tsx +0 -821
  195. data/app/frontend/pages/issues/index.tsx +0 -399
  196. data/app/frontend/pages/issues/show.tsx +0 -1367
  197. data/app/frontend/pages/jobs/index.tsx +0 -381
  198. data/app/frontend/pages/jobs/klass.tsx +0 -215
  199. data/app/frontend/pages/llm_calls/index.tsx +0 -664
  200. data/app/frontend/pages/logs/index.tsx +0 -303
  201. data/app/frontend/pages/mails/index.tsx +0 -218
  202. data/app/frontend/pages/notifications/index.tsx +0 -161
  203. data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
  204. data/app/frontend/pages/overview/show.tsx +0 -333
  205. data/app/frontend/pages/people/index.tsx +0 -102
  206. data/app/frontend/pages/people/show.tsx +0 -149
  207. data/app/frontend/pages/processes/index.tsx +0 -335
  208. data/app/frontend/pages/profiles/index.tsx +0 -110
  209. data/app/frontend/pages/profiles/show.tsx +0 -105
  210. data/app/frontend/pages/queries/index.tsx +0 -492
  211. data/app/frontend/pages/queries/show.tsx +0 -276
  212. data/app/frontend/pages/releases/index.tsx +0 -190
  213. data/app/frontend/pages/releases/release-health.tsx +0 -68
  214. data/app/frontend/pages/releases/release-path.ts +0 -25
  215. data/app/frontend/pages/releases/show.tsx +0 -262
  216. data/app/frontend/pages/requests/index.tsx +0 -235
  217. data/app/frontend/pages/requests/route.tsx +0 -159
  218. data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
  219. data/app/frontend/pages/spans/index.tsx +0 -229
  220. data/app/frontend/pages/spans/show.tsx +0 -220
  221. data/app/frontend/pages/storage_ops/index.tsx +0 -150
  222. data/app/frontend/pages/tenants/index.tsx +0 -236
  223. data/app/frontend/pages/tenants/show.tsx +0 -477
  224. data/app/frontend/pages/tenants/tenant-path.ts +0 -26
  225. data/app/frontend/pages/thresholds/index.tsx +0 -414
  226. data/app/frontend/pages/traces/show.tsx +0 -208
  227. data/app/frontend/pages/transactions/index.tsx +0 -220
  228. data/app/frontend/pages/view_renders/index.tsx +0 -241
  229. data/app/frontend/pages/visits/index.tsx +0 -321
  230. data/app/frontend/public/icon.png +0 -0
  231. data/app/frontend/public/icon.svg +0 -5
  232. data/app/frontend/routes/index.d.ts +0 -2136
  233. data/app/frontend/routes/index.js +0 -2112
  234. data/app/frontend/types/globals.d.ts +0 -9
  235. data/app/frontend/types/index.ts +0 -288
  236. data/app/frontend/types/vite-env.d.ts +0 -9
  237. data/app/helpers/railwatch/assets_helper.rb +0 -52
  238. data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
  239. data/app/jobs/railwatch/application_job.rb +0 -7
  240. data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
  241. data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
  242. data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
  243. data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
  244. data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
  245. data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
  246. data/app/jobs/railwatch/performance_scan_job.rb +0 -11
  247. data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
  248. data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
  249. data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
  250. data/app/jobs/railwatch/rollup_job.rb +0 -132
  251. data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
  252. data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
  253. data/app/models/railwatch/alert.rb +0 -229
  254. data/app/models/railwatch/alert_rule.rb +0 -121
  255. data/app/models/railwatch/anomaly_rule.rb +0 -51
  256. data/app/models/railwatch/application.rb +0 -44
  257. data/app/models/railwatch/application_record.rb +0 -33
  258. data/app/models/railwatch/comment.rb +0 -36
  259. data/app/models/railwatch/deploy.rb +0 -67
  260. data/app/models/railwatch/environment.rb +0 -68
  261. data/app/models/railwatch/execution_presenter.rb +0 -185
  262. data/app/models/railwatch/filter_query.rb +0 -184
  263. data/app/models/railwatch/followup_receipt.rb +0 -27
  264. data/app/models/railwatch/ingest/batch.rb +0 -355
  265. data/app/models/railwatch/ingest/mapper.rb +0 -585
  266. data/app/models/railwatch/ingest/payload.rb +0 -96
  267. data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
  268. data/app/models/railwatch/ingest/writer.rb +0 -137
  269. data/app/models/railwatch/issue.rb +0 -277
  270. data/app/models/railwatch/issue_activity.rb +0 -23
  271. data/app/models/railwatch/issue_detection_presenter.rb +0 -309
  272. data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
  273. data/app/models/railwatch/maintenance_task.rb +0 -53
  274. data/app/models/railwatch/saved_view.rb +0 -63
  275. data/app/models/railwatch/telemetry/aggregations.rb +0 -251
  276. data/app/models/railwatch/telemetry/attachment.rb +0 -31
  277. data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
  278. data/app/models/railwatch/telemetry/broadcast.rb +0 -13
  279. data/app/models/railwatch/telemetry/cache_event.rb +0 -16
  280. data/app/models/railwatch/telemetry/child.rb +0 -53
  281. data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
  282. data/app/models/railwatch/telemetry/deprecation.rb +0 -13
  283. data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
  284. data/app/models/railwatch/telemetry/exception.rb +0 -28
  285. data/app/models/railwatch/telemetry/execution.rb +0 -152
  286. data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
  287. data/app/models/railwatch/telemetry/export_destination.rb +0 -66
  288. data/app/models/railwatch/telemetry/health_sample.rb +0 -72
  289. data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
  290. data/app/models/railwatch/telemetry/llm_call.rb +0 -37
  291. data/app/models/railwatch/telemetry/log.rb +0 -91
  292. data/app/models/railwatch/telemetry/mail.rb +0 -13
  293. data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
  294. data/app/models/railwatch/telemetry/notification.rb +0 -13
  295. data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
  296. data/app/models/railwatch/telemetry/person.rb +0 -50
  297. data/app/models/railwatch/telemetry/process.rb +0 -10
  298. data/app/models/railwatch/telemetry/profile.rb +0 -37
  299. data/app/models/railwatch/telemetry/query.rb +0 -57
  300. data/app/models/railwatch/telemetry/query_shape.rb +0 -45
  301. data/app/models/railwatch/telemetry/release_health.rb +0 -69
  302. data/app/models/railwatch/telemetry/rollup.rb +0 -157
  303. data/app/models/railwatch/telemetry/session.rb +0 -24
  304. data/app/models/railwatch/telemetry/span.rb +0 -19
  305. data/app/models/railwatch/telemetry/storage_op.rb +0 -13
  306. data/app/models/railwatch/telemetry/tenant.rb +0 -240
  307. data/app/models/railwatch/telemetry/transaction.rb +0 -13
  308. data/app/models/railwatch/telemetry/view_render.rb +0 -13
  309. data/app/models/railwatch/telemetry/visit.rb +0 -24
  310. data/app/models/railwatch/telemetry_record.rb +0 -107
  311. data/app/models/railwatch/threshold.rb +0 -66
  312. data/app/models/railwatch/user.rb +0 -47
  313. data/app/models/railwatch/viewer.rb +0 -13
  314. data/app/models/railwatch/window.rb +0 -66
  315. data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
  316. data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
  317. data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
  318. data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
  319. data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
  320. data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
  321. data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
  322. data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
  323. data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
  324. data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
  325. data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
  326. data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
  327. data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
  328. data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
  329. data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
  330. data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
  331. data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
  332. data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
  333. data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
  334. data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
  335. data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
  336. data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
  337. data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
  338. data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
  339. data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
  340. data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
  341. data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
  342. data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
  343. data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
  344. data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
  345. data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
  346. data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
  347. data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
  348. data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
  349. data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
  350. data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
  351. data/docs/embedded.md +0 -503
  352. data/lib/puma/plugin/railwatch.rb +0 -215
  353. data/lib/railwatch/dashboard_assets.rb +0 -45
  354. data/lib/railwatch/embedded.rb +0 -50
  355. data/lib/railwatch/export/client.rb +0 -124
  356. data/lib/railwatch/export/lease.rb +0 -56
  357. data/lib/railwatch/export/outbox.rb +0 -361
  358. data/lib/railwatch/export/policy.rb +0 -79
  359. data/lib/railwatch/export/sender.rb +0 -130
  360. data/lib/railwatch/ingest_request_body_limit.rb +0 -10
  361. data/lib/railwatch/maintenance.rb +0 -194
  362. data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
  363. data/lib/railwatch/transport/local.rb +0 -78
  364. data/lib/railwatch/transport/socket.rb +0 -183
  365. data/lib/railwatch/writer.rb +0 -370
  366. data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
  367. data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
  368. data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
  369. data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
  370. data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
  371. data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
  372. data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
  373. data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
  374. data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
  375. data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
  376. data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
  377. data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
  378. data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
  379. data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
  380. data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
  381. data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
  382. data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
  383. data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
  384. data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
  385. data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
  386. data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
  387. data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
  388. data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
  389. data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
  390. data/public/railwatch/assets/index--NQ7us-L.js +0 -1
  391. data/public/railwatch/assets/index-2kgYe_99.js +0 -1
  392. data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
  393. data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
  394. data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
  395. data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
  396. data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
  397. data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
  398. data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
  399. data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
  400. data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
  401. data/public/railwatch/assets/index-C2j50AL2.js +0 -1
  402. data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
  403. data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
  404. data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
  405. data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
  406. data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
  407. data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
  408. data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
  409. data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
  410. data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
  411. data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
  412. data/public/railwatch/assets/index-Deq14BxB.js +0 -1
  413. data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
  414. data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
  415. data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
  416. data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
  417. data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
  418. data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
  419. data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
  420. data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
  421. data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
  422. data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
  423. data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
  424. data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
  425. data/public/railwatch/assets/route-CWh58C7K.js +0 -1
  426. data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
  427. data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
  428. data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
  429. data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
  430. data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
  431. data/public/railwatch/assets/show-BYPB74t4.js +0 -2
  432. data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
  433. data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
  434. data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
  435. data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
  436. data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
  437. data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
  438. data/public/railwatch/assets/show-g__jnN36.js +0 -1
  439. data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
  440. data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
  441. data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
  442. data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
  443. data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
  444. data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
  445. data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
  446. data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
  447. data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
  448. data/public/railwatch/icon.png +0 -0
  449. data/public/railwatch/icon.svg +0 -5
  450. data/public/railwatch/manifest.json +0 -1245
  451. data/public/railwatch/rails-vite.json +0 -1
  452. /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