@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,1579 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import ExceptionInstance from "../../../Models/AnalyticsModels/ExceptionInstance";
3
+ import Log from "../../../Models/AnalyticsModels/Log";
4
+ import Span, { SpanStatus } from "../../../Models/AnalyticsModels/Span";
5
+ import LogSeverity from "../../../Types/Log/LogSeverity";
6
+ import ObjectID from "../../../Types/ObjectID";
7
+ import {
8
+ REPLAY_TIMELINE_EVENT_KINDS,
9
+ ReplayTimelineEvent,
10
+ ReplayTimelineEventKind,
11
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/ReplayTimelineTypes";
12
+ import {
13
+ ReplayClockAlignmentState,
14
+ ReplaySignal,
15
+ ReplaySignalKind,
16
+ ReplayTelemetryClock,
17
+ makeRecordingSignalId,
18
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/Rail/ReplaySignalTypes";
19
+ import {
20
+ REPLAY_NETWORK_CAPTURE_CAP,
21
+ REPLAY_SIGNAL_SEEK_PRE_ROLL_MS,
22
+ REPLAY_SIGNAL_TITLE_MAX_LENGTH,
23
+ REPLAY_TRACE_WATERFALL_MAX_SPANS,
24
+ ReplayClientErrorSignalDetail,
25
+ ReplayErrorPair,
26
+ ReplayInteractionSignalDetail,
27
+ ReplayLogSignalDetail,
28
+ ReplayNetworkSignalDetail,
29
+ ReplayPerformanceSignalDetail,
30
+ ReplayServerErrorSignalDetail,
31
+ ReplaySignalMatch,
32
+ ReplaySpanSignalDetail,
33
+ ReplayTraceWaterfallSpan,
34
+ buildErrorCounterpartIndex,
35
+ findErrorAfterInteraction,
36
+ findErrorLogsForTrace,
37
+ findSignalMatch,
38
+ formatPerformanceMeasure,
39
+ formatSignalBytes,
40
+ formatSignalDuration,
41
+ formatVitalRating,
42
+ fromExceptionRow,
43
+ fromLogRow,
44
+ fromSpanRow,
45
+ fromTimelineEvent,
46
+ fromTimelineEvents,
47
+ getActiveSignalIndex,
48
+ groupSpansIntoTraces,
49
+ indexTraceSignalsByTraceId,
50
+ isSignalActiveAt,
51
+ isSteppableClientError,
52
+ mergeSignals,
53
+ pairClientAndServerErrors,
54
+ splitSignalUrl,
55
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/Rail/ReplaySignals";
56
+
57
+ /*
58
+ * The rail renders one row shape for everything; these adapters are where
59
+ * a recording event or a server row becomes that shape. Every kind must
60
+ * land with the right severity, title, links and id, telemetry rows must
61
+ * sit on the session clock WITHOUT the clockSkewMs correction (which only
62
+ * applies to client-stamped values), and the merge/active-index helpers
63
+ * must keep the "synced to the playhead" claim true.
64
+ */
65
+
66
+ const START_UNIX_MS: number = 1_700_000_000_000;
67
+
68
+ function makeEvent(
69
+ kind: ReplayTimelineEventKind,
70
+ overrides?: Partial<ReplayTimelineEvent>,
71
+ ): ReplayTimelineEvent {
72
+ return {
73
+ id: makeRecordingSignalId(2, 7),
74
+ kind: kind,
75
+ chunkIndex: 2,
76
+ offsetMs: 12_500,
77
+ ...overrides,
78
+ };
79
+ }
80
+
81
+ const UNANCHORED: ReplayClockAlignmentState = {
82
+ status: "unanchored",
83
+ deltaMs: 0,
84
+ pairCount: 0,
85
+ uncertaintyMs: 3500,
86
+ };
87
+
88
+ const ANCHORED: ReplayClockAlignmentState = {
89
+ status: "anchored",
90
+ deltaMs: -1500,
91
+ pairCount: 3,
92
+ uncertaintyMs: 200,
93
+ };
94
+
95
+ function makeClock(
96
+ alignment: ReplayClockAlignmentState = UNANCHORED,
97
+ serviceNameById?: Record<string, string>,
98
+ ): ReplayTelemetryClock {
99
+ return {
100
+ startTimeUnixMs: START_UNIX_MS,
101
+ alignment: alignment,
102
+ serviceNameById: serviceNameById,
103
+ };
104
+ }
105
+
106
+ function makeLog(args: {
107
+ id: string;
108
+ atMs: number;
109
+ body?: string;
110
+ severity?: LogSeverity;
111
+ severityNumber?: number;
112
+ serviceId?: string;
113
+ traceId?: string;
114
+ spanId?: string;
115
+ }): Log {
116
+ const log: Log = new Log();
117
+
118
+ log.id = new ObjectID(args.id);
119
+ log.time = new Date(START_UNIX_MS + args.atMs);
120
+ log.body = args.body ?? "charge failed: card_declined";
121
+
122
+ if (args.severity !== undefined) {
123
+ log.severityText = args.severity;
124
+ }
125
+
126
+ if (args.severityNumber !== undefined) {
127
+ log.severityNumber = args.severityNumber;
128
+ }
129
+
130
+ if (args.serviceId) {
131
+ log.primaryEntityId = new ObjectID(args.serviceId);
132
+ }
133
+
134
+ if (args.traceId) {
135
+ log.traceId = args.traceId;
136
+ }
137
+
138
+ if (args.spanId) {
139
+ log.spanId = args.spanId;
140
+ }
141
+
142
+ return log;
143
+ }
144
+
145
+ function makeSpan(args: {
146
+ spanId: string;
147
+ traceId: string;
148
+ atMs: number;
149
+ durationMs: number;
150
+ name?: string;
151
+ parentSpanId?: string | undefined;
152
+ status?: SpanStatus;
153
+ serviceId?: string;
154
+ }): Span {
155
+ const span: Span = new Span();
156
+
157
+ span.spanId = args.spanId;
158
+ span.traceId = args.traceId;
159
+ span.startTime = new Date(START_UNIX_MS + args.atMs);
160
+ span.durationUnixNano = args.durationMs * 1_000_000;
161
+ span.name = args.name ?? `span ${args.spanId}`;
162
+
163
+ if (args.parentSpanId) {
164
+ span.parentSpanId = args.parentSpanId;
165
+ }
166
+
167
+ if (args.status !== undefined) {
168
+ span.statusCode = args.status;
169
+ }
170
+
171
+ if (args.serviceId) {
172
+ span.primaryEntityId = new ObjectID(args.serviceId);
173
+ }
174
+
175
+ return span;
176
+ }
177
+
178
+ function makeException(args: {
179
+ id: string;
180
+ atMs: number;
181
+ message?: string;
182
+ exceptionType?: string;
183
+ fingerprint?: string;
184
+ traceId?: string;
185
+ spanId?: string;
186
+ stackTrace?: string;
187
+ serviceId?: string;
188
+ }): ExceptionInstance {
189
+ const instance: ExceptionInstance = new ExceptionInstance();
190
+
191
+ instance.id = new ObjectID(args.id);
192
+ instance.time = new Date(START_UNIX_MS + args.atMs);
193
+ instance.message = args.message ?? "Cannot read properties of undefined";
194
+
195
+ if (args.exceptionType) {
196
+ instance.exceptionType = args.exceptionType;
197
+ }
198
+
199
+ if (args.fingerprint) {
200
+ instance.fingerprint = args.fingerprint;
201
+ }
202
+
203
+ if (args.traceId) {
204
+ instance.traceId = args.traceId;
205
+ }
206
+
207
+ if (args.spanId) {
208
+ instance.spanId = args.spanId;
209
+ }
210
+
211
+ if (args.stackTrace) {
212
+ instance.stackTrace = args.stackTrace;
213
+ }
214
+
215
+ if (args.serviceId) {
216
+ instance.primaryEntityId = new ObjectID(args.serviceId);
217
+ }
218
+
219
+ return instance;
220
+ }
221
+
222
+ function makeSignal(overrides: Partial<ReplaySignal>): ReplaySignal {
223
+ return {
224
+ id: overrides.id ?? "rec:0:0",
225
+ kind: overrides.kind ?? "console",
226
+ source: overrides.source ?? "recording",
227
+ offsetMs: overrides.offsetMs ?? 0,
228
+ severity: overrides.severity ?? "info",
229
+ title: overrides.title ?? "row",
230
+ links: overrides.links ?? {},
231
+ detail: overrides.detail ?? {},
232
+ ...overrides,
233
+ };
234
+ }
235
+
236
+ describe("fromTimelineEvent: every kind maps", () => {
237
+ const expectedKinds: Record<ReplayTimelineEventKind, ReplaySignalKind> = {
238
+ console: "console",
239
+ network: "network",
240
+ route: "navigation",
241
+ navigation: "navigation",
242
+ error: "client-error",
243
+ frustration: "frustration",
244
+ performance: "performance",
245
+ click: "interaction",
246
+ visibility: "marker",
247
+ custom: "custom",
248
+ identify: "marker",
249
+ tags: "marker",
250
+ "click-dropped": "marker",
251
+ "custom-dropped": "marker",
252
+ };
253
+
254
+ it.each(REPLAY_TIMELINE_EVENT_KINDS)(
255
+ "%s becomes a recording signal with the event's id, chunk and offset",
256
+ (kind: ReplayTimelineEventKind) => {
257
+ const signal: ReplaySignal = fromTimelineEvent(makeEvent(kind), {
258
+ startTimeUnixMs: START_UNIX_MS,
259
+ });
260
+
261
+ expect(signal.kind).toBe(expectedKinds[kind]);
262
+ expect(signal.id).toBe("rec:2:7");
263
+ expect(signal.source).toBe("recording");
264
+ expect(signal.chunkIndex).toBe(2);
265
+ expect(signal.offsetMs).toBe(12_500);
266
+ expect(signal.alignment).toBe("exact");
267
+ expect(signal.title.length).toBeGreaterThan(0);
268
+ expect(signal.title).not.toContain("undefined");
269
+ expect(signal.title).not.toContain("NaN");
270
+ },
271
+ );
272
+
273
+ it("never throws on a bare event with no per-kind fields", () => {
274
+ for (const kind of REPLAY_TIMELINE_EVENT_KINDS) {
275
+ expect(() => {
276
+ return fromTimelineEvent(makeEvent(kind), { startTimeUnixMs: null });
277
+ }).not.toThrow();
278
+ }
279
+ });
280
+
281
+ it("stamps a wall clock from the event, else from start + offset, else null", () => {
282
+ const fromEvent: ReplaySignal = fromTimelineEvent(
283
+ makeEvent("click", { atUnixMs: 123 }),
284
+ { startTimeUnixMs: START_UNIX_MS },
285
+ );
286
+ const derived: ReplaySignal = fromTimelineEvent(makeEvent("click"), {
287
+ startTimeUnixMs: START_UNIX_MS,
288
+ });
289
+ const unknown: ReplaySignal = fromTimelineEvent(makeEvent("click"), {
290
+ startTimeUnixMs: null,
291
+ });
292
+
293
+ expect(fromEvent.detail["atUnixMs"]).toBe(123);
294
+ expect(derived.detail["atUnixMs"]).toBe(START_UNIX_MS + 12_500);
295
+ expect(unknown.detail["atUnixMs"]).toBeNull();
296
+ });
297
+ });
298
+
299
+ describe("console rows", () => {
300
+ it("maps level to severity and keeps the full message in detail", () => {
301
+ const message: string = "TypeError: boom\n at app.js:1:1";
302
+ const error: ReplaySignal = fromTimelineEvent(
303
+ makeEvent("console", { level: "error", message: message }),
304
+ { startTimeUnixMs: null },
305
+ );
306
+ const warn: ReplaySignal = fromTimelineEvent(
307
+ makeEvent("console", { level: "warn", message: "careful" }),
308
+ { startTimeUnixMs: null },
309
+ );
310
+
311
+ expect(error.severity).toBe("error");
312
+ expect(error.title).toBe("TypeError: boom");
313
+ expect(error.subtitle).toBe("error");
314
+ expect(error.detail["message"]).toBe(message);
315
+ expect(warn.severity).toBe("warn");
316
+ });
317
+
318
+ it("truncates long titles with an ellipsis but not the detail", () => {
319
+ const message: string = "x".repeat(REPLAY_SIGNAL_TITLE_MAX_LENGTH + 50);
320
+ const signal: ReplaySignal = fromTimelineEvent(
321
+ makeEvent("console", { level: "warn", message: message }),
322
+ { startTimeUnixMs: null },
323
+ );
324
+
325
+ expect(signal.title.length).toBe(REPLAY_SIGNAL_TITLE_MAX_LENGTH);
326
+ expect(signal.title.endsWith("…")).toBe(true);
327
+ expect(signal.detail["message"]).toBe(message);
328
+ });
329
+ });
330
+
331
+ describe("network rows", () => {
332
+ const base: Partial<ReplayTimelineEvent> = {
333
+ method: "post",
334
+ url: "https://api.example.com/api/orders?x=1",
335
+ status: 500,
336
+ durationMs: 220,
337
+ responseBytes: 1229,
338
+ requestBytes: 512,
339
+ initiator: "fetch",
340
+ traceId: "abc123abc123abc123abc123abc123ab",
341
+ isError: true,
342
+ };
343
+
344
+ it("renders 'POST 500 /api/orders' with duration and bytes and a trace link", () => {
345
+ const signal: ReplaySignal = fromTimelineEvent(makeEvent("network", base), {
346
+ startTimeUnixMs: null,
347
+ });
348
+ const detail: ReplayNetworkSignalDetail =
349
+ signal.detail as ReplayNetworkSignalDetail;
350
+
351
+ expect(signal.title).toBe("POST 500 /api/orders?x=1");
352
+ expect(signal.subtitle).toBe("220ms 1.2KB");
353
+ expect(signal.severity).toBe("error");
354
+ expect(signal.links.traceId).toBe(base.traceId);
355
+ expect(detail.method).toBe("POST");
356
+ expect(detail.url).toBe(base.url);
357
+ expect(detail.origin).toBe("https://api.example.com");
358
+ expect(detail.path).toBe("/api/orders?x=1");
359
+ expect(detail.status).toBe(500);
360
+ expect(detail.durationMs).toBe(220);
361
+ expect(detail.responseBytes).toBe(1229);
362
+ expect(detail.requestBytes).toBe(512);
363
+ expect(detail.initiator).toBe("fetch");
364
+ expect(detail.traceId).toBe(base.traceId);
365
+ expect(detail.isError).toBe(true);
366
+ expect(detail.failedBeforeResponse).toBe(false);
367
+ expect(detail.isSlow).toBe(false);
368
+ });
369
+
370
+ it("grades severity by status: 2xx info, 4xx warn, 5xx error, 0 error", () => {
371
+ const grade: (status: number) => string = (status: number): string => {
372
+ return fromTimelineEvent(
373
+ makeEvent("network", { ...base, status: status, isError: false }),
374
+ { startTimeUnixMs: null },
375
+ ).severity;
376
+ };
377
+
378
+ expect(grade(200)).toBe("info");
379
+ expect(grade(302)).toBe("info");
380
+ expect(grade(404)).toBe("warn");
381
+ expect(grade(503)).toBe("error");
382
+ expect(grade(0)).toBe("error");
383
+ });
384
+
385
+ it("names a status-0 request 'failed' and flags it as failed before response", () => {
386
+ const failedBase: Partial<ReplayTimelineEvent> = { ...base, status: 0 };
387
+
388
+ delete failedBase.durationMs;
389
+
390
+ const signal: ReplaySignal = fromTimelineEvent(
391
+ makeEvent("network", failedBase),
392
+ { startTimeUnixMs: null },
393
+ );
394
+ const detail: ReplayNetworkSignalDetail =
395
+ signal.detail as ReplayNetworkSignalDetail;
396
+
397
+ expect(signal.title).toBe("POST failed /api/orders?x=1");
398
+ expect(detail.failedBeforeResponse).toBe(true);
399
+ expect(detail.isError).toBe(true);
400
+ /* No duration was measured, so none is claimed. */
401
+ expect(detail.durationMs).toBeNull();
402
+ expect(signal.subtitle).toBe("1.2KB");
403
+ });
404
+
405
+ it("omits the subtitle entirely when neither duration nor bytes were measured", () => {
406
+ const signal: ReplaySignal = fromTimelineEvent(
407
+ makeEvent("network", { method: "GET", url: "/x", status: 200 }),
408
+ { startTimeUnixMs: null },
409
+ );
410
+
411
+ expect(signal.subtitle).toBeUndefined();
412
+ expect(signal.links.traceId).toBeUndefined();
413
+ });
414
+
415
+ it("flags requests over 1s as slow", () => {
416
+ const signal: ReplaySignal = fromTimelineEvent(
417
+ makeEvent("network", { ...base, status: 200, durationMs: 1400 }),
418
+ { startTimeUnixMs: null },
419
+ );
420
+
421
+ expect((signal.detail as ReplayNetworkSignalDetail).isSlow).toBe(true);
422
+ expect(signal.subtitle).toBe("1.4s 1.2KB");
423
+ });
424
+ });
425
+
426
+ describe("navigation rows", () => {
427
+ it("labels history routes by kind and full loads as such", () => {
428
+ const route: ReplaySignal = fromTimelineEvent(
429
+ makeEvent("route", {
430
+ from: "/cart",
431
+ to: "/checkout",
432
+ routeKind: "pushState",
433
+ }),
434
+ { startTimeUnixMs: null },
435
+ );
436
+ const load: ReplaySignal = fromTimelineEvent(
437
+ makeEvent("navigation", { to: "https://app.example.com/login" }),
438
+ { startTimeUnixMs: null },
439
+ );
440
+
441
+ expect(route.title).toBe("/checkout");
442
+ expect(route.subtitle).toBe("history push");
443
+ expect(route.detail["from"]).toBe("/cart");
444
+ expect(route.detail["kind"]).toBe("pushState");
445
+ expect(load.title).toBe("https://app.example.com/login");
446
+ expect(load.subtitle).toBe("full page load");
447
+ expect(load.detail["kind"]).toBe("full-load");
448
+ expect(load.detail["from"]).toBeNull();
449
+ });
450
+
451
+ it("carries the declared viewport of a full load and never invents one", () => {
452
+ const load: ReplaySignal = fromTimelineEvent(
453
+ makeEvent("navigation", {
454
+ to: "https://app.example.com/login",
455
+ viewportWidth: 1440,
456
+ viewportHeight: 900,
457
+ }),
458
+ { startTimeUnixMs: null },
459
+ );
460
+ const route: ReplaySignal = fromTimelineEvent(
461
+ makeEvent("route", { to: "/checkout", routeKind: "pushState" }),
462
+ { startTimeUnixMs: null },
463
+ );
464
+
465
+ expect(load.detail["viewportWidth"]).toBe(1440);
466
+ expect(load.detail["viewportHeight"]).toBe(900);
467
+ /* A route change does not carry a viewport; the detail must say so, not 0. */
468
+ expect(route.detail["viewportWidth"]).toBeNull();
469
+ expect(route.detail["viewportHeight"]).toBeNull();
470
+ });
471
+ });
472
+
473
+ describe("client error rows", () => {
474
+ it("carries stack, source, line and column into detail with a location line", () => {
475
+ const signal: ReplaySignal = fromTimelineEvent(
476
+ makeEvent("error", {
477
+ errorKind: "error",
478
+ message: "Cannot read properties of undefined (reading 'id')",
479
+ source: "https://app.example.com/static/app.js",
480
+ lineNumber: 12,
481
+ columnNumber: 5,
482
+ stack: "TypeError: Cannot read...\n at render (app.js:12:5)",
483
+ }),
484
+ { startTimeUnixMs: null },
485
+ );
486
+ const detail: ReplayClientErrorSignalDetail =
487
+ signal.detail as ReplayClientErrorSignalDetail;
488
+
489
+ expect(signal.kind).toBe("client-error");
490
+ expect(signal.severity).toBe("error");
491
+ expect(signal.title).toBe(
492
+ "Cannot read properties of undefined (reading 'id')",
493
+ );
494
+ expect(signal.subtitle).toBe("uncaught error · /static/app.js:12:5");
495
+ expect(detail.stack).toContain("at render");
496
+ expect(detail.source).toBe("https://app.example.com/static/app.js");
497
+ expect(detail.lineNumber).toBe(12);
498
+ expect(detail.columnNumber).toBe(5);
499
+ expect(detail.location).toBe("/static/app.js:12:5");
500
+ });
501
+
502
+ it("labels unhandled rejections and tolerates a missing location", () => {
503
+ const signal: ReplaySignal = fromTimelineEvent(
504
+ makeEvent("error", {
505
+ errorKind: "unhandledrejection",
506
+ message: "Network down",
507
+ }),
508
+ { startTimeUnixMs: null },
509
+ );
510
+
511
+ expect(signal.subtitle).toBe("unhandled rejection");
512
+ expect(
513
+ (signal.detail as ReplayClientErrorSignalDetail).location,
514
+ ).toBeNull();
515
+ expect((signal.detail as ReplayClientErrorSignalDetail).stack).toBeNull();
516
+ });
517
+ });
518
+
519
+ describe("frustration and interaction rows", () => {
520
+ it("names each frustration kind and quantifies it", () => {
521
+ const rage: ReplaySignal = fromTimelineEvent(
522
+ makeEvent("frustration", {
523
+ frustrationKind: "rage-click",
524
+ clickCount: 5,
525
+ x: 120.4,
526
+ y: 340.6,
527
+ }),
528
+ { startTimeUnixMs: null },
529
+ );
530
+ const refresh: ReplaySignal = fromTimelineEvent(
531
+ makeEvent("frustration", {
532
+ frustrationKind: "refresh-rage",
533
+ reloadCount: 3,
534
+ }),
535
+ { startTimeUnixMs: null },
536
+ );
537
+ const dead: ReplaySignal = fromTimelineEvent(
538
+ makeEvent("frustration", { frustrationKind: "dead-click" }),
539
+ { startTimeUnixMs: null },
540
+ );
541
+
542
+ expect(rage.kind).toBe("frustration");
543
+ expect(rage.severity).toBe("warn");
544
+ expect(rage.title).toBe("Rage click (5 clicks)");
545
+ expect(rage.subtitle).toBe("at 120, 341");
546
+ expect(refresh.title).toBe("Refresh rage (3 reloads)");
547
+ expect(dead.title).toBe("Dead click");
548
+ expect(dead.subtitle).toBeUndefined();
549
+ });
550
+
551
+ it("prefers the click's text, then its selector, then coordinates", () => {
552
+ const labelled: ReplaySignal = fromTimelineEvent(
553
+ makeEvent("click", {
554
+ selector: "button.pay",
555
+ text: "Pay now",
556
+ x: 10,
557
+ y: 20,
558
+ }),
559
+ { startTimeUnixMs: null },
560
+ );
561
+ const selectorOnly: ReplaySignal = fromTimelineEvent(
562
+ makeEvent("click", { selector: "button.pay", x: 10, y: 20 }),
563
+ { startTimeUnixMs: null },
564
+ );
565
+ const coordinates: ReplaySignal = fromTimelineEvent(
566
+ makeEvent("click", { x: 10, y: 20 }),
567
+ { startTimeUnixMs: null },
568
+ );
569
+
570
+ expect(labelled.kind).toBe("interaction");
571
+ expect(labelled.title).toBe('Click "Pay now"');
572
+ expect(labelled.subtitle).toBe("button.pay");
573
+ expect(selectorOnly.title).toBe("Click button.pay");
574
+ expect(selectorOnly.subtitle).toBe("at 10, 20");
575
+ expect(coordinates.title).toBe("Click at (10, 20)");
576
+ expect(
577
+ (coordinates.detail as ReplayInteractionSignalDetail).isCoordinateOnly,
578
+ ).toBe(true);
579
+ expect(
580
+ (labelled.detail as ReplayInteractionSignalDetail).isCoordinateOnly,
581
+ ).toBe(false);
582
+ });
583
+ });
584
+
585
+ describe("performance rows", () => {
586
+ it("renders budget overruns as 'LCP 4.8s (budget 4s)' with warn severity", () => {
587
+ const signal: ReplaySignal = fromTimelineEvent(
588
+ makeEvent("performance", {
589
+ performanceKind: "lcp",
590
+ durationMs: 4800,
591
+ budgetMs: 4000,
592
+ url: "https://app.example.com/checkout",
593
+ }),
594
+ { startTimeUnixMs: null },
595
+ );
596
+ const detail: ReplayPerformanceSignalDetail =
597
+ signal.detail as ReplayPerformanceSignalDetail;
598
+
599
+ expect(signal.title).toBe("LCP 4.8s (budget 4s)");
600
+ expect(signal.severity).toBe("warn");
601
+ expect(signal.subtitle).toBe("/checkout");
602
+ expect(detail.isOverBudget).toBe(true);
603
+ expect(detail.kind).toBe("lcp");
604
+ });
605
+
606
+ it("grades web vitals by rating and formats CLS without a unit", () => {
607
+ const poor: ReplaySignal = fromTimelineEvent(
608
+ makeEvent("performance", {
609
+ performanceKind: "web-vital",
610
+ metric: "INP",
611
+ value: 320,
612
+ rating: "poor",
613
+ }),
614
+ { startTimeUnixMs: null },
615
+ );
616
+ const good: ReplaySignal = fromTimelineEvent(
617
+ makeEvent("performance", {
618
+ performanceKind: "web-vital",
619
+ metric: "CLS",
620
+ value: 0.0234,
621
+ rating: "good",
622
+ }),
623
+ { startTimeUnixMs: null },
624
+ );
625
+
626
+ expect(poor.title).toBe("INP 320ms poor");
627
+ expect(poor.severity).toBe("error");
628
+ expect((poor.detail as ReplayPerformanceSignalDetail).isOverBudget).toBe(
629
+ true,
630
+ );
631
+ expect(good.title).toBe("CLS 0.023 good");
632
+ expect(good.severity).toBe("success");
633
+ expect((good.detail as ReplayPerformanceSignalDetail).isOverBudget).toBe(
634
+ false,
635
+ );
636
+ });
637
+
638
+ it("does not claim a budget or duration it was not given", () => {
639
+ const signal: ReplaySignal = fromTimelineEvent(
640
+ makeEvent("performance", { performanceKind: "long-task" }),
641
+ { startTimeUnixMs: null },
642
+ );
643
+
644
+ expect(signal.title).toBe("Long task");
645
+ expect(signal.severity).toBe("info");
646
+ expect(signal.title).not.toContain("0ms");
647
+ });
648
+ });
649
+
650
+ describe("custom and marker rows", () => {
651
+ it("names the custom event and counts its properties", () => {
652
+ const signal: ReplaySignal = fromTimelineEvent(
653
+ makeEvent("custom", {
654
+ name: "checkout.step",
655
+ properties: { step: "2", plan: "pro" },
656
+ }),
657
+ { startTimeUnixMs: null },
658
+ );
659
+
660
+ expect(signal.kind).toBe("custom");
661
+ expect(signal.title).toBe("checkout.step");
662
+ expect(signal.subtitle).toBe("2 properties");
663
+ expect(signal.detail["properties"]).toEqual({ step: "2", plan: "pro" });
664
+ });
665
+
666
+ it("renders visibility, identify, tags and cap notices as markers", () => {
667
+ const hidden: ReplaySignal = fromTimelineEvent(
668
+ makeEvent("visibility", { visibilityState: "hidden" }),
669
+ { startTimeUnixMs: null },
670
+ );
671
+ const identify: ReplaySignal = fromTimelineEvent(
672
+ makeEvent("identify", { hasTraits: true }),
673
+ { startTimeUnixMs: null },
674
+ );
675
+ const tags: ReplaySignal = fromTimelineEvent(
676
+ makeEvent("tags", { tags: { plan: "pro" } }),
677
+ { startTimeUnixMs: null },
678
+ );
679
+ const dropped: ReplaySignal = fromTimelineEvent(
680
+ makeEvent("click-dropped", { droppedCount: 12 }),
681
+ { startTimeUnixMs: null },
682
+ );
683
+ const droppedCustom: ReplaySignal = fromTimelineEvent(
684
+ makeEvent("custom-dropped", { droppedCount: 3 }),
685
+ { startTimeUnixMs: null },
686
+ );
687
+
688
+ expect(hidden.title).toBe("Tab hidden");
689
+ expect(hidden.detail["markerKind"]).toBe("visibility");
690
+ expect(identify.title).toBe("User identified");
691
+ expect(identify.subtitle).toBe("with traits");
692
+ expect(tags.title).toBe("Tags set");
693
+ expect(tags.subtitle).toBe("1 tag");
694
+ expect(dropped.title).toBe("12 clicks not labelled (recorder cap)");
695
+ expect(dropped.severity).toBe("warn");
696
+ expect(droppedCustom.title).toBe(
697
+ "3 custom events not recorded (recorder cap)",
698
+ );
699
+ });
700
+
701
+ it("fromTimelineEvents maps a batch in order", () => {
702
+ const signals: Array<ReplaySignal> = fromTimelineEvents(
703
+ [
704
+ makeEvent("console", { id: "rec:0:0", level: "warn", message: "a" }),
705
+ makeEvent("click", { id: "rec:0:1", x: 1, y: 2 }),
706
+ ],
707
+ { startTimeUnixMs: null },
708
+ );
709
+
710
+ expect(
711
+ signals.map((signal: ReplaySignal): string => {
712
+ return signal.id;
713
+ }),
714
+ ).toEqual(["rec:0:0", "rec:0:1"]);
715
+ });
716
+ });
717
+
718
+ describe("fromLogRow", () => {
719
+ it("places the row at time - startTimeUnixMs on the baseline, labelled unanchored", () => {
720
+ const signal: ReplaySignal | null = fromLogRow(
721
+ makeLog({
722
+ id: "log-1",
723
+ atMs: 42_000,
724
+ severity: LogSeverity.Error,
725
+ traceId: "trace-a",
726
+ spanId: "span-a",
727
+ serviceId: "svc-1",
728
+ }),
729
+ makeClock(UNANCHORED, { "svc-1": "payment-svc" }),
730
+ );
731
+
732
+ expect(signal).not.toBeNull();
733
+ expect(signal?.id).toBe("log:log-1");
734
+ expect(signal?.kind).toBe("log");
735
+ expect(signal?.source).toBe("telemetry");
736
+ expect(signal?.offsetMs).toBe(42_000);
737
+ expect(signal?.alignment).toBe("unanchored");
738
+ expect(signal?.severity).toBe("error");
739
+ expect(signal?.title).toBe("[ERROR] charge failed: card_declined");
740
+ expect(signal?.subtitle).toBe("payment-svc");
741
+ expect(signal?.links).toEqual({
742
+ traceId: "trace-a",
743
+ spanId: "span-a",
744
+ logId: "log-1",
745
+ });
746
+
747
+ const detail: ReplayLogSignalDetail =
748
+ signal?.detail as ReplayLogSignalDetail;
749
+
750
+ expect(detail.level).toBe("ERROR");
751
+ expect(detail.serviceName).toBe("payment-svc");
752
+ expect(detail.timeUnixMs).toBe(START_UNIX_MS + 42_000);
753
+ expect(detail.baselineOffsetMs).toBe(42_000);
754
+ });
755
+
756
+ it("applies the anchoring delta when anchored, and keeps the baseline in detail", () => {
757
+ const signal: ReplaySignal | null = fromLogRow(
758
+ makeLog({ id: "log-2", atMs: 42_000, severity: LogSeverity.Warning }),
759
+ makeClock(ANCHORED),
760
+ );
761
+
762
+ expect(signal?.offsetMs).toBe(42_000 + ANCHORED.deltaMs);
763
+ expect(signal?.alignment).toBe("anchored");
764
+ expect((signal?.detail as ReplayLogSignalDetail).baselineOffsetMs).toBe(
765
+ 42_000,
766
+ );
767
+ expect(signal?.severity).toBe("warn");
768
+ expect(signal?.title).toBe("[WARN] charge failed: card_declined");
769
+ });
770
+
771
+ it("is never adjusted by a clock skew, whatever the manifest says", () => {
772
+ /*
773
+ * The clock has no clockSkewMs input on purpose: skew is a client-vs-
774
+ * server delta and these rows are server-stamped. Pin the shape so a
775
+ * future "helpful" parameter cannot creep in.
776
+ */
777
+ const clock: ReplayTelemetryClock = makeClock(UNANCHORED);
778
+
779
+ expect(Object.keys(clock).sort()).toEqual([
780
+ "alignment",
781
+ "serviceNameById",
782
+ "startTimeUnixMs",
783
+ ]);
784
+ expect(
785
+ fromLogRow(makeLog({ id: "log-3", atMs: 10_000 }), clock)?.offsetMs,
786
+ ).toBe(10_000);
787
+ });
788
+
789
+ it("falls back to the OTLP severity number and omits the level when unset", () => {
790
+ const numbered: ReplaySignal | null = fromLogRow(
791
+ makeLog({ id: "log-4", atMs: 1, severityNumber: 18 }),
792
+ makeClock(),
793
+ );
794
+ const plain: ReplaySignal | null = fromLogRow(
795
+ makeLog({ id: "log-5", atMs: 1, body: "hello" }),
796
+ makeClock(),
797
+ );
798
+
799
+ expect(numbered?.severity).toBe("error");
800
+ expect(plain?.severity).toBe("info");
801
+ expect(plain?.title).toBe("hello");
802
+ expect(plain?.subtitle).toBeUndefined();
803
+ });
804
+
805
+ it("returns null for a row with no id or no time", () => {
806
+ const noTime: Log = new Log();
807
+
808
+ noTime.id = new ObjectID("log-6");
809
+
810
+ const noId: Log = new Log();
811
+
812
+ noId.time = new Date(START_UNIX_MS);
813
+
814
+ expect(fromLogRow(noTime, makeClock())).toBeNull();
815
+ expect(fromLogRow(noId, makeClock())).toBeNull();
816
+ });
817
+ });
818
+
819
+ describe("fromExceptionRow", () => {
820
+ it("maps to a server-error with fingerprint, instance, trace and span links", () => {
821
+ const signal: ReplaySignal | null = fromExceptionRow(
822
+ makeException({
823
+ id: "exc-1",
824
+ atMs: 9_000,
825
+ exceptionType: "TypeError",
826
+ message: "Cannot read properties of undefined",
827
+ fingerprint: "fp-1",
828
+ traceId: "trace-a",
829
+ spanId: "span-a",
830
+ stackTrace: "TypeError: ...\n at handler",
831
+ serviceId: "svc-1",
832
+ }),
833
+ makeClock(ANCHORED, { "svc-1": "orders-svc" }),
834
+ );
835
+
836
+ expect(signal?.id).toBe("exc:exc-1");
837
+ expect(signal?.kind).toBe("server-error");
838
+ expect(signal?.severity).toBe("error");
839
+ expect(signal?.offsetMs).toBe(9_000 + ANCHORED.deltaMs);
840
+ expect(signal?.alignment).toBe("anchored");
841
+ expect(signal?.title).toBe(
842
+ "TypeError: Cannot read properties of undefined",
843
+ );
844
+ expect(signal?.subtitle).toBe("orders-svc");
845
+ expect(signal?.links).toEqual({
846
+ exceptionInstanceId: "exc-1",
847
+ exceptionFingerprint: "fp-1",
848
+ traceId: "trace-a",
849
+ spanId: "span-a",
850
+ });
851
+
852
+ const detail: ReplayServerErrorSignalDetail =
853
+ signal?.detail as ReplayServerErrorSignalDetail;
854
+
855
+ expect(detail.stackTrace).toContain("at handler");
856
+ expect(detail.fingerprint).toBe("fp-1");
857
+ expect(detail.baselineOffsetMs).toBe(9_000);
858
+ });
859
+
860
+ it("uses the message alone when there is no type, and 'Exception' when there is neither", () => {
861
+ const messageOnly: ReplaySignal | null = fromExceptionRow(
862
+ makeException({ id: "exc-2", atMs: 1, message: "boom" }),
863
+ makeClock(),
864
+ );
865
+ const bare: ReplaySignal | null = fromExceptionRow(
866
+ makeException({ id: "exc-3", atMs: 1, message: "" }),
867
+ makeClock(),
868
+ );
869
+
870
+ expect(messageOnly?.title).toBe("boom");
871
+ expect(bare?.title).toBe("Exception");
872
+ expect(bare?.links).toEqual({ exceptionInstanceId: "exc-3" });
873
+ });
874
+
875
+ it("returns null without an id or a time", () => {
876
+ const instance: ExceptionInstance = new ExceptionInstance();
877
+
878
+ instance.message = "x";
879
+
880
+ expect(fromExceptionRow(instance, makeClock())).toBeNull();
881
+ });
882
+ });
883
+
884
+ describe("spans and traces", () => {
885
+ it("fromSpanRow yields one trace row spanning the span's duration", () => {
886
+ const signal: ReplaySignal | null = fromSpanRow(
887
+ makeSpan({
888
+ spanId: "s1",
889
+ traceId: "t1",
890
+ atMs: 5_000,
891
+ durationMs: 250,
892
+ name: "GET /api/orders",
893
+ serviceId: "svc-1",
894
+ }),
895
+ makeClock(UNANCHORED, { "svc-1": "orders-svc" }),
896
+ );
897
+
898
+ expect(signal?.id).toBe("span:s1");
899
+ expect(signal?.kind).toBe("span");
900
+ expect(signal?.offsetMs).toBe(5_000);
901
+ expect(signal?.endOffsetMs).toBe(5_250);
902
+ expect(signal?.title).toBe("GET /api/orders");
903
+ expect(signal?.subtitle).toBe("orders-svc · 250ms · 1 span");
904
+ expect(signal?.links).toEqual({ traceId: "t1", spanId: "s1" });
905
+ expect(signal?.severity).toBe("info");
906
+ });
907
+
908
+ it("groupSpansIntoTraces makes one row per trace with a waterfall and an end offset", () => {
909
+ const rows: Array<Span> = [
910
+ makeSpan({
911
+ spanId: "child-b",
912
+ traceId: "t1",
913
+ atMs: 5_120,
914
+ durationMs: 80,
915
+ parentSpanId: "child-a",
916
+ status: SpanStatus.Error,
917
+ }),
918
+ makeSpan({
919
+ spanId: "root",
920
+ traceId: "t1",
921
+ atMs: 5_000,
922
+ durationMs: 300,
923
+ name: "POST /checkout",
924
+ serviceId: "svc-1",
925
+ }),
926
+ makeSpan({
927
+ spanId: "child-a",
928
+ traceId: "t1",
929
+ atMs: 5_100,
930
+ durationMs: 150,
931
+ parentSpanId: "root",
932
+ }),
933
+ makeSpan({
934
+ spanId: "other",
935
+ traceId: "t2",
936
+ atMs: 1_000,
937
+ durationMs: 10,
938
+ name: "GET /health",
939
+ }),
940
+ ];
941
+ const signals: Array<ReplaySignal> = groupSpansIntoTraces(
942
+ rows,
943
+ makeClock(UNANCHORED, { "svc-1": "checkout-svc" }),
944
+ );
945
+
946
+ expect(signals).toHaveLength(2);
947
+ /* Sorted by the earliest span of each trace. */
948
+ expect(signals[0]?.links.traceId).toBe("t2");
949
+
950
+ const trace: ReplaySignal = signals[1] as ReplaySignal;
951
+ const detail: ReplaySpanSignalDetail =
952
+ trace.detail as ReplaySpanSignalDetail;
953
+
954
+ expect(trace.id).toBe("span:root");
955
+ expect(trace.offsetMs).toBe(5_000);
956
+ expect(trace.endOffsetMs).toBe(5_300);
957
+ expect(trace.title).toBe("POST /checkout");
958
+ expect(trace.subtitle).toBe("checkout-svc · 300ms · 3 spans");
959
+ expect(trace.severity).toBe("error");
960
+ expect(detail.spanCount).toBe(3);
961
+ expect(detail.errorSpanCount).toBe(1);
962
+ expect(detail.hasError).toBe(true);
963
+ expect(detail.rootSpanId).toBe("root");
964
+ expect(detail.isWaterfallTruncated).toBe(false);
965
+ expect(
966
+ detail.spans.map(
967
+ (span: ReplayTraceWaterfallSpan): [string, number, number] => {
968
+ return [span.spanId, span.depth, span.startOffsetMs];
969
+ },
970
+ ),
971
+ ).toEqual([
972
+ ["root", 0, 0],
973
+ ["child-a", 1, 100],
974
+ ["child-b", 2, 120],
975
+ ]);
976
+ expect(detail.spans[2]?.hasError).toBe(true);
977
+ expect(detail.spans[1]?.sessionOffsetMs).toBe(5_100);
978
+ });
979
+
980
+ it("uses the earliest span as the root when the real root was not ingested", () => {
981
+ const signals: Array<ReplaySignal> = groupSpansIntoTraces(
982
+ [
983
+ makeSpan({
984
+ spanId: "late",
985
+ traceId: "t1",
986
+ atMs: 2_000,
987
+ durationMs: 5,
988
+ parentSpanId: "missing-root",
989
+ }),
990
+ makeSpan({
991
+ spanId: "early",
992
+ traceId: "t1",
993
+ atMs: 1_000,
994
+ durationMs: 5,
995
+ parentSpanId: "missing-root",
996
+ }),
997
+ ],
998
+ makeClock(),
999
+ );
1000
+
1001
+ expect(signals[0]?.id).toBe("span:early");
1002
+ expect((signals[0]?.detail as ReplaySpanSignalDetail).spans[0]?.depth).toBe(
1003
+ 0,
1004
+ );
1005
+ });
1006
+
1007
+ it("caps the waterfall at the max span count and says so", () => {
1008
+ const rows: Array<Span> = [];
1009
+
1010
+ for (let i: number = 0; i < REPLAY_TRACE_WATERFALL_MAX_SPANS + 5; i++) {
1011
+ rows.push(
1012
+ makeSpan({
1013
+ spanId: `s${i}`,
1014
+ traceId: "t1",
1015
+ atMs: 1_000 + i,
1016
+ durationMs: 1,
1017
+ parentSpanId: i === 0 ? undefined : "s0",
1018
+ }),
1019
+ );
1020
+ }
1021
+
1022
+ const detail: ReplaySpanSignalDetail = groupSpansIntoTraces(
1023
+ rows,
1024
+ makeClock(),
1025
+ )[0]?.detail as ReplaySpanSignalDetail;
1026
+
1027
+ expect(detail.spanCount).toBe(REPLAY_TRACE_WATERFALL_MAX_SPANS + 5);
1028
+ expect(detail.spans).toHaveLength(REPLAY_TRACE_WATERFALL_MAX_SPANS);
1029
+ expect(detail.isWaterfallTruncated).toBe(true);
1030
+ });
1031
+
1032
+ it("skips spans without a trace id or a start time", () => {
1033
+ const orphan: Span = new Span();
1034
+
1035
+ orphan.spanId = "x";
1036
+
1037
+ expect(groupSpansIntoTraces([orphan], makeClock())).toEqual([]);
1038
+ expect(fromSpanRow(orphan, makeClock())).toBeNull();
1039
+ });
1040
+
1041
+ it("shifts trace rows by the anchoring delta when anchored", () => {
1042
+ const signal: ReplaySignal | null = fromSpanRow(
1043
+ makeSpan({ spanId: "s1", traceId: "t1", atMs: 5_000, durationMs: 100 }),
1044
+ makeClock(ANCHORED),
1045
+ );
1046
+
1047
+ expect(signal?.offsetMs).toBe(5_000 + ANCHORED.deltaMs);
1048
+ expect(signal?.endOffsetMs).toBe(5_100 + ANCHORED.deltaMs);
1049
+ });
1050
+ });
1051
+
1052
+ describe("mergeSignals", () => {
1053
+ it("orders by offset, then recording before telemetry, then input order", () => {
1054
+ const merged: Array<ReplaySignal> = mergeSignals(
1055
+ [
1056
+ makeSignal({ id: "log:b", source: "telemetry", offsetMs: 100 }),
1057
+ makeSignal({ id: "rec:1:1", source: "recording", offsetMs: 300 }),
1058
+ ],
1059
+ [
1060
+ makeSignal({ id: "rec:1:0", source: "recording", offsetMs: 100 }),
1061
+ makeSignal({ id: "log:a", source: "telemetry", offsetMs: 100 }),
1062
+ makeSignal({ id: "rec:0:0", source: "recording", offsetMs: 50 }),
1063
+ ],
1064
+ );
1065
+
1066
+ expect(
1067
+ merged.map((signal: ReplaySignal): string => {
1068
+ return signal.id;
1069
+ }),
1070
+ ).toEqual(["rec:0:0", "rec:1:0", "log:b", "log:a", "rec:1:1"]);
1071
+ });
1072
+
1073
+ it("does not mutate its inputs and accepts no lists", () => {
1074
+ const input: Array<ReplaySignal> = [
1075
+ makeSignal({ id: "b", offsetMs: 2 }),
1076
+ makeSignal({ id: "a", offsetMs: 1 }),
1077
+ ];
1078
+
1079
+ mergeSignals(input);
1080
+
1081
+ expect(input[0]?.id).toBe("b");
1082
+ expect(mergeSignals()).toEqual([]);
1083
+ });
1084
+ });
1085
+
1086
+ describe("getActiveSignalIndex", () => {
1087
+ const signals: Array<ReplaySignal> = [
1088
+ makeSignal({ id: "a", offsetMs: 1000 }),
1089
+ makeSignal({ id: "b", offsetMs: 2000 }),
1090
+ makeSignal({ id: "c", offsetMs: 3000 }),
1091
+ ];
1092
+
1093
+ it("returns the last row the playhead has passed, -1 before the first (old helper parity)", () => {
1094
+ expect(getActiveSignalIndex(signals, 0)).toBe(-1);
1095
+ expect(getActiveSignalIndex(signals, 999)).toBe(-1);
1096
+ expect(getActiveSignalIndex(signals, 1000)).toBe(0);
1097
+ expect(getActiveSignalIndex(signals, 2500)).toBe(1);
1098
+ expect(getActiveSignalIndex(signals, 99_999)).toBe(2);
1099
+ expect(getActiveSignalIndex([], 5)).toBe(-1);
1100
+ });
1101
+
1102
+ it("keeps the clicked row active while the playhead sits in its pre-roll window", () => {
1103
+ /* Row click seeks to offset - pre-roll; the clicked row must not dim. */
1104
+ const seekTarget: number = 3000 - REPLAY_SIGNAL_SEEK_PRE_ROLL_MS;
1105
+
1106
+ expect(getActiveSignalIndex(signals, seekTarget)).toBe(1);
1107
+ expect(getActiveSignalIndex(signals, seekTarget, "c")).toBe(2);
1108
+ /* Once the playhead moves past the row, the plain rule applies again. */
1109
+ expect(getActiveSignalIndex(signals, 3000, "c")).toBe(2);
1110
+ expect(getActiveSignalIndex(signals, 1500, "c")).toBe(0);
1111
+ /* An unknown selection changes nothing. */
1112
+ expect(getActiveSignalIndex(signals, seekTarget, "zzz")).toBe(1);
1113
+ });
1114
+
1115
+ it("isSignalActiveAt treats traces as live for their whole duration", () => {
1116
+ const trace: ReplaySignal = makeSignal({
1117
+ id: "span:1",
1118
+ kind: "span",
1119
+ offsetMs: 1000,
1120
+ endOffsetMs: 1500,
1121
+ });
1122
+ const point: ReplaySignal = makeSignal({ id: "rec:0:0", offsetMs: 1000 });
1123
+
1124
+ expect(isSignalActiveAt(trace, 999)).toBe(false);
1125
+ expect(isSignalActiveAt(trace, 1000)).toBe(true);
1126
+ expect(isSignalActiveAt(trace, 1400)).toBe(true);
1127
+ expect(isSignalActiveAt(trace, 1501)).toBe(false);
1128
+ expect(isSignalActiveAt(point, 1000)).toBe(true);
1129
+ expect(isSignalActiveAt(point, 1001)).toBe(false);
1130
+ });
1131
+ });
1132
+
1133
+ describe("cross-references", () => {
1134
+ it("indexes trace rows by trace id and finds error logs on a trace", () => {
1135
+ const trace: ReplaySignal = makeSignal({
1136
+ id: "span:root",
1137
+ kind: "span",
1138
+ source: "telemetry",
1139
+ links: { traceId: "t1", spanId: "root" },
1140
+ });
1141
+ const errorLog: ReplaySignal = makeSignal({
1142
+ id: "log:1",
1143
+ kind: "log",
1144
+ source: "telemetry",
1145
+ severity: "error",
1146
+ links: { traceId: "t1", logId: "1" },
1147
+ });
1148
+ const infoLog: ReplaySignal = makeSignal({
1149
+ id: "log:2",
1150
+ kind: "log",
1151
+ source: "telemetry",
1152
+ severity: "info",
1153
+ links: { traceId: "t1", logId: "2" },
1154
+ });
1155
+ const otherError: ReplaySignal = makeSignal({
1156
+ id: "log:3",
1157
+ kind: "log",
1158
+ source: "telemetry",
1159
+ severity: "error",
1160
+ links: { traceId: "t2", logId: "3" },
1161
+ });
1162
+ const all: Array<ReplaySignal> = [trace, errorLog, infoLog, otherError];
1163
+
1164
+ expect(indexTraceSignalsByTraceId(all).get("t1")).toBe(trace);
1165
+ expect(indexTraceSignalsByTraceId(all).has("t2")).toBe(false);
1166
+ expect(findErrorLogsForTrace(all, "t1")).toEqual([errorLog]);
1167
+ expect(findErrorLogsForTrace(all, "")).toEqual([]);
1168
+ });
1169
+
1170
+ it("pairs a client error with the nearest server exception of the same message within 2s", () => {
1171
+ const client: ReplaySignal = fromTimelineEvent(
1172
+ makeEvent("error", {
1173
+ id: "rec:0:0",
1174
+ offsetMs: 10_000,
1175
+ errorKind: "error",
1176
+ message: "Cannot read properties of undefined",
1177
+ }),
1178
+ { startTimeUnixMs: null },
1179
+ );
1180
+ const near: ReplaySignal = fromExceptionRow(
1181
+ makeException({
1182
+ id: "near",
1183
+ atMs: 11_500,
1184
+ exceptionType: "TypeError",
1185
+ message: "Cannot read properties of undefined",
1186
+ }),
1187
+ makeClock(),
1188
+ ) as ReplaySignal;
1189
+ const nearer: ReplaySignal = fromExceptionRow(
1190
+ makeException({
1191
+ id: "nearer",
1192
+ atMs: 9_600,
1193
+ message: "cannot read properties of undefined",
1194
+ }),
1195
+ makeClock(),
1196
+ ) as ReplaySignal;
1197
+ const far: ReplaySignal = fromExceptionRow(
1198
+ makeException({
1199
+ id: "far",
1200
+ atMs: 13_000,
1201
+ message: "Cannot read properties of undefined",
1202
+ }),
1203
+ makeClock(),
1204
+ ) as ReplaySignal;
1205
+ const different: ReplaySignal = fromExceptionRow(
1206
+ makeException({ id: "different", atMs: 10_000, message: "other" }),
1207
+ makeClock(),
1208
+ ) as ReplaySignal;
1209
+
1210
+ const pairs: Array<ReplayErrorPair> = pairClientAndServerErrors([
1211
+ client,
1212
+ near,
1213
+ nearer,
1214
+ far,
1215
+ different,
1216
+ ]);
1217
+
1218
+ expect(pairs).toEqual([
1219
+ { clientSignalId: "rec:0:0", serverSignalId: "exc:nearer", gapMs: -400 },
1220
+ ]);
1221
+
1222
+ const index: Map<string, string> = buildErrorCounterpartIndex(pairs);
1223
+
1224
+ expect(index.get("rec:0:0")).toBe("exc:nearer");
1225
+ expect(index.get("exc:nearer")).toBe("rec:0:0");
1226
+ expect(index.has("exc:near")).toBe(false);
1227
+ });
1228
+
1229
+ it("matches a client message against 'Type: message' on the server side", () => {
1230
+ const client: ReplaySignal = fromTimelineEvent(
1231
+ makeEvent("error", {
1232
+ offsetMs: 1_000,
1233
+ errorKind: "error",
1234
+ message: "TypeError: boom",
1235
+ }),
1236
+ { startTimeUnixMs: null },
1237
+ );
1238
+ const server: ReplaySignal = fromExceptionRow(
1239
+ makeException({
1240
+ id: "s",
1241
+ atMs: 1_200,
1242
+ exceptionType: "TypeError",
1243
+ message: "boom",
1244
+ }),
1245
+ makeClock(),
1246
+ ) as ReplaySignal;
1247
+
1248
+ expect(pairClientAndServerErrors([client, server])).toHaveLength(1);
1249
+ expect(pairClientAndServerErrors([client])).toEqual([]);
1250
+ });
1251
+
1252
+ it("finds the first client error shortly after an interaction", () => {
1253
+ const click: ReplaySignal = makeSignal({
1254
+ id: "rec:0:0",
1255
+ kind: "interaction",
1256
+ offsetMs: 5_000,
1257
+ });
1258
+ const before: ReplaySignal = makeSignal({
1259
+ id: "rec:0:1",
1260
+ kind: "client-error",
1261
+ offsetMs: 4_900,
1262
+ });
1263
+ const after: ReplaySignal = makeSignal({
1264
+ id: "rec:0:2",
1265
+ kind: "client-error",
1266
+ offsetMs: 5_400,
1267
+ });
1268
+ const later: ReplaySignal = makeSignal({
1269
+ id: "rec:0:3",
1270
+ kind: "client-error",
1271
+ offsetMs: 5_900,
1272
+ });
1273
+ const tooLate: ReplaySignal = makeSignal({
1274
+ id: "rec:0:4",
1275
+ kind: "client-error",
1276
+ offsetMs: 8_000,
1277
+ });
1278
+
1279
+ expect(
1280
+ findErrorAfterInteraction([later, tooLate, before, after], click)?.id,
1281
+ ).toBe("rec:0:2");
1282
+ expect(findErrorAfterInteraction([before, tooLate], click)).toBeNull();
1283
+ });
1284
+ });
1285
+
1286
+ describe("formatters", () => {
1287
+ it("formats bytes and durations for row meta", () => {
1288
+ expect(formatSignalBytes(830)).toBe("830B");
1289
+ expect(formatSignalBytes(1229)).toBe("1.2KB");
1290
+ expect(formatSignalBytes(3.4 * 1024 * 1024)).toBe("3.4MB");
1291
+ expect(formatSignalBytes(-1)).toBe("");
1292
+ expect(formatSignalDuration(220)).toBe("220ms");
1293
+ expect(formatSignalDuration(1234)).toBe("1.2s");
1294
+ expect(formatSignalDuration(125_000)).toBe("2m 5s");
1295
+ expect(formatSignalDuration(Number.NaN)).toBe("");
1296
+ });
1297
+
1298
+ it("splits absolute urls and leaves relative ones whole", () => {
1299
+ expect(splitSignalUrl("https://a.com/p?q=1#h")).toEqual({
1300
+ origin: "https://a.com",
1301
+ path: "/p?q=1#h",
1302
+ });
1303
+ expect(splitSignalUrl("/relative/path")).toEqual({
1304
+ origin: "",
1305
+ path: "/relative/path",
1306
+ });
1307
+ });
1308
+ });
1309
+
1310
+ /*
1311
+ * The recorder's own vocabulary for "this is not a failure": a cancelled
1312
+ * request, a capture-cap marker, a subresource that failed to load and a
1313
+ * repeat of an error already recorded. Every one of these used to render
1314
+ * as a red failed request or an ordinary uncaught error (ux-05, ux-06).
1315
+ */
1316
+ describe("ReplaySignals recorder notices that are not failures", () => {
1317
+ function eventWithExtras(
1318
+ kind: ReplayTimelineEventKind,
1319
+ overrides: Partial<ReplayTimelineEvent>,
1320
+ extras: Record<string, unknown>,
1321
+ ): ReplayTimelineEvent {
1322
+ return {
1323
+ ...makeEvent(kind, overrides),
1324
+ ...extras,
1325
+ } as ReplayTimelineEvent;
1326
+ }
1327
+
1328
+ it("renders an aborted request as cancelled, never as an error", () => {
1329
+ const signal: ReplaySignal = fromTimelineEvent(
1330
+ eventWithExtras(
1331
+ "network",
1332
+ {
1333
+ method: "get",
1334
+ url: "https://api.example.com/search?q=ab",
1335
+ status: 0,
1336
+ durationMs: 40,
1337
+ isError: false,
1338
+ },
1339
+ { aborted: true },
1340
+ ),
1341
+ { startTimeUnixMs: null },
1342
+ );
1343
+ const detail: ReplayNetworkSignalDetail =
1344
+ signal.detail as ReplayNetworkSignalDetail;
1345
+
1346
+ expect(signal.title).toBe("GET cancelled /search?q=ab");
1347
+ expect(signal.severity).toBe("info");
1348
+ expect(detail.aborted).toBe(true);
1349
+ expect(detail.isError).toBe(false);
1350
+ /* An abort is status 0, but nothing failed before a response. */
1351
+ expect(detail.failedBeforeResponse).toBe(false);
1352
+ });
1353
+
1354
+ it("renders the network cap marker as a notice, not an empty failed request", () => {
1355
+ const signal: ReplaySignal = fromTimelineEvent(
1356
+ eventWithExtras(
1357
+ "network",
1358
+ { method: "", url: "", status: 0 },
1359
+ { isCapMarker: true },
1360
+ ),
1361
+ { startTimeUnixMs: null },
1362
+ );
1363
+ const detail: ReplayNetworkSignalDetail =
1364
+ signal.detail as ReplayNetworkSignalDetail;
1365
+
1366
+ expect(signal.title).toBe(
1367
+ `Network capture stopped after ${REPLAY_NETWORK_CAPTURE_CAP} requests`,
1368
+ );
1369
+ expect(signal.title).not.toContain("failed");
1370
+ expect(signal.severity).toBe("info");
1371
+ expect(detail.isCapMarker).toBe(true);
1372
+ expect(detail.isError).toBe(false);
1373
+ });
1374
+
1375
+ it("names a failed resource load and keeps it out of the error stepping", () => {
1376
+ const signal: ReplaySignal = fromTimelineEvent(
1377
+ eventWithExtras(
1378
+ "error",
1379
+ {
1380
+ message: "Resource failed to load: <img>",
1381
+ source: "https://cdn.example.com/logo.png",
1382
+ },
1383
+ { errorKind: "resource", tagName: "img" },
1384
+ ),
1385
+ { startTimeUnixMs: null },
1386
+ );
1387
+ const detail: ReplayClientErrorSignalDetail =
1388
+ signal.detail as ReplayClientErrorSignalDetail;
1389
+
1390
+ expect(signal.title).toBe("Resource failed to load: <img> /logo.png");
1391
+ expect(signal.severity).toBe("warn");
1392
+ expect(detail.kind).toBe("resource");
1393
+ expect(detail.tagName).toBe("img");
1394
+ expect(isSteppableClientError(signal)).toBe(false);
1395
+ });
1396
+
1397
+ it("quantifies a repeat marker and does not step through it as a new error", () => {
1398
+ const signal: ReplaySignal = fromTimelineEvent(
1399
+ eventWithExtras(
1400
+ "error",
1401
+ { message: "TypeError: boom", errorKind: "error" },
1402
+ { isRepeat: true, occurrences: 2400 },
1403
+ ),
1404
+ { startTimeUnixMs: null },
1405
+ );
1406
+ const detail: ReplayClientErrorSignalDetail =
1407
+ signal.detail as ReplayClientErrorSignalDetail;
1408
+
1409
+ expect(signal.subtitle).toContain("seen 2400 times");
1410
+ expect(detail.isRepeat).toBe(true);
1411
+ expect(detail.occurrences).toBe(2400);
1412
+ expect(isSteppableClientError(signal)).toBe(false);
1413
+ });
1414
+
1415
+ it("renders the error cap marker as a recorder notice", () => {
1416
+ const signal: ReplaySignal = fromTimelineEvent(
1417
+ eventWithExtras(
1418
+ "error",
1419
+ {
1420
+ message:
1421
+ "Error capture stopped after 100 distinct errors in this session; later errors were not recorded.",
1422
+ errorKind: "error",
1423
+ },
1424
+ { isCapMarker: true },
1425
+ ),
1426
+ { startTimeUnixMs: null },
1427
+ );
1428
+ const detail: ReplayClientErrorSignalDetail =
1429
+ signal.detail as ReplayClientErrorSignalDetail;
1430
+
1431
+ expect(signal.severity).toBe("warn");
1432
+ expect(signal.subtitle).toBe("recorder cap");
1433
+ expect(detail.isCapMarker).toBe(true);
1434
+ expect(isSteppableClientError(signal)).toBe(false);
1435
+ });
1436
+
1437
+ it("keeps a real uncaught error steppable and unchanged", () => {
1438
+ const signal: ReplaySignal = fromTimelineEvent(
1439
+ makeEvent("error", {
1440
+ message: "TypeError: boom",
1441
+ errorKind: "error",
1442
+ source: "https://app.example.com/app.js",
1443
+ lineNumber: 12,
1444
+ columnNumber: 5,
1445
+ }),
1446
+ { startTimeUnixMs: null },
1447
+ );
1448
+
1449
+ expect(signal.severity).toBe("error");
1450
+ expect(signal.subtitle).toBe("uncaught error · /app.js:12:5");
1451
+ expect(isSteppableClientError(signal)).toBe(true);
1452
+ });
1453
+
1454
+ it("never pairs a resource failure with a server exception", () => {
1455
+ const resource: ReplaySignal = fromTimelineEvent(
1456
+ eventWithExtras(
1457
+ "error",
1458
+ { message: "boom", offsetMs: 1000 },
1459
+ { errorKind: "resource" },
1460
+ ),
1461
+ { startTimeUnixMs: null },
1462
+ );
1463
+ const server: ReplaySignal = {
1464
+ id: "exc:1",
1465
+ kind: "server-error",
1466
+ source: "telemetry",
1467
+ offsetMs: 1200,
1468
+ severity: "error",
1469
+ title: "boom",
1470
+ links: {},
1471
+ detail: { message: "boom", exceptionType: null },
1472
+ alignment: "unanchored",
1473
+ };
1474
+
1475
+ expect(pairClientAndServerErrors([resource, server])).toEqual([]);
1476
+ });
1477
+ });
1478
+
1479
+ /* ux-17: kebab-case library values never reach a row or a detail. */
1480
+ describe("ReplaySignals performance wording", () => {
1481
+ it("spells out a web vital's rating", () => {
1482
+ const signal: ReplaySignal = fromTimelineEvent(
1483
+ makeEvent("performance", {
1484
+ performanceKind: "web-vital",
1485
+ metric: "LCP",
1486
+ value: 4800,
1487
+ rating: "needs-improvement",
1488
+ }),
1489
+ { startTimeUnixMs: null },
1490
+ );
1491
+
1492
+ expect(signal.title).toBe("LCP 4.8s needs improvement");
1493
+ expect(signal.title).not.toContain("needs-improvement");
1494
+ expect(formatVitalRating("needs-improvement")).toBe("needs improvement");
1495
+ expect(formatVitalRating(null)).toBe("");
1496
+ });
1497
+
1498
+ it("names the measure of a budget row in words", () => {
1499
+ const signal: ReplaySignal = fromTimelineEvent(
1500
+ makeEvent("performance", {
1501
+ performanceKind: "long-task",
1502
+ durationMs: 320,
1503
+ budgetMs: 200,
1504
+ }),
1505
+ { startTimeUnixMs: null },
1506
+ );
1507
+ const detail: ReplayPerformanceSignalDetail =
1508
+ signal.detail as ReplayPerformanceSignalDetail;
1509
+
1510
+ expect(signal.title).toBe("Long task 320ms (budget 200ms)");
1511
+ expect(formatPerformanceMeasure(detail)).toBe("Long task");
1512
+ });
1513
+ });
1514
+
1515
+ /*
1516
+ * integration-003: a span cross-link carries the id of the span that was
1517
+ * clicked, while the Traces tab has one row per trace keyed by its ROOT
1518
+ * span, so matching by row id alone selected nothing.
1519
+ */
1520
+ describe("ReplaySignals findSignalMatch", () => {
1521
+ const TRACE: string = "4bf92f3577b34da6a3ce929d0e0e4736";
1522
+
1523
+ function traceRow(): ReplaySignal {
1524
+ const rows: Array<ReplaySignal> = groupSpansIntoTraces(
1525
+ [
1526
+ makeSpan({
1527
+ spanId: "root-a",
1528
+ traceId: TRACE,
1529
+ atMs: 4000,
1530
+ durationMs: 900,
1531
+ }),
1532
+ makeSpan({
1533
+ spanId: "child-b",
1534
+ traceId: TRACE,
1535
+ atMs: 4300,
1536
+ durationMs: 200,
1537
+ parentSpanId: "root-a",
1538
+ }),
1539
+ ],
1540
+ makeClock(ANCHORED),
1541
+ );
1542
+
1543
+ return rows[0] as ReplaySignal;
1544
+ }
1545
+
1546
+ it("matches a row by its own id", () => {
1547
+ const row: ReplaySignal = traceRow();
1548
+
1549
+ expect(findSignalMatch([row], row.id)).toEqual({
1550
+ signal: row,
1551
+ offsetMs: row.offsetMs,
1552
+ });
1553
+ });
1554
+
1555
+ it("resolves a child span id to the trace row that contains it", () => {
1556
+ const row: ReplaySignal = traceRow();
1557
+ const child: ReplayTraceWaterfallSpan | undefined = (
1558
+ row.detail as ReplaySpanSignalDetail
1559
+ ).spans.find((span: ReplayTraceWaterfallSpan): boolean => {
1560
+ return span.spanId === "child-b";
1561
+ });
1562
+
1563
+ const match: ReplaySignalMatch | null = findSignalMatch(
1564
+ [row],
1565
+ "span:child-b",
1566
+ );
1567
+
1568
+ expect(match?.signal.id).toBe(row.id);
1569
+ /* The moment is the CHILD's start, not the trace's. */
1570
+ expect(match?.offsetMs).toBe(child?.sessionOffsetMs);
1571
+ expect(match?.offsetMs).not.toBe(row.offsetMs);
1572
+ });
1573
+
1574
+ it("returns null for an id no row answers to", () => {
1575
+ expect(findSignalMatch([traceRow()], "span:not-here")).toBeNull();
1576
+ expect(findSignalMatch([traceRow()], "log:missing")).toBeNull();
1577
+ expect(findSignalMatch([traceRow()], "")).toBeNull();
1578
+ });
1579
+ });