@oneuptime/common 12.0.33 → 12.0.34

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 (652) hide show
  1. package/Models/AnalyticsModels/RumSession.ts +117 -1
  2. package/Models/AnalyticsModels/RumSessionChunk.ts +13 -0
  3. package/Models/DatabaseModels/AlertCustomField.ts +77 -3
  4. package/Models/DatabaseModels/ApiKeyPermission.ts +3 -27
  5. package/Models/DatabaseModels/IncidentCustomField.ts +77 -3
  6. package/Models/DatabaseModels/InventoryItemCustomField.ts +77 -3
  7. package/Models/DatabaseModels/MonitorCustomField.ts +77 -3
  8. package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +77 -3
  9. package/Models/DatabaseModels/RumApplication.ts +6 -6
  10. package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +77 -3
  11. package/Models/DatabaseModels/StatusPageCustomField.ts +77 -3
  12. package/Models/DatabaseModels/TeamCustomField.ts +77 -3
  13. package/Models/DatabaseModels/TeamMember.ts +0 -9
  14. package/Models/DatabaseModels/TeamMemberCustomField.ts +77 -3
  15. package/Models/DatabaseModels/TeamPermission.ts +3 -24
  16. package/Models/DatabaseModels/TelemetryIngestionKey.ts +260 -0
  17. package/Models/DatabaseModels/UserTelegram.ts +0 -4
  18. package/Scripts/benchmark-fanin-capacity.js +181 -0
  19. package/Server/API/TelemetryAPI.ts +1284 -164
  20. package/Server/API/UserNotificationSettingAPI.ts +55 -0
  21. package/Server/API/UserTelegramAPI.ts +48 -4
  22. package/Server/EnvironmentConfig.ts +35 -2
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.ts +91 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.ts +67 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.ts +139 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.ts +121 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  28. package/Server/Infrastructure/QueueWorker.ts +20 -14
  29. package/Server/Infrastructure/Semaphore.ts +2 -0
  30. package/Server/Middleware/TelemetryIngest.ts +453 -7
  31. package/Server/Services/AccessTokenService.ts +1 -0
  32. package/Server/Services/AlertCustomFieldService.ts +98 -0
  33. package/Server/Services/AlertService.ts +37 -0
  34. package/Server/Services/ApiKeyPermissionService.ts +461 -29
  35. package/Server/Services/CustomFieldMappingService.ts +879 -0
  36. package/Server/Services/GlobalConfigService.ts +194 -0
  37. package/Server/Services/IncidentCustomFieldService.ts +98 -0
  38. package/Server/Services/IncidentService.ts +35 -0
  39. package/Server/Services/MetricService.ts +194 -0
  40. package/Server/Services/MonitorService.ts +32 -0
  41. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +78 -25
  42. package/Server/Services/ProjectService.ts +22 -0
  43. package/Server/Services/RoutineEmailSettingsService.ts +73 -0
  44. package/Server/Services/RumSessionReplayViewService.ts +104 -24
  45. package/Server/Services/ScheduledMaintenanceCustomFieldService.ts +98 -0
  46. package/Server/Services/ScheduledMaintenanceService.ts +32 -0
  47. package/Server/Services/TeamMemberService.ts +78 -0
  48. package/Server/Services/TeamPermissionService.ts +235 -3
  49. package/Server/Services/TelemetryIngestionKeyService.ts +722 -19
  50. package/Server/Services/UserNotificationSettingService.ts +34 -6
  51. package/Server/Services/UserTelegramService.ts +326 -5
  52. package/Server/Types/Database/QueryHelper.ts +2 -0
  53. package/Server/Utils/APIKey/AccessPermission.ts +5 -2
  54. package/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.ts +54 -0
  55. package/Server/Utils/CustomField/CustomFieldMappingRegistry.ts +415 -0
  56. package/Server/Utils/CustomField/CustomFieldMappingValidator.ts +335 -0
  57. package/Server/Utils/DataSource/EgressGuard.ts +177 -8
  58. package/Server/Utils/EmailRollup/EmailRollupConstants.ts +38 -5
  59. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +57 -4
  60. package/Server/Utils/FrontendEnvironment.ts +40 -0
  61. package/Server/Utils/LogRedaction.ts +20 -0
  62. package/Server/Utils/Logger.ts +11 -0
  63. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +338 -77
  64. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +30 -4
  65. package/Server/Utils/Monitor/MonitorAlert.ts +29 -6
  66. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +110 -15
  67. package/Server/Utils/Monitor/MonitorIncident.ts +22 -6
  68. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +29 -0
  69. package/Server/Utils/Monitor/SeriesContextEnricher.ts +304 -0
  70. package/Server/Utils/SSRFProtection.ts +217 -10
  71. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +239 -138
  72. package/Server/Utils/SessionReplay/SessionReplayHealthCounters.ts +305 -0
  73. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1430 -289
  74. package/Server/Utils/StartServer.ts +2 -17
  75. package/Server/Utils/TelegramVerificationToken.ts +185 -0
  76. package/Server/Utils/Telemetry/PinServiceName.ts +197 -0
  77. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +38 -16
  78. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +92 -0
  79. package/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.ts +217 -0
  80. package/Server/Utils/VM/VMAPI.ts +1 -0
  81. package/Server/Utils/VM/VMRunner.ts +975 -95
  82. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +202 -0
  83. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +404 -0
  84. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +468 -0
  85. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +399 -0
  86. package/Tests/App/StatusPage/StatusPageLastUpdated.test.tsx +263 -0
  87. package/Tests/App/StatusPage/StatusPageOverviewLiveAndSearch.test.tsx +753 -0
  88. package/Tests/App/StatusPage/StatusPageResourceSearchBox.test.tsx +224 -0
  89. package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +262 -0
  90. package/Tests/Models/CustomFieldMappingColumns.test.ts +166 -0
  91. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +20 -6
  92. package/Tests/ResponsiveVisibility.test.ts +115 -0
  93. package/Tests/ResponsiveVisibility.ts +183 -0
  94. package/Tests/Server/API/SessionReplayAPI.test.ts +1940 -216
  95. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +186 -0
  96. package/Tests/Server/API/UserTelegramAPISecurity.test.ts +231 -0
  97. package/Tests/Server/EnvironmentConfigFrontendSecurity.test.ts +234 -0
  98. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +202 -0
  99. package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +41 -0
  100. package/Tests/Server/Infrastructure/Postgres/SessionReplayRecordEverySessionByDefaultMigration.test.ts +169 -0
  101. package/Tests/Server/Infrastructure/QueueWorkerTimeout.test.ts +210 -0
  102. package/Tests/Server/Infrastructure/TelemetryExporterDeploymentConfig.test.ts +130 -0
  103. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -1
  104. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +1163 -0
  105. package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +5 -3
  106. package/Tests/Server/Services/AddTelemetryIngestionKeyTypeMigration.test.ts +555 -0
  107. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +678 -0
  108. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +276 -28
  109. package/Tests/Server/Services/CustomFieldDropdownOptionsColumnWidth.test.ts +431 -0
  110. package/Tests/Server/Services/CustomFieldMappingService.test.ts +850 -0
  111. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +32 -0
  112. package/Tests/Server/Services/GlobalConfigService.test.ts +414 -1
  113. package/Tests/Server/Services/MetricRawEntityKeyPrune.test.ts +592 -0
  114. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +226 -1
  115. package/Tests/Server/Services/RoutineEmailSettingsPostgres.test.ts +329 -0
  116. package/Tests/Server/Services/RumSessionReplayViewService.test.ts +267 -0
  117. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +9 -0
  118. package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +9 -0
  119. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +834 -0
  120. package/Tests/Server/Services/TelemetryIngestionKeyPolicyResolution.test.ts +814 -0
  121. package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +998 -0
  122. package/Tests/Server/Services/UserNotificationSettingRollupRouting.test.ts +141 -0
  123. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +18 -10
  124. package/Tests/Server/Services/UserTelegramVerificationSecurity.test.ts +688 -0
  125. package/Tests/Server/Utils/AI/SRE/Insights/FixRouting.test.ts +497 -0
  126. package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +383 -0
  127. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +7 -3
  128. package/Tests/Server/Utils/AnalyticsDatabase/ClusterConfig.test.ts +315 -0
  129. package/Tests/Server/Utils/CustomField/CustomFieldMappingValidator.test.ts +437 -0
  130. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +300 -11
  131. package/Tests/Server/Utils/EmailRollup/EmailRollupBurstWindow.test.ts +83 -0
  132. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  133. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerPreferences.test.ts +362 -0
  134. package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +77 -1
  135. package/Tests/Server/Utils/FrontendEnvironment.test.ts +190 -0
  136. package/Tests/Server/Utils/LogRedaction.test.ts +26 -0
  137. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +23 -9
  138. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +584 -0
  139. package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestHeaderCriteria.test.ts +236 -0
  140. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +437 -0
  141. package/Tests/Server/Utils/Monitor/MonitorTemplateUtilSeriesContext.test.ts +176 -0
  142. package/Tests/Server/Utils/Monitor/SeriesContextEnricher.test.ts +356 -0
  143. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +595 -0
  144. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +198 -1
  145. package/Tests/Server/Utils/SessionReplay/SessionReplayHealthCounters.test.ts +453 -0
  146. package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1378 -0
  147. package/Tests/Server/Utils/SessionReplayOriginAllowListRefactor.test.ts +420 -0
  148. package/Tests/Server/Utils/TelegramVerificationToken.test.ts +253 -0
  149. package/Tests/Server/Utils/Telemetry/PinServiceName.test.ts +666 -0
  150. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriterCapacity.test.ts +458 -0
  151. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +424 -0
  152. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.test.ts +528 -0
  153. package/Tests/Server/Utils/TelemetryExporterEnvironment.test.ts +56 -0
  154. package/Tests/Server/Utils/VM/VMRunnerHostBridgeLatency.test.ts +74 -0
  155. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +100 -7
  156. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +1176 -6
  157. package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +220 -0
  158. package/Tests/Types/CustomField/CustomFieldValueMapping.test.ts +348 -0
  159. package/Tests/Types/JSONFunctions.test.ts +260 -0
  160. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +422 -53
  161. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +495 -15
  162. package/Tests/Types/Monitor/DockerSwarmAlertTemplates.test.ts +191 -45
  163. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +370 -42
  164. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +375 -20
  165. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +636 -42
  166. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +189 -0
  167. package/Tests/Types/Monitor/KubernetesTemplateGroupByKeys.test.ts +95 -14
  168. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +190 -21
  169. package/Tests/Types/Monitor/ProxmoxAlertTemplates.test.ts +352 -22
  170. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationAlertDebuggability.test.ts +363 -0
  171. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +93 -18
  172. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +259 -0
  173. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +476 -0
  174. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +136 -0
  175. package/Tests/Types/Monitor/SeriesContext/SeriesDebugHints.test.ts +661 -0
  176. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +507 -0
  177. package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +270 -0
  178. package/Tests/Types/Monitor/TemplateGroupByKeys.test.ts +38 -10
  179. package/Tests/Types/Monitor/Utils/RecommendationCriteriaAssertions.ts +102 -0
  180. package/Tests/Types/NotificationSetting/RoutineEmailEvents.test.ts +47 -0
  181. package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +608 -0
  182. package/Tests/Types/Rum/SessionReplayCustomEvents.test.ts +434 -0
  183. package/Tests/Types/WebsiteRequest.test.ts +285 -3
  184. package/Tests/UI/Components/Charts/ChartBucketIdentity.test.ts +16 -3
  185. package/Tests/UI/Components/Charts/ChartTrailingBucketGap.test.ts +691 -0
  186. package/Tests/UI/Components/ComponentsModal.test.tsx +12 -4
  187. package/Tests/UI/Components/ComponentsModalUsability.test.tsx +518 -0
  188. package/Tests/UI/Components/CustomFields/CustomFieldsDetailMapping.test.tsx +324 -0
  189. package/Tests/UI/Components/CustomFields/MapFromCustomFieldInput.test.tsx +176 -0
  190. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +484 -0
  191. package/Tests/UI/Components/HeaderRightRail.test.tsx +157 -0
  192. package/Tests/UI/Components/IconDropdownItem.test.tsx +102 -0
  193. package/Tests/UI/Components/JSONTablePrototypePollution.test.tsx +117 -0
  194. package/Tests/UI/Components/KeyboardShortcutsModal.test.tsx +214 -0
  195. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +561 -0
  196. package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +302 -0
  197. package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +28 -12
  198. package/Tests/UI/Components/ShortcutDialogGuard.test.tsx +74 -0
  199. package/Tests/UI/Components/StatusPage/ResourceGroupSectionAutoExpand.test.tsx +248 -0
  200. package/Tests/UI/Components/TableCellWrapping.test.tsx +625 -0
  201. package/Tests/UI/Components/TableLoadingStates.test.tsx +205 -0
  202. package/Tests/UI/Components/Workflow/NodePlacement.test.ts +148 -0
  203. package/Tests/UI/Components/Workflow/Workflow.test.tsx +981 -0
  204. package/Tests/UI/ConfigBrowserTelemetry.test.ts +70 -0
  205. package/Tests/UI/Rum/ChunkLoader.test.ts +1036 -2
  206. package/Tests/UI/Rum/FidelityNotices.test.ts +187 -0
  207. package/Tests/UI/Rum/InactivityMap.test.ts +341 -0
  208. package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +259 -0
  209. package/Tests/UI/Rum/RecordingHealthCard.test.tsx +855 -0
  210. package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +594 -0
  211. package/Tests/UI/Rum/ReplayCard.test.tsx +451 -0
  212. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +536 -0
  213. package/Tests/UI/Rum/ReplayEngine.test.ts +2503 -0
  214. package/Tests/UI/Rum/ReplayEngineTypes.test.ts +171 -0
  215. package/Tests/UI/Rum/ReplayHeader.test.tsx +711 -0
  216. package/Tests/UI/Rum/ReplayLink.test.tsx +119 -0
  217. package/Tests/UI/Rum/ReplayPinControl.test.tsx +408 -0
  218. package/Tests/UI/Rum/ReplayPlaybackIntent.test.ts +152 -0
  219. package/Tests/UI/Rum/ReplayRail.test.tsx +1296 -0
  220. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +900 -0
  221. package/Tests/UI/Rum/ReplayScrubber.test.tsx +763 -147
  222. package/Tests/UI/Rum/ReplaySignalTypes.test.ts +189 -0
  223. package/Tests/UI/Rum/ReplaySignals.test.ts +1579 -0
  224. package/Tests/UI/Rum/ReplayStage.test.tsx +396 -473
  225. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +918 -0
  226. package/Tests/UI/Rum/ReplayTimeline.test.tsx +696 -0
  227. package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +499 -0
  228. package/Tests/UI/Rum/SessionReplaySearchBar.test.tsx +346 -0
  229. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +545 -0
  230. package/Tests/UI/Rum/SessionReplayTable.test.tsx +991 -0
  231. package/Tests/UI/Rum/TargetedCapturePanel.test.tsx +254 -0
  232. package/Tests/UI/Telemetry/BrowserExporterIsolation.test.ts +71 -0
  233. package/Tests/UI/Utils/GlobalKeyboardShortcut.test.ts +344 -0
  234. package/Tests/Utils/API.test.ts +308 -0
  235. package/Tests/Utils/HTTPResponseBodyReader.test.ts +323 -0
  236. package/Tests/Utils/NetworkDiscovery/DiscoveryScanStatus.test.ts +117 -0
  237. package/Tests/Utils/Rum/ChunkMath.test.ts +83 -0
  238. package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +993 -0
  239. package/Tests/Utils/Rum/SessionReplayStringMap.test.ts +245 -0
  240. package/Tests/Utils/StatusPage/ResourceSearch.test.ts +631 -0
  241. package/Tests/Utils/Telemetry/OriginAllowList.test.ts +633 -0
  242. package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +461 -0
  243. package/Tests/Utils/ValueFormatter.test.ts +148 -0
  244. package/Types/CustomField/CustomFieldMappingCatalog.ts +161 -0
  245. package/Types/CustomField/CustomFieldMappingSourceResource.ts +19 -0
  246. package/Types/CustomField/CustomFieldValueMapping.ts +288 -0
  247. package/Types/Icon/IconProp.ts +1 -0
  248. package/Types/JSONFunctions.ts +189 -43
  249. package/Types/Monitor/CephAlertTemplates.ts +157 -195
  250. package/Types/Monitor/DockerAlertTemplates.ts +338 -128
  251. package/Types/Monitor/DockerSwarmAlertTemplates.ts +95 -112
  252. package/Types/Monitor/HostAlertTemplates.ts +302 -130
  253. package/Types/Monitor/IotAlertTemplates.ts +112 -104
  254. package/Types/Monitor/KubernetesAlertTemplates.ts +415 -224
  255. package/Types/Monitor/KubernetesMetricCatalog.ts +23 -19
  256. package/Types/Monitor/PodmanAlertTemplates.ts +116 -163
  257. package/Types/Monitor/ProxmoxAlertTemplates.ts +217 -99
  258. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +98 -6
  259. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +44 -7
  260. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +383 -0
  261. package/Types/Monitor/RumAlertTemplates.ts +46 -7
  262. package/Types/Monitor/SeriesContext/SeriesDebugHints.ts +596 -0
  263. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +554 -0
  264. package/Types/Monitor/ServiceAlertTemplates.ts +98 -11
  265. package/Types/Monitor/UptimeHistoryLabels.ts +56 -0
  266. package/Types/NotificationSetting/RoutineEmailEvents.ts +30 -0
  267. package/Types/Rum/SessionReplay.ts +209 -0
  268. package/Types/Rum/SessionReplayApi.ts +722 -0
  269. package/Types/Rum/SessionReplayCaptureTrigger.ts +21 -12
  270. package/Types/Rum/SessionReplayConsentMode.ts +11 -7
  271. package/Types/Rum/SessionReplayCustomEvents.ts +549 -0
  272. package/Types/Rum/SessionReplayHealth.ts +238 -0
  273. package/Types/Telemetry/TelemetryIngestSurface.ts +95 -0
  274. package/Types/Telemetry/TelemetryIngestionKeyPolicy.ts +75 -0
  275. package/Types/Telemetry/TelemetryIngestionKeyType.ts +29 -0
  276. package/Types/WebsiteRequest.ts +85 -3
  277. package/UI/Components/Charts/Area/AreaChart.tsx +13 -2
  278. package/UI/Components/Charts/Bar/BarChart.tsx +6 -2
  279. package/UI/Components/Charts/Line/LineChart.tsx +13 -2
  280. package/UI/Components/Charts/Types/XAxis/XAxis.ts +29 -0
  281. package/UI/Components/Charts/Utils/DataPoint.ts +22 -3
  282. package/UI/Components/Charts/Utils/TimeAnnotation.ts +76 -9
  283. package/UI/Components/Charts/Utils/XAxis.ts +217 -0
  284. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +143 -25
  285. package/UI/Components/CustomFields/MapFromCustomFieldInput.tsx +216 -0
  286. package/UI/Components/Graphs/DayUptimeGraph.tsx +173 -8
  287. package/UI/Components/Graphs/UptimeBarTooltip.tsx +22 -278
  288. package/UI/Components/Graphs/UptimeDaySummary.tsx +327 -0
  289. package/UI/Components/Header/Header.tsx +25 -9
  290. package/UI/Components/Header/IconDropdown/IconDropdownItem.tsx +10 -1
  291. package/UI/Components/Header/IconDropdown/IconDropdownMenu.tsx +6 -1
  292. package/UI/Components/Header/ProjectPicker/ProjectPicker.tsx +6 -1
  293. package/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.tsx +6 -1
  294. package/UI/Components/Icon/Icon.tsx +11 -0
  295. package/UI/Components/JSONTable/JSONTable.tsx +2 -2
  296. package/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.tsx +125 -0
  297. package/UI/Components/KeyboardShortcut/Screenshots/README.md +19 -0
  298. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-command-palette.png +0 -0
  299. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog-dark.png +0 -0
  300. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog.png +0 -0
  301. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-help-menu.png +0 -0
  302. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-narrow.png +0 -0
  303. package/UI/Components/ModelTable/Column.ts +15 -0
  304. package/UI/Components/Monitor/SeriesDebugCommandsViewer.tsx +64 -0
  305. package/UI/Components/Monitor/SeriesLabelsViewer.tsx +93 -0
  306. package/UI/Components/MonitorGraphs/Uptime.tsx +14 -2
  307. package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +63 -10
  308. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +44 -0
  309. package/UI/Components/StatusPage/ResourceGroupSection.tsx +40 -0
  310. package/UI/Components/Table/CellClassName.ts +81 -0
  311. package/UI/Components/Table/TableRow.tsx +20 -14
  312. package/UI/Components/Table/TableSkeletonRows.tsx +9 -9
  313. package/UI/Components/Table/Types/Column.ts +36 -0
  314. package/UI/Components/Tooltip/Tooltip.tsx +11 -1
  315. package/UI/Components/Workflow/ComponentsModal.tsx +60 -39
  316. package/UI/Components/Workflow/NodePlacement.ts +57 -0
  317. package/UI/Components/Workflow/Workflow.tsx +47 -18
  318. package/UI/Config.ts +15 -33
  319. package/UI/Utils/GlobalKeyboardShortcut.ts +208 -0
  320. package/UI/Utils/Telemetry/BrowserTelemetryConfig.ts +28 -0
  321. package/UI/Utils/Telemetry/Telemetry.ts +10 -8
  322. package/Utils/API.ts +129 -3
  323. package/Utils/HTTPResponseBodyReader.ts +221 -0
  324. package/Utils/NetworkDiscovery/DiscoveryScanStatus.ts +77 -0
  325. package/Utils/Rum/ChunkMath.ts +106 -0
  326. package/Utils/Rum/SessionReplayHealth.ts +732 -0
  327. package/Utils/Rum/SessionReplayStringMap.ts +226 -0
  328. package/Utils/Schema/ModelSchema.ts +1 -0
  329. package/Utils/StatusPage/ResourceSearch.ts +290 -0
  330. package/Utils/Telemetry/OriginAllowList.ts +355 -0
  331. package/Utils/Uptime/DayUptimeGraphUtil.ts +205 -0
  332. package/Utils/ValueFormatter.ts +35 -1
  333. package/build/dist/Models/AnalyticsModels/RumSession.js +102 -19
  334. package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -1
  335. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +11 -9
  336. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
  337. package/build/dist/Models/DatabaseModels/AlertCustomField.js +79 -3
  338. package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
  339. package/build/dist/Models/DatabaseModels/ApiKeyPermission.js +3 -27
  340. package/build/dist/Models/DatabaseModels/ApiKeyPermission.js.map +1 -1
  341. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +79 -3
  342. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  343. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +79 -3
  344. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -1
  345. package/build/dist/Models/DatabaseModels/MonitorCustomField.js +79 -3
  346. package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
  347. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +79 -3
  348. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
  349. package/build/dist/Models/DatabaseModels/RumApplication.js +6 -6
  350. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  351. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +79 -3
  352. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
  353. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +79 -3
  354. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
  355. package/build/dist/Models/DatabaseModels/TeamCustomField.js +79 -3
  356. package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
  357. package/build/dist/Models/DatabaseModels/TeamMember.js +0 -9
  358. package/build/dist/Models/DatabaseModels/TeamMember.js.map +1 -1
  359. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +79 -3
  360. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
  361. package/build/dist/Models/DatabaseModels/TeamPermission.js +3 -24
  362. package/build/dist/Models/DatabaseModels/TeamPermission.js.map +1 -1
  363. package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +267 -0
  364. package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
  365. package/build/dist/Models/DatabaseModels/UserTelegram.js +0 -4
  366. package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
  367. package/build/dist/Server/API/TelemetryAPI.js +769 -109
  368. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  369. package/build/dist/Server/API/UserNotificationSettingAPI.js +35 -0
  370. package/build/dist/Server/API/UserNotificationSettingAPI.js.map +1 -0
  371. package/build/dist/Server/API/UserTelegramAPI.js +28 -6
  372. package/build/dist/Server/API/UserTelegramAPI.js.map +1 -1
  373. package/build/dist/Server/EnvironmentConfig.js +29 -2
  374. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  375. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js +60 -0
  376. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js.map +1 -0
  377. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js +53 -0
  378. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js.map +1 -0
  379. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js +82 -0
  380. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js.map +1 -0
  381. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js +46 -0
  382. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js.map +1 -0
  383. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  384. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  385. package/build/dist/Server/Infrastructure/QueueWorker.js +19 -7
  386. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  387. package/build/dist/Server/Infrastructure/Semaphore.js +1 -0
  388. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  389. package/build/dist/Server/Middleware/TelemetryIngest.js +315 -8
  390. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  391. package/build/dist/Server/Services/AccessTokenService.js +1 -0
  392. package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
  393. package/build/dist/Server/Services/AlertCustomFieldService.js +93 -0
  394. package/build/dist/Server/Services/AlertCustomFieldService.js.map +1 -1
  395. package/build/dist/Server/Services/AlertService.js +34 -0
  396. package/build/dist/Server/Services/AlertService.js.map +1 -1
  397. package/build/dist/Server/Services/ApiKeyPermissionService.js +331 -28
  398. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  399. package/build/dist/Server/Services/CustomFieldMappingService.js +614 -0
  400. package/build/dist/Server/Services/CustomFieldMappingService.js.map +1 -0
  401. package/build/dist/Server/Services/GlobalConfigService.js +133 -0
  402. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  403. package/build/dist/Server/Services/IncidentCustomFieldService.js +93 -0
  404. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  405. package/build/dist/Server/Services/IncidentService.js +32 -0
  406. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  407. package/build/dist/Server/Services/MetricService.js +163 -0
  408. package/build/dist/Server/Services/MetricService.js.map +1 -1
  409. package/build/dist/Server/Services/MonitorService.js +29 -0
  410. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  411. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +79 -27
  412. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  413. package/build/dist/Server/Services/ProjectService.js +22 -0
  414. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  415. package/build/dist/Server/Services/RoutineEmailSettingsService.js +69 -0
  416. package/build/dist/Server/Services/RoutineEmailSettingsService.js.map +1 -0
  417. package/build/dist/Server/Services/RumSessionReplayViewService.js +86 -24
  418. package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -1
  419. package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js +93 -0
  420. package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js.map +1 -1
  421. package/build/dist/Server/Services/ScheduledMaintenanceService.js +29 -0
  422. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  423. package/build/dist/Server/Services/TeamMemberService.js +62 -2
  424. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  425. package/build/dist/Server/Services/TeamPermissionService.js +166 -3
  426. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  427. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +562 -18
  428. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  429. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -6
  430. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  431. package/build/dist/Server/Services/UserTelegramService.js +257 -4
  432. package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
  433. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  434. package/build/dist/Server/Utils/APIKey/AccessPermission.js +2 -2
  435. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  436. package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js +27 -0
  437. package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js.map +1 -0
  438. package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js +226 -0
  439. package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js.map +1 -0
  440. package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js +189 -0
  441. package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js.map +1 -0
  442. package/build/dist/Server/Utils/DataSource/EgressGuard.js +121 -7
  443. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  444. package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js +38 -5
  445. package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js.map +1 -1
  446. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +44 -2
  447. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  448. package/build/dist/Server/Utils/FrontendEnvironment.js +33 -0
  449. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -0
  450. package/build/dist/Server/Utils/LogRedaction.js +20 -0
  451. package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
  452. package/build/dist/Server/Utils/Logger.js +10 -0
  453. package/build/dist/Server/Utils/Logger.js.map +1 -1
  454. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +287 -68
  455. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  456. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +20 -4
  457. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  458. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +29 -6
  459. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  460. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +88 -13
  461. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  462. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  463. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  464. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -0
  465. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  466. package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js +237 -0
  467. package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js.map +1 -0
  468. package/build/dist/Server/Utils/SSRFProtection.js +136 -9
  469. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  470. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +174 -89
  471. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  472. package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js +223 -0
  473. package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js.map +1 -0
  474. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +945 -164
  475. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  476. package/build/dist/Server/Utils/StartServer.js +3 -14
  477. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  478. package/build/dist/Server/Utils/TelegramVerificationToken.js +129 -0
  479. package/build/dist/Server/Utils/TelegramVerificationToken.js.map +1 -0
  480. package/build/dist/Server/Utils/Telemetry/PinServiceName.js +163 -0
  481. package/build/dist/Server/Utils/Telemetry/PinServiceName.js.map +1 -0
  482. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +28 -15
  483. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
  484. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +64 -0
  485. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -0
  486. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js +171 -0
  487. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js.map +1 -0
  488. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  489. package/build/dist/Server/Utils/VM/VMRunner.js +780 -69
  490. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  491. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +60 -0
  492. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -0
  493. package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js +20 -0
  494. package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js.map +1 -0
  495. package/build/dist/Types/CustomField/CustomFieldValueMapping.js +141 -0
  496. package/build/dist/Types/CustomField/CustomFieldValueMapping.js.map +1 -0
  497. package/build/dist/Types/Icon/IconProp.js +1 -0
  498. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  499. package/build/dist/Types/JSONFunctions.js +118 -37
  500. package/build/dist/Types/JSONFunctions.js.map +1 -1
  501. package/build/dist/Types/Monitor/CephAlertTemplates.js +130 -171
  502. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  503. package/build/dist/Types/Monitor/DockerAlertTemplates.js +286 -121
  504. package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -1
  505. package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js +84 -105
  506. package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js.map +1 -1
  507. package/build/dist/Types/Monitor/HostAlertTemplates.js +250 -122
  508. package/build/dist/Types/Monitor/HostAlertTemplates.js.map +1 -1
  509. package/build/dist/Types/Monitor/IotAlertTemplates.js +81 -81
  510. package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -1
  511. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +375 -215
  512. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  513. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +19 -19
  514. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  515. package/build/dist/Types/Monitor/PodmanAlertTemplates.js +104 -153
  516. package/build/dist/Types/Monitor/PodmanAlertTemplates.js.map +1 -1
  517. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +156 -91
  518. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  519. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +67 -6
  520. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  521. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +38 -7
  522. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  523. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +261 -0
  524. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -0
  525. package/build/dist/Types/Monitor/RumAlertTemplates.js +46 -7
  526. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  527. package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js +422 -0
  528. package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js.map +1 -0
  529. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +441 -0
  530. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -0
  531. package/build/dist/Types/Monitor/ServiceAlertTemplates.js +79 -15
  532. package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -1
  533. package/build/dist/Types/Monitor/UptimeHistoryLabels.js +17 -0
  534. package/build/dist/Types/Monitor/UptimeHistoryLabels.js.map +1 -0
  535. package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js +29 -0
  536. package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js.map +1 -0
  537. package/build/dist/Types/Rum/SessionReplay.js +117 -0
  538. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  539. package/build/dist/Types/Rum/SessionReplayApi.js +181 -0
  540. package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -0
  541. package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +21 -12
  542. package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -1
  543. package/build/dist/Types/Rum/SessionReplayConsentMode.js +11 -7
  544. package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -1
  545. package/build/dist/Types/Rum/SessionReplayCustomEvents.js +172 -0
  546. package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -0
  547. package/build/dist/Types/Rum/SessionReplayHealth.js +37 -0
  548. package/build/dist/Types/Rum/SessionReplayHealth.js.map +1 -0
  549. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +85 -0
  550. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -0
  551. package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js +15 -0
  552. package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js.map +1 -0
  553. package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js +30 -0
  554. package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js.map +1 -0
  555. package/build/dist/Types/WebsiteRequest.js +55 -3
  556. package/build/dist/Types/WebsiteRequest.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -2
  558. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/Bar/BarChart.js +6 -2
  560. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/Line/LineChart.js +13 -2
  562. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  563. package/build/dist/UI/Components/Charts/Types/XAxis/XAxis.js.map +1 -1
  564. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -3
  565. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  566. package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +40 -9
  567. package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -1
  568. package/build/dist/UI/Components/Charts/Utils/XAxis.js +186 -0
  569. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  570. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +80 -6
  571. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  572. package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js +118 -0
  573. package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js.map +1 -0
  574. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +112 -7
  575. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  576. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -162
  577. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
  578. package/build/dist/UI/Components/Graphs/UptimeDaySummary.js +181 -0
  579. package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -0
  580. package/build/dist/UI/Components/Header/Header.js +2 -3
  581. package/build/dist/UI/Components/Header/Header.js.map +1 -1
  582. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js +2 -1
  583. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js.map +1 -1
  584. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js +7 -1
  585. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js.map +1 -1
  586. package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js +7 -1
  587. package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js.map +1 -1
  588. package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js +7 -1
  589. package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js.map +1 -1
  590. package/build/dist/UI/Components/Icon/Icon.js +5 -0
  591. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  592. package/build/dist/UI/Components/JSONTable/JSONTable.js +2 -2
  593. package/build/dist/UI/Components/JSONTable/JSONTable.js.map +1 -1
  594. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js +35 -0
  595. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js.map +1 -0
  596. package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js +34 -0
  597. package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js.map +1 -0
  598. package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js +49 -0
  599. package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js.map +1 -0
  600. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +1 -1
  601. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  602. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +32 -6
  603. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
  604. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +38 -0
  605. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  606. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +22 -1
  607. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -1
  608. package/build/dist/UI/Components/Table/CellClassName.js +55 -0
  609. package/build/dist/UI/Components/Table/CellClassName.js.map +1 -0
  610. package/build/dist/UI/Components/Table/TableRow.js +16 -13
  611. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  612. package/build/dist/UI/Components/Table/TableSkeletonRows.js +9 -8
  613. package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -1
  614. package/build/dist/UI/Components/Tooltip/Tooltip.js +25 -1
  615. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  616. package/build/dist/UI/Components/Workflow/ComponentsModal.js +47 -30
  617. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  618. package/build/dist/UI/Components/Workflow/NodePlacement.js +37 -0
  619. package/build/dist/UI/Components/Workflow/NodePlacement.js.map +1 -0
  620. package/build/dist/UI/Components/Workflow/Workflow.js +35 -18
  621. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  622. package/build/dist/UI/Config.js +10 -20
  623. package/build/dist/UI/Config.js.map +1 -1
  624. package/build/dist/UI/Utils/GlobalKeyboardShortcut.js +138 -0
  625. package/build/dist/UI/Utils/GlobalKeyboardShortcut.js.map +1 -0
  626. package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js +14 -0
  627. package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js.map +1 -0
  628. package/build/dist/UI/Utils/Telemetry/Telemetry.js +7 -5
  629. package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
  630. package/build/dist/Utils/API.js +67 -5
  631. package/build/dist/Utils/API.js.map +1 -1
  632. package/build/dist/Utils/HTTPResponseBodyReader.js +157 -0
  633. package/build/dist/Utils/HTTPResponseBodyReader.js.map +1 -0
  634. package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js +69 -0
  635. package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js.map +1 -0
  636. package/build/dist/Utils/Rum/ChunkMath.js +76 -0
  637. package/build/dist/Utils/Rum/ChunkMath.js.map +1 -1
  638. package/build/dist/Utils/Rum/SessionReplayHealth.js +534 -0
  639. package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -0
  640. package/build/dist/Utils/Rum/SessionReplayStringMap.js +165 -0
  641. package/build/dist/Utils/Rum/SessionReplayStringMap.js.map +1 -0
  642. package/build/dist/Utils/Schema/ModelSchema.js +1 -0
  643. package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
  644. package/build/dist/Utils/StatusPage/ResourceSearch.js +200 -0
  645. package/build/dist/Utils/StatusPage/ResourceSearch.js.map +1 -0
  646. package/build/dist/Utils/Telemetry/OriginAllowList.js +299 -0
  647. package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -0
  648. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +147 -0
  649. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -0
  650. package/build/dist/Utils/ValueFormatter.js +32 -1
  651. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  652. package/package.json +1 -1
@@ -0,0 +1,1296 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ act,
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ waitFor,
8
+ within,
9
+ } from "@testing-library/react";
10
+ /*
11
+ * The Dashboard has its own copy of react, so a component imported from there
12
+ * would otherwise call hooks on a DIFFERENT React instance than the one
13
+ * react-dom renders with. Common's jest moduleNameMapper pins react,
14
+ * react-dom and react-router-dom to this project's single copy for every
15
+ * importer; see the note at the top of ReplayStage.test.tsx.
16
+ */
17
+ import * as React from "react";
18
+ import { MemoryRouter } from "react-router-dom";
19
+ import { describe, expect, it, jest } from "@jest/globals";
20
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
21
+ import ObjectID from "../../../Types/ObjectID";
22
+ import ExceptionInstance from "../../../Models/AnalyticsModels/ExceptionInstance";
23
+ import Log from "../../../Models/AnalyticsModels/Log";
24
+ import Span from "../../../Models/AnalyticsModels/Span";
25
+ import AnalyticsBaseModel from "../../../Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
26
+ import ListResult from "../../../Types/BaseDatabase/ListResult";
27
+ import LogSeverity from "../../../Types/Log/LogSeverity";
28
+ import ReplayRail, {
29
+ ReplayRailHandle,
30
+ ReplayRailProps,
31
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/Rail/ReplayRail";
32
+ import {
33
+ ReplayBackendListRequest,
34
+ ReplayBackendSignalsStore,
35
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/Rail/ReplayBackendSignals";
36
+ import {
37
+ ReplayRailTabId,
38
+ ReplaySignal,
39
+ ReplaySignalKind,
40
+ ReplaySignalSeverity,
41
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/Rail/ReplaySignalTypes";
42
+ import {
43
+ REPLAY_CLICK_EVENTS_CAPABILITY,
44
+ getRailEmptyCopy,
45
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/Rail/ReplayRailEmptyCopy";
46
+ import { SESSION_REPLAY_RECORDER_CAPABILITIES } from "../../../Types/Rum/SessionReplay";
47
+ import {
48
+ buildRailTabModels,
49
+ computeRailWindow,
50
+ groupRepeatedSignals,
51
+ stepRailRow,
52
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/Rail/ReplayRailTabs";
53
+
54
+ /*
55
+ * The synced rail. What is pinned here is the contract the player and the
56
+ * E2E suite rely on: the tabs never claim a telemetry count before a fetch,
57
+ * the "now" divider sits between past and future rows, a row click seeks
58
+ * one second early AND leaves the clicked row active (scrubber-devtools-5),
59
+ * following yields to the viewer's scroll and comes back on the chip
60
+ * (scrubber-devtools-6), a locked tab names the permission, and long lists
61
+ * mount only a window of rows.
62
+ */
63
+
64
+ const START_UNIX_MS: number = 1_725_000_000_000;
65
+ const SESSION_ID: string = "0123456789abcdef0123456789abcdef";
66
+ const TRACE_ID: string = "4bf92f3577b34da6a3ce929d0e0e4736";
67
+
68
+ let ordinal: number = 0;
69
+
70
+ function makeSignal(
71
+ kind: ReplaySignalKind,
72
+ offsetMs: number,
73
+ overrides?: Partial<ReplaySignal>,
74
+ ): ReplaySignal {
75
+ ordinal++;
76
+
77
+ const severity: ReplaySignalSeverity =
78
+ kind === "client-error" || kind === "server-error" ? "error" : "info";
79
+ const detailByKind: Record<string, Record<string, unknown>> = {
80
+ console: {
81
+ level: "log",
82
+ message: `console line ${ordinal}`,
83
+ atUnixMs: null,
84
+ },
85
+ network: {
86
+ method: "GET",
87
+ url: `https://api.example.com/items/${ordinal}`,
88
+ origin: "https://api.example.com",
89
+ path: `/items/${ordinal}`,
90
+ status: 200,
91
+ durationMs: 120,
92
+ responseBytes: 512,
93
+ requestBytes: null,
94
+ initiator: "fetch",
95
+ traceId: null,
96
+ isError: false,
97
+ failedBeforeResponse: false,
98
+ isSlow: false,
99
+ atUnixMs: null,
100
+ },
101
+ navigation: {
102
+ from: "/cart",
103
+ to: "/checkout",
104
+ kind: "pushState",
105
+ viewportWidth: null,
106
+ viewportHeight: null,
107
+ atUnixMs: null,
108
+ },
109
+ "client-error": {
110
+ kind: "error",
111
+ message: `TypeError ${ordinal}`,
112
+ source: "app.js",
113
+ lineNumber: 12,
114
+ columnNumber: 5,
115
+ stack: "TypeError: boom\n at app.js:12:5",
116
+ location: "app.js:12:5",
117
+ atUnixMs: null,
118
+ },
119
+ interaction: {
120
+ selector: "button.pay",
121
+ text: "Pay now",
122
+ x: 100,
123
+ y: 200,
124
+ isCoordinateOnly: false,
125
+ atUnixMs: null,
126
+ },
127
+ };
128
+
129
+ return {
130
+ id: `rec:${Math.floor(offsetMs / 15000)}:${ordinal}`,
131
+ kind: kind,
132
+ source: "recording",
133
+ offsetMs: offsetMs,
134
+ severity: severity,
135
+ title: `${kind} at ${offsetMs}`,
136
+ chunkIndex: Math.floor(offsetMs / 15000),
137
+ links: {},
138
+ detail: detailByKind[kind] || {},
139
+ alignment: "exact",
140
+ ...overrides,
141
+ };
142
+ }
143
+
144
+ function defaultSignals(): Array<ReplaySignal> {
145
+ return [
146
+ makeSignal("network", 2000, {
147
+ id: "rec:0:1",
148
+ title: "POST 500 /api/orders",
149
+ severity: "error",
150
+ links: { traceId: TRACE_ID },
151
+ detail: {
152
+ method: "POST",
153
+ url: "https://api.example.com/api/orders",
154
+ origin: "https://api.example.com",
155
+ path: "/api/orders",
156
+ status: 500,
157
+ durationMs: 220,
158
+ responseBytes: 1200,
159
+ requestBytes: null,
160
+ initiator: "fetch",
161
+ traceId: TRACE_ID,
162
+ isError: true,
163
+ failedBeforeResponse: false,
164
+ isSlow: false,
165
+ atUnixMs: null,
166
+ },
167
+ }),
168
+ makeSignal("console", 2500, { id: "rec:0:2", title: "order save failed" }),
169
+ makeSignal("navigation", 4000, {
170
+ id: "rec:0:3",
171
+ title: "/cart → /checkout",
172
+ }),
173
+ makeSignal("client-error", 9000, {
174
+ id: "rec:0:4",
175
+ title: "TypeError: cannot read total",
176
+ }),
177
+ ];
178
+ }
179
+
180
+ interface RenderResult {
181
+ seeks: Array<number>;
182
+ selections: Array<string | null>;
183
+ follows: Array<boolean>;
184
+ tabs: Array<ReplayRailTabId>;
185
+ queries: Array<string>;
186
+ handle: React.RefObject<ReplayRailHandle>;
187
+ rerender: (overrides: Partial<ReplayRailProps>) => void;
188
+ }
189
+
190
+ function renderRail(overrides?: Partial<ReplayRailProps>): RenderResult {
191
+ const seeks: Array<number> = [];
192
+ const selections: Array<string | null> = [];
193
+ const follows: Array<boolean> = [];
194
+ const tabs: Array<ReplayRailTabId> = [];
195
+ const queries: Array<string> = [];
196
+ const handle: React.RefObject<ReplayRailHandle> =
197
+ React.createRef<ReplayRailHandle>();
198
+
199
+ const baseProps: ReplayRailProps = {
200
+ signals: defaultSignals(),
201
+ sessionId: SESSION_ID,
202
+ startTimeUnixMs: START_UNIX_MS,
203
+ isFinalized: true,
204
+ isExpiredFootage: false,
205
+ currentTimeMs: 3000,
206
+ isPlaying: false,
207
+ selectedSignalId: null,
208
+ onSeek: (offsetMs: number): void => {
209
+ seeks.push(offsetMs);
210
+ },
211
+ onSelectSignal: (signalId: string | null): void => {
212
+ selections.push(signalId);
213
+ },
214
+ onFollowChange: (follow: boolean): void => {
215
+ follows.push(follow);
216
+ },
217
+ onTabChange: (tabId: ReplayRailTabId): void => {
218
+ tabs.push(tabId);
219
+ },
220
+ onQueryChange: (query: string): void => {
221
+ queries.push(query);
222
+ },
223
+ loadedChunkCount: 1,
224
+ totalChunkCount: 1,
225
+ };
226
+
227
+ const view: ReturnType<typeof render> = render(
228
+ <MemoryRouter>
229
+ <ReplayRail ref={handle} {...baseProps} {...overrides} />
230
+ </MemoryRouter>,
231
+ );
232
+
233
+ return {
234
+ seeks: seeks,
235
+ selections: selections,
236
+ follows: follows,
237
+ tabs: tabs,
238
+ queries: queries,
239
+ handle: handle,
240
+ rerender: (next: Partial<ReplayRailProps>): void => {
241
+ view.rerender(
242
+ <MemoryRouter>
243
+ <ReplayRail ref={handle} {...baseProps} {...overrides} {...next} />
244
+ </MemoryRouter>,
245
+ );
246
+ },
247
+ };
248
+ }
249
+
250
+ function rows(): Array<HTMLElement> {
251
+ return screen.queryAllByTestId("rail-row");
252
+ }
253
+
254
+ function rowTitles(): Array<string> {
255
+ return rows().map((row: HTMLElement): string => {
256
+ return row.getAttribute("data-signal-id") || "";
257
+ });
258
+ }
259
+
260
+ function listResult<T extends AnalyticsBaseModel>(
261
+ data: Array<T>,
262
+ ): ListResult<T> {
263
+ return { data: data, count: data.length, skip: 0, limit: 500 };
264
+ }
265
+
266
+ function makeStore(options: {
267
+ logs?: Array<Log>;
268
+ spans?: Array<Span>;
269
+ exceptions?: Array<ExceptionInstance>;
270
+ reject?: Partial<Record<"log" | "span" | "exception", unknown>>;
271
+ }): {
272
+ store: ReplayBackendSignalsStore;
273
+ calls: Array<string>;
274
+ } {
275
+ const calls: Array<string> = [];
276
+
277
+ const fetchList: <T extends AnalyticsBaseModel>(
278
+ request: ReplayBackendListRequest<T>,
279
+ ) => Promise<ListResult<T>> = async <T extends AnalyticsBaseModel>(
280
+ request: ReplayBackendListRequest<T>,
281
+ ): Promise<ListResult<T>> => {
282
+ const modelType: unknown = request.modelType;
283
+ const kind: "log" | "span" | "exception" =
284
+ modelType === Log ? "log" : modelType === Span ? "span" : "exception";
285
+
286
+ calls.push(kind);
287
+
288
+ if (options.reject && options.reject[kind] !== undefined) {
289
+ throw options.reject[kind];
290
+ }
291
+
292
+ const data: Array<AnalyticsBaseModel> =
293
+ kind === "log"
294
+ ? options.logs || []
295
+ : kind === "span"
296
+ ? options.spans || []
297
+ : options.exceptions || [];
298
+
299
+ return listResult(data as Array<T>);
300
+ };
301
+
302
+ return {
303
+ store: new ReplayBackendSignalsStore({
304
+ sessionId: SESSION_ID,
305
+ startTimeUnixMs: START_UNIX_MS,
306
+ endTimeUnixMs: START_UNIX_MS + 60_000,
307
+ isFinalized: true,
308
+ fetchList: fetchList,
309
+ }),
310
+ calls: calls,
311
+ };
312
+ }
313
+
314
+ function makeLog(id: string, atMs: number, body: string): Log {
315
+ const log: Log = new Log();
316
+
317
+ log.id = new ObjectID(id);
318
+ log.time = new Date(START_UNIX_MS + atMs);
319
+ log.body = body;
320
+ log.severityText = LogSeverity.Error;
321
+ log.traceId = TRACE_ID;
322
+
323
+ return log;
324
+ }
325
+
326
+ describe("ReplayRail tabs and counts", () => {
327
+ it("counts recording rows per tab and never claims a telemetry count before a fetch", () => {
328
+ renderRail();
329
+
330
+ expect(screen.getByTestId("rail-tab-all")).toHaveTextContent("All4");
331
+ expect(screen.getByTestId("rail-tab-network")).toHaveTextContent(
332
+ "Network1",
333
+ );
334
+ expect(screen.getByTestId("rail-tab-console")).toHaveTextContent(
335
+ "Console1",
336
+ );
337
+ /* The client error is real; the server half is unknown, so the count is the client's. */
338
+ expect(screen.getByTestId("rail-tab-errors")).toHaveTextContent("Errors1");
339
+ /* Logs and Traces have no number at all - not "0". */
340
+ expect(screen.getByTestId("rail-tab-logs")).toHaveTextContent(/^Logs$/);
341
+ expect(screen.getByTestId("rail-tab-traces")).toHaveTextContent(/^Traces$/);
342
+ });
343
+
344
+ it("keeps Errors count-less before the exception fetch when the recording has no client error", () => {
345
+ renderRail({
346
+ signals: [makeSignal("network", 2000), makeSignal("console", 2500)],
347
+ });
348
+
349
+ expect(screen.getByTestId("rail-tab-errors")).toHaveTextContent(/^Errors$/);
350
+ });
351
+
352
+ it("shows matching/total on the tab badges while a search is active", () => {
353
+ renderRail({ query: "status:>=400" });
354
+
355
+ expect(screen.getByTestId("rail-tab-network")).toHaveTextContent(
356
+ "Network1/1",
357
+ );
358
+ expect(screen.getByTestId("rail-tab-console")).toHaveTextContent(
359
+ "Console0/1",
360
+ );
361
+ expect(screen.getByTestId("rail-tab-all")).toHaveTextContent("All1/4");
362
+ });
363
+
364
+ it("switches tabs with role=tab buttons and reports the change", () => {
365
+ const result: RenderResult = renderRail();
366
+
367
+ fireEvent.click(screen.getByTestId("rail-tab-network"));
368
+
369
+ expect(result.tabs).toEqual(["network"]);
370
+ expect(rowTitles()).toEqual(["rec:0:1"]);
371
+ expect(screen.getByTestId("rail-tab-network")).toHaveAttribute(
372
+ "aria-selected",
373
+ "true",
374
+ );
375
+ });
376
+
377
+ /*
378
+ * ux-01: the strip is a roving-tabindex tablist, so Tab reaches only the
379
+ * selected tab. Without an arrow handler the other eight tabs could not
380
+ * be reached from the keyboard at all - and the player's global map read
381
+ * the arrows as +-5s seeks.
382
+ */
383
+ it("moves between tabs with the arrow keys, carrying focus", () => {
384
+ const result: RenderResult = renderRail();
385
+ const tablist: HTMLElement = screen.getByTestId("rail-tablist");
386
+
387
+ fireEvent.keyDown(tablist, { key: "ArrowRight" });
388
+
389
+ expect(result.tabs).toEqual(["console"]);
390
+ expect(screen.getByTestId("rail-tab-console")).toHaveFocus();
391
+
392
+ result.rerender({ activeTab: "console" });
393
+ fireEvent.keyDown(tablist, { key: "ArrowLeft" });
394
+
395
+ expect(result.tabs).toEqual(["console", "all"]);
396
+ expect(screen.getByTestId("rail-tab-all")).toHaveFocus();
397
+ });
398
+
399
+ it("wraps at the ends and jumps with Home and End", () => {
400
+ const result: RenderResult = renderRail();
401
+ const tablist: HTMLElement = screen.getByTestId("rail-tablist");
402
+
403
+ /* "all" is first: ArrowLeft wraps to the last tab. */
404
+ fireEvent.keyDown(tablist, { key: "ArrowLeft" });
405
+
406
+ expect(result.tabs).toEqual(["traces"]);
407
+
408
+ result.rerender({ activeTab: "traces" });
409
+ fireEvent.keyDown(tablist, { key: "Home" });
410
+
411
+ expect(result.tabs).toEqual(["traces", "all"]);
412
+
413
+ fireEvent.keyDown(tablist, { key: "End" });
414
+
415
+ expect(result.tabs).toEqual(["traces", "all", "traces"]);
416
+ });
417
+
418
+ it("keeps the arrow keys away from the player's seek shortcuts", () => {
419
+ const result: RenderResult = renderRail();
420
+
421
+ /* fireEvent returns false when the handler called preventDefault. */
422
+ const wasNotPrevented: boolean = fireEvent.keyDown(
423
+ screen.getByTestId("rail-tablist"),
424
+ { key: "ArrowRight" },
425
+ );
426
+
427
+ expect(wasNotPrevented).toBe(false);
428
+ expect(result.seeks).toEqual([]);
429
+ });
430
+
431
+ it("renders the coverage note while chunks are still loading and hides it once all are in", () => {
432
+ const result: RenderResult = renderRail({
433
+ loadedChunkCount: 2,
434
+ totalChunkCount: 9,
435
+ });
436
+
437
+ expect(screen.getByTestId("rail-coverage-note")).toHaveTextContent(
438
+ "Recording rows come from 2 of 9 segments loaded so far",
439
+ );
440
+
441
+ result.rerender({ loadedChunkCount: 9, totalChunkCount: 9 });
442
+
443
+ expect(screen.queryByTestId("rail-coverage-note")).not.toBeInTheDocument();
444
+ });
445
+ });
446
+
447
+ describe("ReplayRail playhead sync", () => {
448
+ it("places the now divider between past and future rows and dims the future ones", () => {
449
+ renderRail({ currentTimeMs: 3000 });
450
+
451
+ const list: HTMLElement = screen.getByTestId("rail-list");
452
+ const children: Array<Element> = Array.from(list.children);
453
+ const dividerIndex: number = children.findIndex(
454
+ (child: Element): boolean => {
455
+ return child.getAttribute("data-testid") === "rail-now-divider";
456
+ },
457
+ );
458
+
459
+ /* Two rows are at or before 3000ms (2000, 2500); the divider follows them. */
460
+ expect(dividerIndex).toBe(2);
461
+ expect(screen.getByTestId("rail-now-divider")).toHaveTextContent(
462
+ "now 0:03.0",
463
+ );
464
+
465
+ const allRows: Array<HTMLElement> = rows();
466
+
467
+ expect(allRows[0]).toHaveAttribute("data-future", "false");
468
+ expect(allRows[1]).toHaveAttribute("data-future", "false");
469
+ expect(allRows[2]).toHaveAttribute("data-future", "true");
470
+ expect(allRows[3]).toHaveAttribute("data-future", "true");
471
+ });
472
+
473
+ it("marks the last row the playhead passed as active with aria-current", () => {
474
+ renderRail({ currentTimeMs: 3000 });
475
+
476
+ const active: HTMLElement = screen.getByTestId("rail-row-active");
477
+ const activeRow: HTMLElement = active.closest(
478
+ "[data-testid='rail-row']",
479
+ ) as HTMLElement;
480
+
481
+ expect(activeRow).toHaveAttribute("data-signal-id", "rec:0:2");
482
+ expect(activeRow).toHaveAttribute("data-active", "true");
483
+ expect(activeRow).toHaveAttribute("role", "listitem");
484
+ expect(screen.getByTestId("rail-list")).toHaveAttribute("role", "list");
485
+
486
+ /*
487
+ * ux-13: the row's clickable surface is a real focusable button that
488
+ * carries aria-current, not a bare div inside a role=option whose
489
+ * children ARIA would have hidden.
490
+ */
491
+ const body: HTMLElement = within(activeRow).getByRole("button", {
492
+ name: /TypeError|console|request|navigation|click/i,
493
+ });
494
+
495
+ expect(body.tagName).toBe("BUTTON");
496
+ expect(body).toHaveAttribute("aria-current", "true");
497
+ });
498
+
499
+ it("puts the divider before the first row when nothing has happened yet", () => {
500
+ renderRail({ currentTimeMs: 0 });
501
+
502
+ const list: HTMLElement = screen.getByTestId("rail-list");
503
+
504
+ expect(list.children[0]).toHaveAttribute("data-testid", "rail-now-divider");
505
+ expect(screen.queryByTestId("rail-row-active")).not.toBeInTheDocument();
506
+ });
507
+ });
508
+
509
+ describe("ReplayRail row click (scrubber-devtools-5)", () => {
510
+ it("seeks one second before the row and selects it", () => {
511
+ const result: RenderResult = renderRail();
512
+
513
+ fireEvent.click(within(rows()[3] as HTMLElement).getByText(/TypeError/));
514
+
515
+ expect(result.seeks).toEqual([8000]);
516
+ expect(result.selections).toEqual(["rec:0:4"]);
517
+ });
518
+
519
+ it("keeps the clicked row active while the playhead sits in its pre-roll window", () => {
520
+ const result: RenderResult = renderRail();
521
+
522
+ fireEvent.click(within(rows()[3] as HTMLElement).getByText(/TypeError/));
523
+
524
+ /* The player applies the seek and the selection. */
525
+ result.rerender({ currentTimeMs: 8000, selectedSignalId: "rec:0:4" });
526
+
527
+ const activeRow: HTMLElement = screen
528
+ .getByTestId("rail-row-active")
529
+ .closest("[data-testid='rail-row']") as HTMLElement;
530
+
531
+ expect(activeRow).toHaveAttribute("data-signal-id", "rec:0:4");
532
+ expect(activeRow).toHaveAttribute("data-selected", "true");
533
+ expect(activeRow).toHaveAttribute("data-future", "false");
534
+ /* The row before it is NOT the active one, which is what the old rule did. */
535
+ expect(rows()[2]).toHaveAttribute("data-active", "false");
536
+ });
537
+
538
+ it("expands the detail under the selected row and closes it from the detail", () => {
539
+ const result: RenderResult = renderRail({ selectedSignalId: "rec:0:4" });
540
+
541
+ const detail: HTMLElement = screen.getByTestId("rail-detail");
542
+
543
+ expect(detail).toHaveAttribute("data-signal-kind", "client-error");
544
+ expect(detail).toHaveTextContent("app.js:12:5");
545
+
546
+ fireEvent.click(screen.getByLabelText("Close detail"));
547
+
548
+ expect(result.selections).toEqual([null]);
549
+ });
550
+
551
+ it("keeps the trace link a sibling of the seek button, never nested inside it", () => {
552
+ renderRail();
553
+
554
+ const row: HTMLElement = rows()[0] as HTMLElement;
555
+ const link: HTMLElement = within(row).getByText("trace");
556
+ const seek: HTMLElement = within(row).getByLabelText(/^Seek to/);
557
+
558
+ expect(link.closest("a")).not.toBeNull();
559
+ expect(seek.tagName.toLowerCase()).toBe("button");
560
+ expect(seek.contains(link)).toBe(false);
561
+ expect(link.closest("button")).toBeNull();
562
+ expect(link.closest("a")?.parentElement).toBe(seek.parentElement);
563
+ });
564
+
565
+ it("the seek hover action seeks without changing the selection", () => {
566
+ const result: RenderResult = renderRail();
567
+
568
+ fireEvent.click(
569
+ within(rows()[1] as HTMLElement).getByLabelText(/^Seek to/),
570
+ );
571
+
572
+ expect(result.seeks).toEqual([1500]);
573
+ expect(result.selections).toEqual([]);
574
+ });
575
+ });
576
+
577
+ describe("ReplayRail follow (scrubber-devtools-6)", () => {
578
+ it("turns follow off on a wheel inside the list and offers to resume", () => {
579
+ const result: RenderResult = renderRail();
580
+
581
+ expect(screen.queryByTestId("rail-resume-follow")).not.toBeInTheDocument();
582
+
583
+ fireEvent.wheel(screen.getByTestId("rail-list"), { deltaY: 40 });
584
+
585
+ expect(result.follows).toEqual([false]);
586
+ expect(screen.getByTestId("rail-resume-follow")).toBeInTheDocument();
587
+
588
+ fireEvent.click(screen.getByTestId("rail-resume-follow"));
589
+
590
+ expect(result.follows).toEqual([false, true]);
591
+ expect(screen.queryByTestId("rail-resume-follow")).not.toBeInTheDocument();
592
+ });
593
+
594
+ it("re-anchors the divider after a seek while paused, not only while playing", () => {
595
+ const result: RenderResult = renderRail({
596
+ currentTimeMs: 0,
597
+ isPlaying: false,
598
+ });
599
+ const list: HTMLElement = screen.getByTestId("rail-list");
600
+
601
+ Object.defineProperty(list, "clientHeight", {
602
+ configurable: true,
603
+ value: 100,
604
+ });
605
+
606
+ let scrollTop: number = 0;
607
+
608
+ Object.defineProperty(list, "scrollTop", {
609
+ configurable: true,
610
+ get: (): number => {
611
+ return scrollTop;
612
+ },
613
+ set: (value: number): void => {
614
+ scrollTop = value;
615
+ },
616
+ });
617
+
618
+ const divider: HTMLElement = screen.getByTestId("rail-now-divider");
619
+
620
+ Object.defineProperty(divider, "offsetTop", {
621
+ configurable: true,
622
+ value: 500,
623
+ });
624
+
625
+ /* A marker click while paused moves the playhead past three rows. */
626
+ result.rerender({ currentTimeMs: 5000, isPlaying: false });
627
+
628
+ /* offsetTop (500) - 40% of the list height (40) = 460. */
629
+ expect(scrollTop).toBe(460);
630
+ });
631
+
632
+ it("offers Jump to now when following is off and the divider is off-screen", () => {
633
+ renderRail({ follow: false, currentTimeMs: 3000 });
634
+
635
+ const list: HTMLElement = screen.getByTestId("rail-list");
636
+ const divider: HTMLElement = screen.getByTestId("rail-now-divider");
637
+
638
+ Object.defineProperty(list, "clientHeight", {
639
+ configurable: true,
640
+ value: 100,
641
+ });
642
+ Object.defineProperty(list, "scrollTop", {
643
+ configurable: true,
644
+ value: 900,
645
+ writable: true,
646
+ });
647
+ Object.defineProperty(divider, "offsetTop", {
648
+ configurable: true,
649
+ value: 50,
650
+ });
651
+
652
+ fireEvent.scroll(list);
653
+
654
+ expect(screen.getByTestId("rail-jump-to-now")).toBeInTheDocument();
655
+ /* Resume is offered too, but Jump to now must not turn following on. */
656
+ expect(screen.getByTestId("rail-resume-follow")).toBeInTheDocument();
657
+ });
658
+ });
659
+
660
+ describe("ReplayRail keyboard and stepping", () => {
661
+ it("] and [ step through the rows of the current tab, seeking and selecting", () => {
662
+ const result: RenderResult = renderRail({ currentTimeMs: 3000 });
663
+
664
+ /* The playhead is on row 1 (2500ms); next is the navigation at 4000. */
665
+ fireEvent.keyDown(screen.getByTestId("rail-list"), { key: "]" });
666
+
667
+ expect(result.seeks).toEqual([3000]);
668
+ expect(result.selections).toEqual(["rec:0:3"]);
669
+
670
+ /* From the selected row, previous is the console row at 2500. */
671
+ result.rerender({ currentTimeMs: 3000, selectedSignalId: "rec:0:3" });
672
+ fireEvent.keyDown(screen.getByTestId("rail-list"), { key: "[" });
673
+
674
+ expect(result.seeks).toEqual([3000, 1500]);
675
+ expect(result.selections).toEqual(["rec:0:3", "rec:0:2"]);
676
+ });
677
+
678
+ it("stepping stays inside the current tab", () => {
679
+ const result: RenderResult = renderRail({
680
+ currentTimeMs: 0,
681
+ activeTab: "console",
682
+ });
683
+
684
+ /* Only one console row; next lands on it, next again has nowhere to go. */
685
+ expect(result.handle.current?.stepSignal(1)?.id).toBe("rec:0:2");
686
+
687
+ result.rerender({
688
+ currentTimeMs: 1500,
689
+ activeTab: "console",
690
+ selectedSignalId: "rec:0:2",
691
+ });
692
+
693
+ expect(result.handle.current?.stepSignal(1)).toBeNull();
694
+ expect(result.seeks).toEqual([1500]);
695
+ });
696
+
697
+ it("j/k move the selection without seeking, Enter seeks to it, Escape clears it", () => {
698
+ const result: RenderResult = renderRail({ currentTimeMs: 3000 });
699
+ const list: HTMLElement = screen.getByTestId("rail-list");
700
+
701
+ fireEvent.keyDown(list, { key: "j" });
702
+
703
+ expect(result.selections).toEqual(["rec:0:3"]);
704
+ expect(result.seeks).toEqual([]);
705
+
706
+ result.rerender({ currentTimeMs: 3000, selectedSignalId: "rec:0:3" });
707
+ fireEvent.keyDown(list, { key: "Enter" });
708
+
709
+ expect(result.seeks).toEqual([3000]);
710
+
711
+ fireEvent.keyDown(list, { key: "Escape" });
712
+
713
+ expect(result.selections).toEqual(["rec:0:3", null]);
714
+ });
715
+
716
+ /*
717
+ * ux-13: the row's primary surface is a real button, so a keyboard user
718
+ * can reach and activate it - the old role=option row body was a bare
719
+ * div with an onClick and no tab stop at all. Exactly one row is in the
720
+ * Tab order, and j/k carry focus with the selection.
721
+ */
722
+ it("gives the rows a focusable body button with one tab stop, and moves focus with j/k", () => {
723
+ const result: RenderResult = renderRail({
724
+ currentTimeMs: 3000,
725
+ selectedSignalId: "rec:0:2",
726
+ });
727
+
728
+ const bodies: Array<HTMLElement> = rows().map((row: HTMLElement) => {
729
+ return row.querySelector("[data-rail-row-body='true']") as HTMLElement;
730
+ });
731
+
732
+ expect(
733
+ bodies.every((body: HTMLElement): boolean => {
734
+ return body.tagName === "BUTTON";
735
+ }),
736
+ ).toBe(true);
737
+ expect(
738
+ bodies.filter((body: HTMLElement): boolean => {
739
+ return body.getAttribute("tabindex") === "0";
740
+ }),
741
+ ).toHaveLength(1);
742
+ /* The selected row is the tab stop, and says its detail is open. */
743
+ expect(bodies[1]).toHaveAttribute("tabindex", "0");
744
+ expect(bodies[1]).toHaveAttribute("aria-expanded", "true");
745
+ expect(bodies[0]).toHaveAttribute("aria-expanded", "false");
746
+
747
+ (bodies[1] as HTMLElement).focus();
748
+ fireEvent.keyDown(screen.getByTestId("rail-list"), { key: "j" });
749
+
750
+ expect(result.selections).toEqual(["rec:0:3"]);
751
+
752
+ result.rerender({ currentTimeMs: 3000, selectedSignalId: "rec:0:3" });
753
+
754
+ expect(
755
+ (rows()[2] as HTMLElement).querySelector("[data-rail-row-body='true']"),
756
+ ).toHaveFocus();
757
+ });
758
+
759
+ it("leaves Enter to the row control that has focus", () => {
760
+ const result: RenderResult = renderRail({
761
+ currentTimeMs: 3000,
762
+ selectedSignalId: "rec:0:1",
763
+ });
764
+
765
+ /* The hover Seek button of a DIFFERENT row than the selected one. */
766
+ const seekButton: HTMLElement = within(
767
+ rows()[3] as HTMLElement,
768
+ ).getByLabelText(/^Seek to /);
769
+
770
+ seekButton.focus();
771
+
772
+ const wasNotPrevented: boolean = fireEvent.keyDown(seekButton, {
773
+ key: "Enter",
774
+ });
775
+
776
+ /* The list must not swallow it and seek the SELECTED row instead. */
777
+ expect(wasNotPrevented).toBe(true);
778
+ expect(result.seeks).toEqual([]);
779
+
780
+ fireEvent.click(seekButton);
781
+
782
+ expect(result.seeks).toEqual([8000]);
783
+ });
784
+
785
+ it("the handle reveals a signal on another tab by switching to it", () => {
786
+ const result: RenderResult = renderRail({
787
+ currentTimeMs: 0,
788
+ activeTab: "console",
789
+ });
790
+
791
+ let revealed: boolean | undefined = undefined;
792
+
793
+ act((): void => {
794
+ revealed = result.handle.current?.revealSignal("rec:0:1");
795
+ });
796
+
797
+ expect(revealed).toBe(true);
798
+ expect(result.tabs).toEqual(["network"]);
799
+ expect(result.selections).toEqual(["rec:0:1"]);
800
+ expect(result.seeks).toEqual([1000]);
801
+ act((): void => {
802
+ revealed = result.handle.current?.revealSignal("rec:9:9");
803
+ });
804
+
805
+ expect(revealed).toBe(false);
806
+ });
807
+
808
+ /*
809
+ * integration-003: a "view the session here" link from a span carries
810
+ * the id of the span that was CLICKED, but the Traces tab keys its rows
811
+ * by the ROOT span, so ?signal=span:<child> selected nothing at all.
812
+ */
813
+ it("reveals a trace row from any span id it contains, at that span's moment", () => {
814
+ const trace: ReplaySignal = makeSignal("span", 4000, {
815
+ id: "span:root-a",
816
+ title: "GET /api/orders",
817
+ links: { traceId: TRACE_ID, spanId: "root-a" },
818
+ detail: {
819
+ traceId: TRACE_ID,
820
+ rootSpanId: "root-a",
821
+ rootName: "GET /api/orders",
822
+ serviceId: null,
823
+ serviceName: "orders-svc",
824
+ durationMs: 900,
825
+ spanCount: 2,
826
+ errorSpanCount: 0,
827
+ hasError: false,
828
+ startUnixMs: START_UNIX_MS + 4000,
829
+ baselineOffsetMs: 4000,
830
+ isWaterfallTruncated: false,
831
+ spans: [
832
+ {
833
+ spanId: "root-a",
834
+ parentSpanId: null,
835
+ name: "GET /api/orders",
836
+ serviceName: "orders-svc",
837
+ depth: 0,
838
+ startOffsetMs: 0,
839
+ durationMs: 900,
840
+ hasError: false,
841
+ sessionOffsetMs: 4000,
842
+ },
843
+ {
844
+ spanId: "child-b",
845
+ parentSpanId: "root-a",
846
+ name: "SELECT orders",
847
+ serviceName: "orders-svc",
848
+ depth: 1,
849
+ startOffsetMs: 300,
850
+ durationMs: 200,
851
+ hasError: false,
852
+ sessionOffsetMs: 4300,
853
+ },
854
+ ],
855
+ },
856
+ });
857
+
858
+ const result: RenderResult = renderRail({
859
+ signals: [trace],
860
+ currentTimeMs: 0,
861
+ activeTab: "console",
862
+ });
863
+
864
+ let revealed: boolean | undefined = undefined;
865
+
866
+ act((): void => {
867
+ revealed = result.handle.current?.revealSignal("span:child-b");
868
+ });
869
+
870
+ expect(revealed).toBe(true);
871
+ expect(result.tabs).toEqual(["traces"]);
872
+ expect(result.selections).toEqual(["span:root-a"]);
873
+ /* The child's own moment, one second early like every rail seek. */
874
+ expect(result.seeks).toEqual([3300]);
875
+ });
876
+
877
+ it("selectSignal selects without moving the playhead", () => {
878
+ const result: RenderResult = renderRail({
879
+ currentTimeMs: 0,
880
+ activeTab: "console",
881
+ });
882
+
883
+ let selected: boolean | undefined = undefined;
884
+
885
+ act((): void => {
886
+ selected = result.handle.current?.selectSignal("rec:0:1");
887
+ });
888
+
889
+ expect(selected).toBe(true);
890
+ expect(result.tabs).toEqual(["network"]);
891
+ expect(result.selections).toEqual(["rec:0:1"]);
892
+ expect(result.seeks).toEqual([]);
893
+ });
894
+
895
+ it("/ handler focuses the search box through the handle", () => {
896
+ const result: RenderResult = renderRail();
897
+
898
+ act((): void => {
899
+ result.handle.current?.focusSearch();
900
+ });
901
+
902
+ expect(screen.getByTestId("rail-search-input")).toHaveFocus();
903
+ });
904
+ });
905
+
906
+ describe("ReplayRail filtering", () => {
907
+ it("filters rows by query tokens and reports the query", () => {
908
+ const result: RenderResult = renderRail();
909
+
910
+ fireEvent.change(screen.getByTestId("rail-search-input"), {
911
+ target: { value: "status:>=400" },
912
+ });
913
+
914
+ expect(result.queries).toEqual(["status:>=400"]);
915
+ expect(rowTitles()).toEqual(["rec:0:1"]);
916
+ });
917
+
918
+ it("chips narrow the current tab and clear together with the query", () => {
919
+ renderRail({ activeTab: "network" });
920
+
921
+ fireEvent.click(screen.getByTestId("rail-chip-network-2xx"));
922
+
923
+ expect(rows()).toHaveLength(0);
924
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
925
+ "No requests match this filter",
926
+ );
927
+
928
+ fireEvent.click(screen.getByText("Clear filters"));
929
+
930
+ expect(rows()).toHaveLength(1);
931
+ });
932
+
933
+ it("the ±30s scope keeps only rows near the playhead", () => {
934
+ renderRail({
935
+ currentTimeMs: 2000,
936
+ signals: [
937
+ makeSignal("console", 1000, { id: "rec:0:1" }),
938
+ makeSignal("console", 50_000, { id: "rec:3:2" }),
939
+ ],
940
+ });
941
+
942
+ fireEvent.click(screen.getByText("±30s"));
943
+
944
+ expect(rowTitles()).toEqual(["rec:0:1"]);
945
+ });
946
+
947
+ it("collapses consecutive identical rows into one with a repeat count", () => {
948
+ renderRail({
949
+ signals: [
950
+ makeSignal("console", 1000, { id: "rec:0:1", title: "render loop" }),
951
+ makeSignal("console", 1100, { id: "rec:0:2", title: "render loop" }),
952
+ makeSignal("console", 1200, { id: "rec:0:3", title: "render loop" }),
953
+ makeSignal("console", 1300, { id: "rec:0:4", title: "other" }),
954
+ ],
955
+ });
956
+
957
+ expect(rows()).toHaveLength(2);
958
+ expect(rows()[0]).toHaveTextContent("×3");
959
+ expect(screen.getByTestId("rail-tab-console")).toHaveTextContent(
960
+ "Console4",
961
+ );
962
+ });
963
+ });
964
+
965
+ describe("ReplayRail telemetry tabs", () => {
966
+ it("fetches logs on first open and counts them once loaded", async () => {
967
+ const { store, calls } = makeStore({
968
+ logs: [makeLog("aaaaaaaaaaaaaaaaaaaaaaaa", 5000, "charge failed")],
969
+ });
970
+ const result: RenderResult = renderRail({ backendStore: store });
971
+
972
+ expect(calls).toEqual([]);
973
+
974
+ fireEvent.click(screen.getByTestId("rail-tab-logs"));
975
+
976
+ await waitFor((): void => {
977
+ expect(screen.getByTestId("rail-tab-logs")).toHaveTextContent("Logs1");
978
+ });
979
+
980
+ expect(calls).toEqual(["log"]);
981
+ expect(rows()).toHaveLength(1);
982
+ expect(rows()[0]).toHaveTextContent("[ERROR] charge failed");
983
+ /* Server-stamped rows carry the alignment note in the header. */
984
+ expect(screen.getByTestId("rail-alignment-note")).toHaveTextContent(
985
+ /unanchored/,
986
+ );
987
+
988
+ /* Switching back does not refetch a finalized session. */
989
+ fireEvent.click(screen.getByTestId("rail-tab-all"));
990
+ expect(calls).toEqual(["log"]);
991
+ expect(result.tabs).toEqual(["logs", "all"]);
992
+ });
993
+
994
+ it("names the missing permission on a locked tab", async () => {
995
+ const { store } = makeStore({
996
+ reject: {
997
+ log: new HTTPErrorResponse(403, { message: "Forbidden" }, {}),
998
+ },
999
+ });
1000
+
1001
+ renderRail({ backendStore: store, activeTab: "logs" });
1002
+
1003
+ await waitFor((): void => {
1004
+ expect(store.getSnapshot().slots.log.status).toBe("locked");
1005
+ });
1006
+
1007
+ const permission: string = store.getSnapshot().slots.log
1008
+ .lockedPermission as string;
1009
+
1010
+ expect(permission.length).toBeGreaterThan(0);
1011
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
1012
+ `Your role lacks "${permission}"`,
1013
+ );
1014
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
1015
+ "Backend logs are locked",
1016
+ );
1017
+ });
1018
+
1019
+ it("loads every telemetry kind immediately once footage has expired", async () => {
1020
+ const { store, calls } = makeStore({});
1021
+
1022
+ renderRail({ backendStore: store, isExpiredFootage: true, signals: [] });
1023
+
1024
+ await waitFor((): void => {
1025
+ expect(calls.length).toBe(3);
1026
+ });
1027
+
1028
+ expect(new Set(calls)).toEqual(new Set(["log", "span", "exception"]));
1029
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
1030
+ "Signals expired with the footage",
1031
+ );
1032
+ });
1033
+
1034
+ it("offers a retry when a fetch fails", async () => {
1035
+ const { store, calls } = makeStore({
1036
+ reject: { log: new HTTPErrorResponse(502, { message: "bad" }, {}) },
1037
+ });
1038
+
1039
+ renderRail({ backendStore: store, activeTab: "logs" });
1040
+
1041
+ await waitFor((): void => {
1042
+ expect(store.getSnapshot().slots.log.status).toBe("error");
1043
+ });
1044
+
1045
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(/HTTP 502/);
1046
+ expect(screen.getAllByText(/HTTP 502/)).toHaveLength(1);
1047
+
1048
+ fireEvent.click(screen.getByText("Retry"));
1049
+
1050
+ await waitFor((): void => {
1051
+ expect(calls).toEqual(["log", "log"]);
1052
+ });
1053
+ });
1054
+ });
1055
+
1056
+ describe("ReplayRail empty copy", () => {
1057
+ it("explains why each tab is empty rather than saying nothing", () => {
1058
+ const cases: Array<[ReplayRailTabId, string]> = [
1059
+ ["console", "No console output was recorded in the loaded footage"],
1060
+ ["network", "No requests were recorded in the loaded footage"],
1061
+ ["logs", "No backend logs carried this session's id"],
1062
+ ["traces", "No backend spans carried this session's id"],
1063
+ ];
1064
+
1065
+ for (const [tabId, expected] of cases) {
1066
+ const copy: {
1067
+ title: string;
1068
+ detail: string;
1069
+ snippet?: string | undefined;
1070
+ } = getRailEmptyCopy({
1071
+ tabId: tabId,
1072
+ isFiltering: false,
1073
+ hadRowsBeforeFilter: false,
1074
+ slot:
1075
+ tabId === "logs" || tabId === "traces"
1076
+ ? {
1077
+ status: "ready",
1078
+ rowCount: 0,
1079
+ isTruncated: false,
1080
+ fetchedAtUnixMs: START_UNIX_MS,
1081
+ }
1082
+ : null,
1083
+ isExpiredFootage: false,
1084
+ recorderCapabilities: null,
1085
+ hasLoadedFootage: true,
1086
+ });
1087
+
1088
+ expect(copy.title).toBe(expected);
1089
+ expect(copy.detail.length).toBeGreaterThan(20);
1090
+ }
1091
+ });
1092
+
1093
+ it("ships the session.id snippet with the Logs and Traces copy", () => {
1094
+ renderRail({ signals: [], activeTab: "logs", backendStore: null });
1095
+
1096
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
1097
+ "OneUptimeReplay.onSessionChange",
1098
+ );
1099
+ expect(
1100
+ screen.getByTestId("rail-empty").querySelector("pre"),
1101
+ ).not.toBeNull();
1102
+ });
1103
+
1104
+ /*
1105
+ * ux-04: the capability names are a closed vocabulary and the recorder
1106
+ * announces "click-events". The copy compared against "click", so every
1107
+ * CURRENT recorder was told its recording predates click labels.
1108
+ */
1109
+ it("tells old recordings apart on the Interactions tab, listing capabilities", () => {
1110
+ renderRail({
1111
+ signals: [],
1112
+ activeTab: "interactions",
1113
+ recorderCapabilities: ["web-vitals", "visibility"],
1114
+ });
1115
+
1116
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
1117
+ "This recording predates click labels",
1118
+ );
1119
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent("web-vitals");
1120
+ });
1121
+
1122
+ it("does not accuse a current recorder of predating click labels", () => {
1123
+ expect(SESSION_REPLAY_RECORDER_CAPABILITIES).toContain(
1124
+ REPLAY_CLICK_EVENTS_CAPABILITY,
1125
+ );
1126
+
1127
+ renderRail({
1128
+ signals: [],
1129
+ activeTab: "interactions",
1130
+ recorderCapabilities: [...SESSION_REPLAY_RECORDER_CAPABILITIES],
1131
+ });
1132
+
1133
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
1134
+ "No clicks in the loaded footage",
1135
+ );
1136
+ expect(screen.getByTestId("rail-empty")).not.toHaveTextContent(
1137
+ "predates click labels",
1138
+ );
1139
+ });
1140
+
1141
+ it("says nothing about the recorder before the first chunk has decoded", () => {
1142
+ renderRail({
1143
+ signals: [],
1144
+ activeTab: "interactions",
1145
+ recorderCapabilities: ["web-vitals"],
1146
+ loadedChunkCount: 0,
1147
+ totalChunkCount: 4,
1148
+ });
1149
+
1150
+ expect(screen.getByTestId("rail-empty")).toHaveTextContent(
1151
+ "No interactions yet",
1152
+ );
1153
+ expect(screen.getByTestId("rail-empty")).not.toHaveTextContent(
1154
+ "predates click labels",
1155
+ );
1156
+ });
1157
+
1158
+ it("renders skeleton rows, never empty copy, while the manifest loads", () => {
1159
+ renderRail({ signals: [], isLoading: true });
1160
+
1161
+ expect(screen.getByTestId("rail-skeleton")).toBeInTheDocument();
1162
+ expect(screen.queryByTestId("rail-empty")).not.toBeInTheDocument();
1163
+ });
1164
+ });
1165
+
1166
+ describe("ReplayRail windowing", () => {
1167
+ it("mounts only a slice of a long list around the active row", () => {
1168
+ const many: Array<ReplaySignal> = [];
1169
+
1170
+ for (let i: number = 0; i < 700; i++) {
1171
+ many.push(
1172
+ makeSignal("network", i * 100, {
1173
+ id: `rec:${Math.floor(i / 150)}:${i}`,
1174
+ title: `GET 200 /items/${i}`,
1175
+ }),
1176
+ );
1177
+ }
1178
+
1179
+ renderRail({ signals: many, currentTimeMs: 35_000 });
1180
+
1181
+ const mounted: Array<HTMLElement> = rows();
1182
+
1183
+ expect(mounted.length).toBeGreaterThan(0);
1184
+ expect(mounted.length).toBeLessThan(700);
1185
+
1186
+ /* The active row (index 350) is inside the window. */
1187
+ expect(screen.getByTestId("rail-row-active")).toBeInTheDocument();
1188
+
1189
+ const ids: Array<string> = rowTitles();
1190
+
1191
+ expect(ids).toContain("rec:2:350");
1192
+ expect(ids).not.toContain("rec:0:0");
1193
+ expect(ids).not.toContain("rec:4:699");
1194
+ });
1195
+
1196
+ it("computeRailWindow always includes the selected row", () => {
1197
+ expect(
1198
+ computeRailWindow({
1199
+ rowCount: 1000,
1200
+ centerIndex: 100,
1201
+ mustIncludeIndexes: [900],
1202
+ }),
1203
+ ).toEqual({ startIndex: 0, endIndex: 901 });
1204
+ expect(computeRailWindow({ rowCount: 20, centerIndex: 5 })).toEqual({
1205
+ startIndex: 0,
1206
+ endIndex: 20,
1207
+ });
1208
+ });
1209
+ });
1210
+
1211
+ describe("ReplayRail pure helpers", () => {
1212
+ it("groupRepeatedSignals only merges consecutive identical groupable rows", () => {
1213
+ const grouped: ReturnType<typeof groupRepeatedSignals> =
1214
+ groupRepeatedSignals([
1215
+ makeSignal("console", 1, { id: "a", title: "x" }),
1216
+ makeSignal("console", 2, { id: "b", title: "x" }),
1217
+ makeSignal("network", 3, { id: "c", title: "x" }),
1218
+ makeSignal("network", 4, { id: "d", title: "x" }),
1219
+ makeSignal("console", 5, { id: "e", title: "x" }),
1220
+ ]);
1221
+
1222
+ expect(
1223
+ grouped.map(
1224
+ (row: { repeatCount: number; memberIds: Array<string> }): number => {
1225
+ return row.repeatCount;
1226
+ },
1227
+ ),
1228
+ ).toEqual([2, 1, 1, 1]);
1229
+ expect(grouped[0]?.memberIds).toEqual(["a", "b"]);
1230
+ });
1231
+
1232
+ it("stepRailRow starts from the selection, else the playhead, and clamps", () => {
1233
+ const list: ReturnType<typeof groupRepeatedSignals> = groupRepeatedSignals([
1234
+ makeSignal("console", 1000, { id: "a" }),
1235
+ makeSignal("console", 2000, { id: "b" }),
1236
+ makeSignal("network", 3000, { id: "c" }),
1237
+ ]);
1238
+
1239
+ expect(
1240
+ stepRailRow(list, { selectedSignalId: null, currentTimeMs: 0, delta: 1 })
1241
+ ?.signal.id,
1242
+ ).toBe("a");
1243
+ expect(
1244
+ stepRailRow(list, {
1245
+ selectedSignalId: null,
1246
+ currentTimeMs: 0,
1247
+ delta: -1,
1248
+ }),
1249
+ ).toBeNull();
1250
+ expect(
1251
+ stepRailRow(list, { selectedSignalId: "b", currentTimeMs: 0, delta: 1 })
1252
+ ?.signal.id,
1253
+ ).toBe("c");
1254
+ expect(
1255
+ stepRailRow(list, { selectedSignalId: "c", currentTimeMs: 0, delta: 1 }),
1256
+ ).toBeNull();
1257
+ });
1258
+
1259
+ it("buildRailTabModels leaves telemetry counts null until a slot has rows", () => {
1260
+ const models: ReturnType<typeof buildRailTabModels> = buildRailTabModels({
1261
+ signals: defaultSignals(),
1262
+ matchingSignals: null,
1263
+ slots: null,
1264
+ });
1265
+ const byId: Record<string, number | null> = {};
1266
+
1267
+ for (const model of models) {
1268
+ byId[model.id] = model.count;
1269
+ }
1270
+
1271
+ expect(byId["all"]).toBe(4);
1272
+ expect(byId["logs"]).toBeNull();
1273
+ expect(byId["traces"]).toBeNull();
1274
+ expect(byId["errors"]).toBe(1);
1275
+ });
1276
+ });
1277
+
1278
+ describe("ReplayRail hover", () => {
1279
+ it("reports the hovered row's offset for the ghost playhead and clears it on leave", () => {
1280
+ const hovers: Array<number | null> = [];
1281
+
1282
+ renderRail({
1283
+ onHoverSignal: (offsetMs: number | null): void => {
1284
+ hovers.push(offsetMs);
1285
+ },
1286
+ });
1287
+
1288
+ fireEvent.mouseEnter(rows()[0] as HTMLElement);
1289
+ fireEvent.mouseLeave(rows()[0] as HTMLElement);
1290
+
1291
+ expect(hovers).toEqual([2000, null]);
1292
+ });
1293
+ });
1294
+
1295
+ /* Keep jest from flagging the unused helper on platforms without a real clipboard. */
1296
+ export const noop: () => void = jest.fn() as unknown as () => void;