@oneuptime/common 13.0.1 → 13.0.4

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 (781) hide show
  1. package/Models/AnalyticsModels/RumSession.ts +34 -0
  2. package/Models/DatabaseModels/AlertEpisodeLabelRule.ts +2 -2
  3. package/Models/DatabaseModels/AlertEpisodeOnCallRule.ts +2 -2
  4. package/Models/DatabaseModels/AlertEpisodeOwnerRule.ts +2 -2
  5. package/Models/DatabaseModels/AlertEpisodePrivacyRule.ts +2 -2
  6. package/Models/DatabaseModels/AlertGroupingRule.ts +2 -2
  7. package/Models/DatabaseModels/AlertLabelRule.ts +2 -2
  8. package/Models/DatabaseModels/AlertOnCallRule.ts +2 -2
  9. package/Models/DatabaseModels/AlertOwnerRule.ts +2 -2
  10. package/Models/DatabaseModels/AlertPrivacyRule.ts +2 -2
  11. package/Models/DatabaseModels/AlertReminderRule.ts +6 -4
  12. package/Models/DatabaseModels/AutoRemediationRule.ts +2 -2
  13. package/Models/DatabaseModels/CephClusterLabelRule.ts +2 -2
  14. package/Models/DatabaseModels/CephClusterOwnerRule.ts +2 -2
  15. package/Models/DatabaseModels/CloudResourceLabelRule.ts +2 -2
  16. package/Models/DatabaseModels/CloudResourceOwnerRule.ts +2 -2
  17. package/Models/DatabaseModels/DashboardLabelRule.ts +2 -2
  18. package/Models/DatabaseModels/DashboardOwnerRule.ts +2 -2
  19. package/Models/DatabaseModels/DatabaseBaseModel/RelationOnlyRuleBaseModel.ts +11 -0
  20. package/Models/DatabaseModels/DatabaseBaseModel/RuleBaseModel.ts +44 -0
  21. package/Models/DatabaseModels/DockerHostLabelRule.ts +2 -2
  22. package/Models/DatabaseModels/DockerHostOwnerRule.ts +2 -2
  23. package/Models/DatabaseModels/DockerSwarmClusterLabelRule.ts +2 -2
  24. package/Models/DatabaseModels/DockerSwarmClusterOwnerRule.ts +2 -2
  25. package/Models/DatabaseModels/HostLabelRule.ts +2 -2
  26. package/Models/DatabaseModels/HostOwnerRule.ts +2 -2
  27. package/Models/DatabaseModels/IncidentEpisodeLabelRule.ts +2 -2
  28. package/Models/DatabaseModels/IncidentEpisodeOnCallRule.ts +2 -2
  29. package/Models/DatabaseModels/IncidentEpisodeOwnerRule.ts +2 -2
  30. package/Models/DatabaseModels/IncidentEpisodePrivacyRule.ts +2 -2
  31. package/Models/DatabaseModels/IncidentGroupingRule.ts +2 -2
  32. package/Models/DatabaseModels/IncidentLabelRule.ts +2 -2
  33. package/Models/DatabaseModels/IncidentOnCallRule.ts +2 -2
  34. package/Models/DatabaseModels/IncidentOwnerRule.ts +2 -2
  35. package/Models/DatabaseModels/IncidentPrivacyRule.ts +2 -2
  36. package/Models/DatabaseModels/IncidentReminderRule.ts +6 -4
  37. package/Models/DatabaseModels/IncidentSlaRule.ts +2 -2
  38. package/Models/DatabaseModels/IncomingCallPolicy.ts +3 -4
  39. package/Models/DatabaseModels/IncomingCallPolicyLabelRule.ts +2 -2
  40. package/Models/DatabaseModels/IncomingCallPolicyOwnerRule.ts +2 -2
  41. package/Models/DatabaseModels/IncomingCallPolicyPhoneNumber.ts +254 -0
  42. package/Models/DatabaseModels/Index.ts +2 -0
  43. package/Models/DatabaseModels/IoTFleetLabelRule.ts +2 -2
  44. package/Models/DatabaseModels/IoTFleetOwnerRule.ts +2 -2
  45. package/Models/DatabaseModels/KubernetesClusterLabelRule.ts +2 -2
  46. package/Models/DatabaseModels/KubernetesClusterOwnerRule.ts +2 -2
  47. package/Models/DatabaseModels/MonitorLabelRule.ts +2 -2
  48. package/Models/DatabaseModels/MonitorOwnerRule.ts +2 -2
  49. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +2 -2
  50. package/Models/DatabaseModels/NetworkDeviceLabelRule.ts +2 -2
  51. package/Models/DatabaseModels/NetworkDeviceOwnerRule.ts +2 -2
  52. package/Models/DatabaseModels/NetworkSiteAssignmentRule.ts +2 -2
  53. package/Models/DatabaseModels/OnCallDutyPolicyLabelRule.ts +2 -2
  54. package/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.ts +2 -2
  55. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.ts +2 -2
  56. package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.ts +2 -2
  57. package/Models/DatabaseModels/PodmanHostLabelRule.ts +2 -2
  58. package/Models/DatabaseModels/PodmanHostOwnerRule.ts +2 -2
  59. package/Models/DatabaseModels/ProxmoxClusterLabelRule.ts +2 -2
  60. package/Models/DatabaseModels/ProxmoxClusterOwnerRule.ts +2 -2
  61. package/Models/DatabaseModels/RumApplicationLabelRule.ts +2 -2
  62. package/Models/DatabaseModels/RumApplicationOwnerRule.ts +2 -2
  63. package/Models/DatabaseModels/RunbookLabelRule.ts +2 -2
  64. package/Models/DatabaseModels/RunbookOwnerRule.ts +2 -2
  65. package/Models/DatabaseModels/RunbookRule.ts +2 -2
  66. package/Models/DatabaseModels/ScheduledMaintenanceLabelRule.ts +2 -2
  67. package/Models/DatabaseModels/ScheduledMaintenanceOwnerRule.ts +2 -2
  68. package/Models/DatabaseModels/ScheduledMaintenanceReminderRule.ts +6 -4
  69. package/Models/DatabaseModels/ServerlessFunctionLabelRule.ts +2 -2
  70. package/Models/DatabaseModels/ServerlessFunctionOwnerRule.ts +2 -2
  71. package/Models/DatabaseModels/ServiceLabelRule.ts +2 -2
  72. package/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.ts +283 -4
  73. package/Models/DatabaseModels/ServiceOwnerRule.ts +2 -2
  74. package/Models/DatabaseModels/StatusPageLabelRule.ts +2 -2
  75. package/Models/DatabaseModels/StatusPageMonitorRule.ts +2 -2
  76. package/Models/DatabaseModels/StatusPageOwnerRule.ts +2 -2
  77. package/Models/DatabaseModels/VMwareVCenterLabelRule.ts +2 -2
  78. package/Models/DatabaseModels/VMwareVCenterOwnerRule.ts +2 -2
  79. package/Models/DatabaseModels/WorkflowLabelRule.ts +2 -2
  80. package/Models/DatabaseModels/WorkflowOwnerRule.ts +2 -2
  81. package/Server/API/TelemetryAPI.ts +323 -2
  82. package/Server/Infrastructure/Postgres/SchemaMigrations/1792400000000-AddSloBurnRateRuleIncidents.ts +111 -0
  83. package/Server/Infrastructure/Postgres/SchemaMigrations/1792500000000-AddConfigurableRuleCriteria.ts +644 -0
  84. package/Server/Infrastructure/Postgres/SchemaMigrations/1792600000000-AddIncomingCallPolicyPhoneNumbers.ts +72 -0
  85. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  86. package/Server/Middleware/MultipartFormData.ts +46 -21
  87. package/Server/Services/AlertEpisodeLabelRuleEngineService.ts +21 -0
  88. package/Server/Services/AlertEpisodeOnCallRuleEngineService.ts +21 -0
  89. package/Server/Services/AlertEpisodeOwnerRuleEngineService.ts +21 -0
  90. package/Server/Services/AlertEpisodePrivacyRuleEngineService.ts +21 -0
  91. package/Server/Services/AlertGroupingEngineService.ts +27 -0
  92. package/Server/Services/AlertLabelRuleEngineService.ts +27 -0
  93. package/Server/Services/AlertOnCallRuleEngineService.ts +27 -0
  94. package/Server/Services/AlertOwnerRuleEngineService.ts +27 -0
  95. package/Server/Services/AlertPrivacyRuleEngineService.ts +27 -0
  96. package/Server/Services/AlertReminderRuleService.ts +65 -33
  97. package/Server/Services/AutoRemediationRuleEngineService.ts +90 -8
  98. package/Server/Services/CephClusterLabelRuleEngineService.ts +20 -0
  99. package/Server/Services/CephClusterOwnerRuleEngineService.ts +20 -0
  100. package/Server/Services/CloudResourceLabelRuleEngineService.ts +20 -0
  101. package/Server/Services/CloudResourceOwnerRuleEngineService.ts +20 -0
  102. package/Server/Services/DashboardLabelRuleEngineService.ts +20 -0
  103. package/Server/Services/DashboardOwnerRuleEngineService.ts +20 -0
  104. package/Server/Services/DatabaseService.ts +409 -6
  105. package/Server/Services/DockerHostLabelRuleEngineService.ts +20 -0
  106. package/Server/Services/DockerHostOwnerRuleEngineService.ts +20 -0
  107. package/Server/Services/DockerSwarmClusterLabelRuleEngineService.ts +23 -0
  108. package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +23 -0
  109. package/Server/Services/HostLabelRuleEngineService.ts +13 -0
  110. package/Server/Services/HostOwnerRuleEngineService.ts +13 -0
  111. package/Server/Services/IncidentEpisodeLabelRuleEngineService.ts +21 -0
  112. package/Server/Services/IncidentEpisodeOnCallRuleEngineService.ts +21 -0
  113. package/Server/Services/IncidentEpisodeOwnerRuleEngineService.ts +21 -0
  114. package/Server/Services/IncidentEpisodePrivacyRuleEngineService.ts +21 -0
  115. package/Server/Services/IncidentGroupingEngineService.ts +64 -13
  116. package/Server/Services/IncidentLabelRuleEngineService.ts +64 -9
  117. package/Server/Services/IncidentOnCallRuleEngineService.ts +64 -10
  118. package/Server/Services/IncidentOwnerRuleEngineService.ts +64 -10
  119. package/Server/Services/IncidentPrivacyRuleEngineService.ts +64 -10
  120. package/Server/Services/IncidentReminderRuleService.ts +66 -34
  121. package/Server/Services/IncidentSlaRuleService.ts +55 -12
  122. package/Server/Services/IncomingCallPolicyLabelRuleEngineService.ts +20 -0
  123. package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +20 -0
  124. package/Server/Services/IncomingCallPolicyPhoneNumberService.ts +184 -0
  125. package/Server/Services/IncomingCallPolicyService.ts +276 -5
  126. package/Server/Services/Index.ts +2 -0
  127. package/Server/Services/IoTFleetLabelRuleEngineService.ts +20 -0
  128. package/Server/Services/IoTFleetOwnerRuleEngineService.ts +20 -0
  129. package/Server/Services/KubernetesClusterLabelRuleEngineService.ts +23 -0
  130. package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +23 -0
  131. package/Server/Services/LabelService.ts +50 -7
  132. package/Server/Services/MonitorLabelRuleEngineService.ts +20 -0
  133. package/Server/Services/MonitorOwnerRuleEngineService.ts +20 -0
  134. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +2 -0
  135. package/Server/Services/NetworkDeviceAutoImportRuleService.ts +93 -1
  136. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +26 -0
  137. package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +25 -0
  138. package/Server/Services/NetworkDeviceService.ts +2 -0
  139. package/Server/Services/NetworkSiteAssignmentRuleService.ts +90 -1
  140. package/Server/Services/NetworkSiteService.ts +242 -29
  141. package/Server/Services/NetworkSiteTypeService.ts +177 -77
  142. package/Server/Services/OnCallDutyPolicyLabelRuleEngineService.ts +20 -0
  143. package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +20 -0
  144. package/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.ts +22 -0
  145. package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +22 -0
  146. package/Server/Services/PodmanHostLabelRuleEngineService.ts +20 -0
  147. package/Server/Services/PodmanHostOwnerRuleEngineService.ts +20 -0
  148. package/Server/Services/ProjectCallSMSConfigService.ts +156 -7
  149. package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +25 -0
  150. package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +25 -0
  151. package/Server/Services/RumApplicationLabelRuleEngineService.ts +22 -0
  152. package/Server/Services/RumApplicationOwnerRuleEngineService.ts +22 -0
  153. package/Server/Services/RunbookLabelRuleEngineService.ts +20 -0
  154. package/Server/Services/RunbookOwnerRuleEngineService.ts +20 -0
  155. package/Server/Services/RunbookRuleEngineService.ts +18 -1
  156. package/Server/Services/ScheduledMaintenanceLabelRuleEngineService.ts +62 -10
  157. package/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.ts +62 -11
  158. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +48 -18
  159. package/Server/Services/ServerlessFunctionLabelRuleEngineService.ts +25 -0
  160. package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +25 -0
  161. package/Server/Services/ServiceLabelRuleEngineService.ts +20 -0
  162. package/Server/Services/ServiceLevelObjectiveBurnRateRuleService.ts +675 -12
  163. package/Server/Services/ServiceLevelObjectiveService.ts +55 -13
  164. package/Server/Services/ServiceOwnerRuleEngineService.ts +20 -0
  165. package/Server/Services/StatusPageLabelRuleEngineService.ts +20 -0
  166. package/Server/Services/StatusPageMonitorRuleEngineService.ts +31 -2
  167. package/Server/Services/StatusPageMonitorRuleService.ts +34 -1
  168. package/Server/Services/StatusPageOwnerRuleEngineService.ts +20 -0
  169. package/Server/Services/UserService.ts +23 -50
  170. package/Server/Services/VMwareVCenterLabelRuleEngineService.ts +25 -0
  171. package/Server/Services/VMwareVCenterOwnerRuleEngineService.ts +25 -0
  172. package/Server/Services/WorkflowLabelRuleEngineService.ts +20 -0
  173. package/Server/Services/WorkflowOwnerRuleEngineService.ts +20 -0
  174. package/Server/Static/Brand/README.md +10 -5
  175. package/Server/Static/Brand/favicons/oneuptime-up-v1.svg +8 -0
  176. package/Server/Types/Database/QueryHelper.ts +88 -0
  177. package/Server/Utils/AI/Toolbox/SloTools.ts +19 -2
  178. package/Server/Utils/IncomingCallPhoneNumber.ts +29 -16
  179. package/Server/Utils/Profiling.ts +13 -1
  180. package/Server/Utils/Rules/MonitorRuleCriteriaCache.ts +38 -0
  181. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +498 -0
  182. package/Server/Utils/Telemetry.ts +3 -1
  183. package/Server/Utils/VendorAssets.ts +3 -0
  184. package/Tests/App/Dashboard/AIInsightViewTriage.test.tsx +348 -0
  185. package/Tests/App/Dashboard/ArchiveResourceCard.test.tsx +476 -0
  186. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +25 -0
  187. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +449 -24
  188. package/Tests/App/Dashboard/DashboardPersistentSideMenuLayouts.test.tsx +702 -0
  189. package/Tests/App/Dashboard/ExceptionsLandingRedirect.test.tsx +63 -0
  190. package/Tests/App/Dashboard/FeedOrdering.test.ts +150 -0
  191. package/Tests/App/Dashboard/GoogleSecOpsConnectionsErrors.test.tsx +279 -100
  192. package/Tests/App/Dashboard/InventoryItemSideMenu.test.tsx +155 -0
  193. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +11 -0
  194. package/Tests/App/Dashboard/PasskeySettings.test.tsx +215 -71
  195. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +347 -0
  196. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +263 -0
  197. package/Tests/App/Dashboard/ResourceFeed.test.tsx +413 -17
  198. package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +138 -0
  199. package/Tests/App/Dashboard/SloDashboardTemplateWidgets.test.tsx +940 -0
  200. package/Tests/App/Dashboard/TelemetryIngestionKeyEdit.test.tsx +378 -0
  201. package/Tests/App/Dashboard/TelemetryIngestionKeyForm.test.tsx +735 -0
  202. package/Tests/App/Dashboard/TwoFactorStatus.test.tsx +275 -108
  203. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +13 -3
  204. package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +127 -4
  205. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +5 -4
  206. package/Tests/Models/DatabaseModels/IncomingCallPolicyPhoneNumber.test.ts +362 -0
  207. package/Tests/Models/DatabaseModels/RuleBaseModel.test.ts +69 -0
  208. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +181 -0
  209. package/Tests/Server/API/SessionReplayAPI.test.ts +1303 -145
  210. package/Tests/Server/API/UserTwoFactorSelfService.test.ts +407 -0
  211. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +611 -0
  212. package/Tests/Server/Middleware/MultipartFormData.test.ts +61 -3
  213. package/Tests/Server/Middleware/TelemetryIngestionDisabled.test.ts +211 -0
  214. package/Tests/Server/Services/AlertIncidentRuleCriteriaEngineServices.test.ts +651 -0
  215. package/Tests/Server/Services/AutoRemediationRuleEngineService.test.ts +79 -0
  216. package/Tests/Server/Services/DatabaseServiceRelationOnlyRuleRollout.test.ts +330 -0
  217. package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +522 -0
  218. package/Tests/Server/Services/IncidentSlaRuleCriteriaCorrelation.test.ts +99 -0
  219. package/Tests/Server/Services/IncomingCallPolicyPhoneNumberService.test.ts +1540 -0
  220. package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +179 -2
  221. package/Tests/Server/Services/NetworkSiteAssignmentRuleService.test.ts +170 -0
  222. package/Tests/Server/Services/NetworkSiteService.test.ts +559 -53
  223. package/Tests/Server/Services/NetworkSiteTypeService.test.ts +122 -55
  224. package/Tests/Server/Services/ReminderRuleCriteria.test.ts +173 -0
  225. package/Tests/Server/Services/ResourceRuleCriteriaEngineServices.test.ts +170 -0
  226. package/Tests/Server/Services/ScheduledMaintenanceRuleCriteriaCorrelation.test.ts +154 -0
  227. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleService.test.ts +2450 -470
  228. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +464 -121
  229. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +281 -0
  230. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +132 -0
  231. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +159 -14
  232. package/Tests/Server/Services/UserEmailChangePasswordResetExpiry.test.ts +6 -8
  233. package/Tests/Server/Services/UserTwoFactorAuthAdmin.test.ts +203 -34
  234. package/Tests/Server/Types/Database/QueryHelperJsonArrayObjects.test.ts +128 -0
  235. package/Tests/Server/Utils/AI/SloTools.test.ts +190 -1
  236. package/Tests/Server/Utils/AI/Toolbox/AIToolbox.test.ts +461 -0
  237. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +25 -0
  238. package/Tests/Server/Utils/Billing/BillingFailureNoticeThrottle.test.ts +359 -0
  239. package/Tests/Server/Utils/CodeRepository/GitHub/GitHubWebhookEvents.test.ts +318 -0
  240. package/Tests/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.test.ts +220 -0
  241. package/Tests/Server/Utils/Dashboard/SloTemplatePublicDashboard.test.ts +1043 -0
  242. package/Tests/Server/Utils/Execute.test.ts +302 -0
  243. package/Tests/Server/Utils/GracefulShutdown.test.ts +565 -0
  244. package/Tests/Server/Utils/IncomingCallPhoneNumber.test.ts +36 -20
  245. package/Tests/Server/Utils/LoggerCore.test.ts +597 -0
  246. package/Tests/Server/Utils/Monitor/MonitorTemplateUtil.test.ts +1109 -0
  247. package/Tests/Server/Utils/Monitor/SloTemplateMetricEmitterContract.test.ts +750 -0
  248. package/Tests/Server/Utils/ProductAnalytics.test.ts +404 -0
  249. package/Tests/Server/Utils/Profiling.test.ts +366 -0
  250. package/Tests/Server/Utils/Rules/MonitorRuleCriteriaCache.test.ts +99 -0
  251. package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +558 -0
  252. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +711 -0
  253. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +522 -0
  254. package/Tests/Server/Utils/VendorAssets.test.ts +17 -0
  255. package/Tests/Types/Dashboard/DashboardTemplateEditorialInvariants.test.ts +659 -0
  256. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +97 -0
  257. package/Tests/Types/Dashboard/SloDashboardTemplate.test.ts +1179 -0
  258. package/Tests/Types/Database/AccessControl/OwnedThrough.test.ts +136 -0
  259. package/Tests/Types/OnCallDutyPolicy/LayerUtilCorePaths.test.ts +1014 -0
  260. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +9 -6
  261. package/Tests/Types/Rules/RuleCriteriaFieldRegistry.test.ts +276 -0
  262. package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +180 -1
  263. package/Tests/Types/Telemetry/LlmConventions.test.ts +495 -0
  264. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +13 -6
  265. package/Tests/UI/Components/Alert.test.tsx +532 -93
  266. package/Tests/UI/Components/Feed.test.tsx +241 -0
  267. package/Tests/UI/Components/Forms/BasicFormStepNavigation.test.tsx +368 -0
  268. package/Tests/UI/Components/Forms/FormSummary.test.tsx +454 -0
  269. package/Tests/UI/Components/Forms/ModelFormJsonColumns.test.tsx +290 -0
  270. package/Tests/UI/Components/Forms/ModelFormRuleCriteria.test.tsx +331 -0
  271. package/Tests/UI/Components/IconOpticalSize.test.tsx +79 -31
  272. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +6 -6
  273. package/Tests/UI/Components/NavBar.test.tsx +91 -2
  274. package/Tests/UI/Components/PencilButton.test.tsx +211 -0
  275. package/Tests/UI/Components/RuleCriteria/RuleCriteriaBuilder.test.tsx +510 -0
  276. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +350 -0
  277. package/Tests/UI/Components/TableSortAndMobileColumns.test.tsx +50 -0
  278. package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +329 -0
  279. package/Tests/UI/Components/useFeedItems.test.tsx +107 -0
  280. package/Tests/UI/Rum/ChunkLoader.test.ts +401 -0
  281. package/Tests/UI/Rum/RecordingHealthCard.test.tsx +1 -1
  282. package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +7 -1
  283. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +2 -0
  284. package/Tests/UI/Rum/ReplayEngine.test.ts +1069 -5
  285. package/Tests/UI/Rum/ReplayHeader.test.tsx +579 -2
  286. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +23 -5
  287. package/Tests/UI/Rum/ReplayRail.test.tsx +143 -1
  288. package/Tests/UI/Rum/ReplayRenderBudget.test.tsx +338 -0
  289. package/Tests/UI/Rum/ReplaySignals.test.ts +179 -0
  290. package/Tests/UI/Rum/ReplayStage.test.tsx +210 -3
  291. package/Tests/UI/Rum/ReplayTimeline.test.tsx +180 -1
  292. package/Tests/UI/Rum/ReplayUserSessionsMenu.test.tsx +405 -0
  293. package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +13 -16
  294. package/Tests/UI/Rum/SessionReplayFacets.test.tsx +199 -0
  295. package/Tests/UI/Rum/SessionReplaySearchBar.test.tsx +58 -21
  296. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +2 -0
  297. package/Tests/UI/Rum/SessionReplayTable.test.tsx +717 -4
  298. package/Tests/UI/Rum/SessionReplayUsersPage.test.tsx +435 -0
  299. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +577 -0
  300. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +1 -1
  301. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -1
  302. package/Tests/UI/Utils/LabelRuleImportExport.test.ts +192 -0
  303. package/Tests/UI/Utils/Navigation.test.ts +30 -0
  304. package/Tests/Utils/Dashboard/SloTemplateVariableScoping.test.ts +880 -0
  305. package/Tests/Utils/LabelRuleImportExport.test.ts +158 -0
  306. package/Tests/Utils/NetworkDiscovery/AutoImportRuleMatcher.test.ts +93 -0
  307. package/Tests/Utils/NetworkSite/CidrMatchUtil.test.ts +138 -0
  308. package/Tests/Utils/NetworkSite/TypeHierarchyUtil.test.ts +174 -0
  309. package/Tests/Utils/Rules/RuleCriteriaMatcher.test.ts +1424 -0
  310. package/Tests/Utils/Rum/SessionIdentity.test.ts +72 -0
  311. package/Tests/Utils/Rum/SessionReplayHealth.test.ts +884 -0
  312. package/Tests/Utils/Slo/SloBurnRateRuleState.test.ts +374 -28
  313. package/Tests/Utils/Telemetry/OriginAllowList.test.ts +118 -0
  314. package/Types/Dashboard/DashboardTemplates.ts +465 -0
  315. package/Types/OnCallDutyPolicy/Layer.ts +12 -0
  316. package/Types/Rules/RuleCriteria.ts +51 -0
  317. package/Types/Rules/RuleCriteriaFieldRegistry.ts +449 -0
  318. package/Types/Rum/SessionReplay.ts +75 -0
  319. package/Types/Rum/SessionReplayApi.ts +93 -0
  320. package/UI/Components/Alerts/Alert.tsx +102 -72
  321. package/UI/Components/Button/Button.tsx +4 -1
  322. package/UI/Components/Dropdown/Dropdown.tsx +2 -0
  323. package/UI/Components/Feed/Feed.tsx +15 -0
  324. package/UI/Components/Feed/useFeedItems.ts +227 -0
  325. package/UI/Components/Forms/BasicForm.tsx +42 -21
  326. package/UI/Components/Forms/ModelForm.tsx +80 -1
  327. package/UI/Components/Icon/Icon.tsx +3 -35
  328. package/UI/Components/ModelTable/ModelTable.tsx +19 -0
  329. package/UI/Components/Navbar/NavBar.tsx +1 -0
  330. package/UI/Components/RuleCriteria/RuleCriteriaBuilder.tsx +735 -0
  331. package/UI/Components/RuleCriteria/RuleCriteriaModelForm.tsx +209 -0
  332. package/UI/Components/RuleCriteria/RuleCriteriaModelTable.tsx +205 -0
  333. package/UI/Components/RuleCriteria/RuleCriteriaSummary.tsx +123 -0
  334. package/UI/Components/Table/Table.tsx +3 -0
  335. package/UI/Components/Table/TableBody.tsx +3 -0
  336. package/UI/Components/Table/TableRow.tsx +8 -2
  337. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +14 -2
  338. package/UI/Styles/Theme.css +1 -0
  339. package/UI/Utils/LabelRuleImportExport.ts +114 -6
  340. package/UI/Utils/Navigation.ts +1 -2
  341. package/UI/esbuild-config.js +46 -0
  342. package/Utils/Faker.ts +93 -6
  343. package/Utils/LabelRuleImportExport.ts +295 -2
  344. package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +108 -8
  345. package/Utils/NetworkSite/CidrMatchUtil.ts +102 -0
  346. package/Utils/NetworkSite/TypeHierarchyUtil.ts +152 -2
  347. package/Utils/Rules/RuleCriteriaMatcher.ts +931 -0
  348. package/Utils/Rum/SessionIdentity.ts +16 -0
  349. package/Utils/Slo/SloBurnRateRuleState.ts +87 -33
  350. package/Utils/Telemetry/OriginAllowList.ts +74 -0
  351. package/build/dist/Models/AnalyticsModels/RumSession.js +32 -0
  352. package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -1
  353. package/build/dist/Models/DatabaseModels/AlertEpisodeLabelRule.js +2 -2
  354. package/build/dist/Models/DatabaseModels/AlertEpisodeLabelRule.js.map +1 -1
  355. package/build/dist/Models/DatabaseModels/AlertEpisodeOnCallRule.js +2 -2
  356. package/build/dist/Models/DatabaseModels/AlertEpisodeOnCallRule.js.map +1 -1
  357. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerRule.js +2 -2
  358. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerRule.js.map +1 -1
  359. package/build/dist/Models/DatabaseModels/AlertEpisodePrivacyRule.js +2 -2
  360. package/build/dist/Models/DatabaseModels/AlertEpisodePrivacyRule.js.map +1 -1
  361. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js +2 -2
  362. package/build/dist/Models/DatabaseModels/AlertGroupingRule.js.map +1 -1
  363. package/build/dist/Models/DatabaseModels/AlertLabelRule.js +2 -2
  364. package/build/dist/Models/DatabaseModels/AlertLabelRule.js.map +1 -1
  365. package/build/dist/Models/DatabaseModels/AlertOnCallRule.js +2 -2
  366. package/build/dist/Models/DatabaseModels/AlertOnCallRule.js.map +1 -1
  367. package/build/dist/Models/DatabaseModels/AlertOwnerRule.js +2 -2
  368. package/build/dist/Models/DatabaseModels/AlertOwnerRule.js.map +1 -1
  369. package/build/dist/Models/DatabaseModels/AlertPrivacyRule.js +2 -2
  370. package/build/dist/Models/DatabaseModels/AlertPrivacyRule.js.map +1 -1
  371. package/build/dist/Models/DatabaseModels/AlertReminderRule.js +6 -5
  372. package/build/dist/Models/DatabaseModels/AlertReminderRule.js.map +1 -1
  373. package/build/dist/Models/DatabaseModels/AutoRemediationRule.js +2 -2
  374. package/build/dist/Models/DatabaseModels/AutoRemediationRule.js.map +1 -1
  375. package/build/dist/Models/DatabaseModels/CephClusterLabelRule.js +2 -2
  376. package/build/dist/Models/DatabaseModels/CephClusterLabelRule.js.map +1 -1
  377. package/build/dist/Models/DatabaseModels/CephClusterOwnerRule.js +2 -2
  378. package/build/dist/Models/DatabaseModels/CephClusterOwnerRule.js.map +1 -1
  379. package/build/dist/Models/DatabaseModels/CloudResourceLabelRule.js +2 -2
  380. package/build/dist/Models/DatabaseModels/CloudResourceLabelRule.js.map +1 -1
  381. package/build/dist/Models/DatabaseModels/CloudResourceOwnerRule.js +2 -2
  382. package/build/dist/Models/DatabaseModels/CloudResourceOwnerRule.js.map +1 -1
  383. package/build/dist/Models/DatabaseModels/DashboardLabelRule.js +2 -2
  384. package/build/dist/Models/DatabaseModels/DashboardLabelRule.js.map +1 -1
  385. package/build/dist/Models/DatabaseModels/DashboardOwnerRule.js +2 -2
  386. package/build/dist/Models/DatabaseModels/DashboardOwnerRule.js.map +1 -1
  387. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/RelationOnlyRuleBaseModel.js +12 -0
  388. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/RelationOnlyRuleBaseModel.js.map +1 -0
  389. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/RuleBaseModel.js +50 -0
  390. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/RuleBaseModel.js.map +1 -0
  391. package/build/dist/Models/DatabaseModels/DockerHostLabelRule.js +2 -2
  392. package/build/dist/Models/DatabaseModels/DockerHostLabelRule.js.map +1 -1
  393. package/build/dist/Models/DatabaseModels/DockerHostOwnerRule.js +2 -2
  394. package/build/dist/Models/DatabaseModels/DockerHostOwnerRule.js.map +1 -1
  395. package/build/dist/Models/DatabaseModels/DockerSwarmClusterLabelRule.js +2 -2
  396. package/build/dist/Models/DatabaseModels/DockerSwarmClusterLabelRule.js.map +1 -1
  397. package/build/dist/Models/DatabaseModels/DockerSwarmClusterOwnerRule.js +2 -2
  398. package/build/dist/Models/DatabaseModels/DockerSwarmClusterOwnerRule.js.map +1 -1
  399. package/build/dist/Models/DatabaseModels/HostLabelRule.js +2 -2
  400. package/build/dist/Models/DatabaseModels/HostLabelRule.js.map +1 -1
  401. package/build/dist/Models/DatabaseModels/HostOwnerRule.js +2 -2
  402. package/build/dist/Models/DatabaseModels/HostOwnerRule.js.map +1 -1
  403. package/build/dist/Models/DatabaseModels/IncidentEpisodeLabelRule.js +2 -2
  404. package/build/dist/Models/DatabaseModels/IncidentEpisodeLabelRule.js.map +1 -1
  405. package/build/dist/Models/DatabaseModels/IncidentEpisodeOnCallRule.js +2 -2
  406. package/build/dist/Models/DatabaseModels/IncidentEpisodeOnCallRule.js.map +1 -1
  407. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerRule.js +2 -2
  408. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerRule.js.map +1 -1
  409. package/build/dist/Models/DatabaseModels/IncidentEpisodePrivacyRule.js +2 -2
  410. package/build/dist/Models/DatabaseModels/IncidentEpisodePrivacyRule.js.map +1 -1
  411. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js +2 -2
  412. package/build/dist/Models/DatabaseModels/IncidentGroupingRule.js.map +1 -1
  413. package/build/dist/Models/DatabaseModels/IncidentLabelRule.js +2 -2
  414. package/build/dist/Models/DatabaseModels/IncidentLabelRule.js.map +1 -1
  415. package/build/dist/Models/DatabaseModels/IncidentOnCallRule.js +2 -2
  416. package/build/dist/Models/DatabaseModels/IncidentOnCallRule.js.map +1 -1
  417. package/build/dist/Models/DatabaseModels/IncidentOwnerRule.js +2 -2
  418. package/build/dist/Models/DatabaseModels/IncidentOwnerRule.js.map +1 -1
  419. package/build/dist/Models/DatabaseModels/IncidentPrivacyRule.js +2 -2
  420. package/build/dist/Models/DatabaseModels/IncidentPrivacyRule.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/IncidentReminderRule.js +6 -5
  422. package/build/dist/Models/DatabaseModels/IncidentReminderRule.js.map +1 -1
  423. package/build/dist/Models/DatabaseModels/IncidentSlaRule.js +2 -2
  424. package/build/dist/Models/DatabaseModels/IncidentSlaRule.js.map +1 -1
  425. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +3 -4
  426. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
  427. package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js +2 -2
  428. package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js.map +1 -1
  429. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js +2 -2
  430. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js.map +1 -1
  431. package/build/dist/Models/DatabaseModels/IncomingCallPolicyPhoneNumber.js +277 -0
  432. package/build/dist/Models/DatabaseModels/IncomingCallPolicyPhoneNumber.js.map +1 -0
  433. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  434. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  435. package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js +2 -2
  436. package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js.map +1 -1
  437. package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js +2 -2
  438. package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js.map +1 -1
  439. package/build/dist/Models/DatabaseModels/KubernetesClusterLabelRule.js +2 -2
  440. package/build/dist/Models/DatabaseModels/KubernetesClusterLabelRule.js.map +1 -1
  441. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerRule.js +2 -2
  442. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerRule.js.map +1 -1
  443. package/build/dist/Models/DatabaseModels/MonitorLabelRule.js +2 -2
  444. package/build/dist/Models/DatabaseModels/MonitorLabelRule.js.map +1 -1
  445. package/build/dist/Models/DatabaseModels/MonitorOwnerRule.js +2 -2
  446. package/build/dist/Models/DatabaseModels/MonitorOwnerRule.js.map +1 -1
  447. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +2 -2
  448. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  449. package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js +2 -2
  450. package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js.map +1 -1
  451. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js +2 -2
  452. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js.map +1 -1
  453. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js +2 -2
  454. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js.map +1 -1
  455. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js +2 -2
  456. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js.map +1 -1
  457. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js +2 -2
  458. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js.map +1 -1
  459. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js +2 -2
  460. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js.map +1 -1
  461. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js +2 -2
  462. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js.map +1 -1
  463. package/build/dist/Models/DatabaseModels/PodmanHostLabelRule.js +2 -2
  464. package/build/dist/Models/DatabaseModels/PodmanHostLabelRule.js.map +1 -1
  465. package/build/dist/Models/DatabaseModels/PodmanHostOwnerRule.js +2 -2
  466. package/build/dist/Models/DatabaseModels/PodmanHostOwnerRule.js.map +1 -1
  467. package/build/dist/Models/DatabaseModels/ProxmoxClusterLabelRule.js +2 -2
  468. package/build/dist/Models/DatabaseModels/ProxmoxClusterLabelRule.js.map +1 -1
  469. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerRule.js +2 -2
  470. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerRule.js.map +1 -1
  471. package/build/dist/Models/DatabaseModels/RumApplicationLabelRule.js +2 -2
  472. package/build/dist/Models/DatabaseModels/RumApplicationLabelRule.js.map +1 -1
  473. package/build/dist/Models/DatabaseModels/RumApplicationOwnerRule.js +2 -2
  474. package/build/dist/Models/DatabaseModels/RumApplicationOwnerRule.js.map +1 -1
  475. package/build/dist/Models/DatabaseModels/RunbookLabelRule.js +2 -2
  476. package/build/dist/Models/DatabaseModels/RunbookLabelRule.js.map +1 -1
  477. package/build/dist/Models/DatabaseModels/RunbookOwnerRule.js +2 -2
  478. package/build/dist/Models/DatabaseModels/RunbookOwnerRule.js.map +1 -1
  479. package/build/dist/Models/DatabaseModels/RunbookRule.js +2 -2
  480. package/build/dist/Models/DatabaseModels/RunbookRule.js.map +1 -1
  481. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceLabelRule.js +2 -2
  482. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceLabelRule.js.map +1 -1
  483. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerRule.js +2 -2
  484. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerRule.js.map +1 -1
  485. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceReminderRule.js +6 -5
  486. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceReminderRule.js.map +1 -1
  487. package/build/dist/Models/DatabaseModels/ServerlessFunctionLabelRule.js +2 -2
  488. package/build/dist/Models/DatabaseModels/ServerlessFunctionLabelRule.js.map +1 -1
  489. package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerRule.js +2 -2
  490. package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerRule.js.map +1 -1
  491. package/build/dist/Models/DatabaseModels/ServiceLabelRule.js +2 -2
  492. package/build/dist/Models/DatabaseModels/ServiceLabelRule.js.map +1 -1
  493. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js +281 -4
  494. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js.map +1 -1
  495. package/build/dist/Models/DatabaseModels/ServiceOwnerRule.js +2 -2
  496. package/build/dist/Models/DatabaseModels/ServiceOwnerRule.js.map +1 -1
  497. package/build/dist/Models/DatabaseModels/StatusPageLabelRule.js +2 -2
  498. package/build/dist/Models/DatabaseModels/StatusPageLabelRule.js.map +1 -1
  499. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +2 -2
  500. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -1
  501. package/build/dist/Models/DatabaseModels/StatusPageOwnerRule.js +2 -2
  502. package/build/dist/Models/DatabaseModels/StatusPageOwnerRule.js.map +1 -1
  503. package/build/dist/Models/DatabaseModels/VMwareVCenterLabelRule.js +2 -2
  504. package/build/dist/Models/DatabaseModels/VMwareVCenterLabelRule.js.map +1 -1
  505. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerRule.js +2 -2
  506. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerRule.js.map +1 -1
  507. package/build/dist/Models/DatabaseModels/WorkflowLabelRule.js +2 -2
  508. package/build/dist/Models/DatabaseModels/WorkflowLabelRule.js.map +1 -1
  509. package/build/dist/Models/DatabaseModels/WorkflowOwnerRule.js +2 -2
  510. package/build/dist/Models/DatabaseModels/WorkflowOwnerRule.js.map +1 -1
  511. package/build/dist/Server/API/TelemetryAPI.js +211 -4
  512. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  513. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792400000000-AddSloBurnRateRuleIncidents.js +60 -0
  514. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792400000000-AddSloBurnRateRuleIncidents.js.map +1 -0
  515. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792500000000-AddConfigurableRuleCriteria.js +301 -0
  516. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792500000000-AddConfigurableRuleCriteria.js.map +1 -0
  517. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792600000000-AddIncomingCallPolicyPhoneNumbers.js +30 -0
  518. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792600000000-AddIncomingCallPolicyPhoneNumbers.js.map +1 -0
  519. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  520. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  521. package/build/dist/Server/Middleware/MultipartFormData.js +38 -21
  522. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  523. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js +17 -0
  524. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js.map +1 -1
  525. package/build/dist/Server/Services/AlertEpisodeOnCallRuleEngineService.js +17 -0
  526. package/build/dist/Server/Services/AlertEpisodeOnCallRuleEngineService.js.map +1 -1
  527. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js +17 -0
  528. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js.map +1 -1
  529. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js +17 -0
  530. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js.map +1 -1
  531. package/build/dist/Server/Services/AlertGroupingEngineService.js +21 -0
  532. package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
  533. package/build/dist/Server/Services/AlertLabelRuleEngineService.js +21 -0
  534. package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
  535. package/build/dist/Server/Services/AlertOnCallRuleEngineService.js +21 -0
  536. package/build/dist/Server/Services/AlertOnCallRuleEngineService.js.map +1 -1
  537. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js +21 -0
  538. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js.map +1 -1
  539. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js +21 -0
  540. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js.map +1 -1
  541. package/build/dist/Server/Services/AlertReminderRuleService.js +44 -26
  542. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  543. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +54 -11
  544. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  545. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +16 -0
  546. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -1
  547. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +16 -0
  548. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
  549. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +16 -0
  550. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -1
  551. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +16 -0
  552. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
  553. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js +16 -0
  554. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js.map +1 -1
  555. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js +16 -0
  556. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js.map +1 -1
  557. package/build/dist/Server/Services/DatabaseService.js +235 -10
  558. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  559. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js +16 -0
  560. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js.map +1 -1
  561. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +16 -0
  562. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
  563. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js +16 -0
  564. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js.map +1 -1
  565. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +16 -0
  566. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
  567. package/build/dist/Server/Services/HostLabelRuleEngineService.js +12 -0
  568. package/build/dist/Server/Services/HostLabelRuleEngineService.js.map +1 -1
  569. package/build/dist/Server/Services/HostOwnerRuleEngineService.js +12 -0
  570. package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
  571. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js +17 -0
  572. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js.map +1 -1
  573. package/build/dist/Server/Services/IncidentEpisodeOnCallRuleEngineService.js +17 -0
  574. package/build/dist/Server/Services/IncidentEpisodeOnCallRuleEngineService.js.map +1 -1
  575. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js +17 -0
  576. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js.map +1 -1
  577. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js +17 -0
  578. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js.map +1 -1
  579. package/build/dist/Server/Services/IncidentGroupingEngineService.js +39 -13
  580. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  581. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +39 -9
  582. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
  583. package/build/dist/Server/Services/IncidentOnCallRuleEngineService.js +39 -10
  584. package/build/dist/Server/Services/IncidentOnCallRuleEngineService.js.map +1 -1
  585. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js +39 -10
  586. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js.map +1 -1
  587. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js +39 -10
  588. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js.map +1 -1
  589. package/build/dist/Server/Services/IncidentReminderRuleService.js +44 -26
  590. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  591. package/build/dist/Server/Services/IncidentSlaRuleService.js +33 -12
  592. package/build/dist/Server/Services/IncidentSlaRuleService.js.map +1 -1
  593. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js +16 -0
  594. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js.map +1 -1
  595. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +16 -0
  596. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -1
  597. package/build/dist/Server/Services/IncomingCallPolicyPhoneNumberService.js +127 -0
  598. package/build/dist/Server/Services/IncomingCallPolicyPhoneNumberService.js.map +1 -0
  599. package/build/dist/Server/Services/IncomingCallPolicyService.js +187 -4
  600. package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
  601. package/build/dist/Server/Services/Index.js +2 -0
  602. package/build/dist/Server/Services/Index.js.map +1 -1
  603. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +16 -0
  604. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -1
  605. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +16 -0
  606. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -1
  607. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js +16 -0
  608. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js.map +1 -1
  609. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +16 -0
  610. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
  611. package/build/dist/Server/Services/LabelService.js +39 -7
  612. package/build/dist/Server/Services/LabelService.js.map +1 -1
  613. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js +16 -0
  614. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js.map +1 -1
  615. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js +16 -0
  616. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js.map +1 -1
  617. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +2 -0
  618. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  619. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +57 -1
  620. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
  621. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +17 -0
  622. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  623. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +16 -0
  624. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
  625. package/build/dist/Server/Services/NetworkDeviceService.js +2 -0
  626. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  627. package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js +56 -1
  628. package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js.map +1 -1
  629. package/build/dist/Server/Services/NetworkSiteService.js +187 -22
  630. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  631. package/build/dist/Server/Services/NetworkSiteTypeService.js +126 -51
  632. package/build/dist/Server/Services/NetworkSiteTypeService.js.map +1 -1
  633. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js +16 -0
  634. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js.map +1 -1
  635. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +16 -0
  636. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -1
  637. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js +16 -0
  638. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js.map +1 -1
  639. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +16 -0
  640. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -1
  641. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js +16 -0
  642. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js.map +1 -1
  643. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +16 -0
  644. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
  645. package/build/dist/Server/Services/ProjectCallSMSConfigService.js +115 -6
  646. package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
  647. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +16 -0
  648. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -1
  649. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +16 -0
  650. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
  651. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js +16 -0
  652. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js.map +1 -1
  653. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js +16 -0
  654. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -1
  655. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js +16 -0
  656. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js.map +1 -1
  657. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js +16 -0
  658. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js.map +1 -1
  659. package/build/dist/Server/Services/RunbookRuleEngineService.js +12 -0
  660. package/build/dist/Server/Services/RunbookRuleEngineService.js.map +1 -1
  661. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js +38 -9
  662. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js.map +1 -1
  663. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js +38 -10
  664. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js.map +1 -1
  665. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +32 -14
  666. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  667. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js +16 -0
  668. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js.map +1 -1
  669. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +16 -0
  670. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -1
  671. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js +16 -0
  672. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js.map +1 -1
  673. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js +554 -12
  674. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js.map +1 -1
  675. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +50 -14
  676. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  677. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +16 -0
  678. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
  679. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js +16 -0
  680. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js.map +1 -1
  681. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +25 -2
  682. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -1
  683. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +19 -1
  684. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -1
  685. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js +16 -0
  686. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js.map +1 -1
  687. package/build/dist/Server/Services/UserService.js +14 -49
  688. package/build/dist/Server/Services/UserService.js.map +1 -1
  689. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js +16 -0
  690. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js.map +1 -1
  691. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js +16 -0
  692. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js.map +1 -1
  693. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js +16 -0
  694. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js.map +1 -1
  695. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js +16 -0
  696. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js.map +1 -1
  697. package/build/dist/Server/Types/Database/QueryHelper.js +67 -0
  698. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  699. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +21 -4
  700. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  701. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +9 -3
  702. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -1
  703. package/build/dist/Server/Utils/Profiling.js +10 -1
  704. package/build/dist/Server/Utils/Profiling.js.map +1 -1
  705. package/build/dist/Server/Utils/Rules/MonitorRuleCriteriaCache.js +30 -0
  706. package/build/dist/Server/Utils/Rules/MonitorRuleCriteriaCache.js.map +1 -0
  707. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +344 -0
  708. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  709. package/build/dist/Server/Utils/Telemetry.js +1 -1
  710. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  711. package/build/dist/Server/Utils/VendorAssets.js +2 -0
  712. package/build/dist/Server/Utils/VendorAssets.js.map +1 -1
  713. package/build/dist/Types/Dashboard/DashboardTemplates.js +425 -0
  714. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  715. package/build/dist/Types/OnCallDutyPolicy/Layer.js +11 -0
  716. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  717. package/build/dist/Types/Rules/RuleCriteria.js +18 -0
  718. package/build/dist/Types/Rules/RuleCriteria.js.map +1 -0
  719. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +435 -0
  720. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -0
  721. package/build/dist/Types/Rum/SessionReplay.js +64 -0
  722. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  723. package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
  724. package/build/dist/UI/Components/Alerts/Alert.js +63 -41
  725. package/build/dist/UI/Components/Alerts/Alert.js.map +1 -1
  726. package/build/dist/UI/Components/Button/Button.js +3 -1
  727. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  728. package/build/dist/UI/Components/Dropdown/Dropdown.js +1 -1
  729. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  730. package/build/dist/UI/Components/Feed/Feed.js +4 -1
  731. package/build/dist/UI/Components/Feed/Feed.js.map +1 -1
  732. package/build/dist/UI/Components/Feed/useFeedItems.js +137 -0
  733. package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -0
  734. package/build/dist/UI/Components/Forms/BasicForm.js +38 -19
  735. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  736. package/build/dist/UI/Components/Forms/ModelForm.js +65 -1
  737. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  738. package/build/dist/UI/Components/Icon/Icon.js +3 -35
  739. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  740. package/build/dist/UI/Components/ModelTable/ModelTable.js +12 -1
  741. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  742. package/build/dist/UI/Components/Navbar/NavBar.js +1 -0
  743. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  744. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaBuilder.js +383 -0
  745. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaBuilder.js.map +1 -0
  746. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaModelForm.js +124 -0
  747. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaModelForm.js.map +1 -0
  748. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaModelTable.js +126 -0
  749. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaModelTable.js.map +1 -0
  750. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaSummary.js +63 -0
  751. package/build/dist/UI/Components/RuleCriteria/RuleCriteriaSummary.js.map +1 -0
  752. package/build/dist/UI/Components/Table/Table.js +1 -1
  753. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  754. package/build/dist/UI/Components/Table/TableBody.js +4 -2
  755. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  756. package/build/dist/UI/Components/Table/TableRow.js +3 -2
  757. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  758. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +14 -2
  759. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +1 -1
  760. package/build/dist/UI/Utils/LabelRuleImportExport.js +81 -5
  761. package/build/dist/UI/Utils/LabelRuleImportExport.js.map +1 -1
  762. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  763. package/build/dist/Utils/Faker.js +76 -6
  764. package/build/dist/Utils/Faker.js.map +1 -1
  765. package/build/dist/Utils/LabelRuleImportExport.js +157 -3
  766. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  767. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js +81 -5
  768. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
  769. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js +77 -0
  770. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js.map +1 -1
  771. package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js +109 -2
  772. package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js.map +1 -1
  773. package/build/dist/Utils/Rules/RuleCriteriaMatcher.js +526 -0
  774. package/build/dist/Utils/Rules/RuleCriteriaMatcher.js.map +1 -0
  775. package/build/dist/Utils/Rum/SessionIdentity.js +13 -1
  776. package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -1
  777. package/build/dist/Utils/Slo/SloBurnRateRuleState.js +28 -15
  778. package/build/dist/Utils/Slo/SloBurnRateRuleState.js.map +1 -1
  779. package/build/dist/Utils/Telemetry/OriginAllowList.js +61 -0
  780. package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -1
  781. package/package.json +20 -20
@@ -9,7 +9,10 @@ import RumSessionChunkService from "../../../Server/Services/RumSessionChunkServ
9
9
  import ExceptionInstanceService from "../../../Server/Services/ExceptionInstanceService";
10
10
  import { Statement } from "../../../Server/Utils/AnalyticsDatabase/Statement";
11
11
  import SessionReplayIdentity from "../../../Server/Utils/SessionReplay/SessionReplayIdentity";
12
- import SessionReplayReadService from "../../../Server/Utils/SessionReplay/SessionReplayReadService";
12
+ import SessionReplayReadService, {
13
+ DEFAULT_SESSION_REPLAY_USERS_LIMIT,
14
+ SessionReplayUsersRequest,
15
+ } from "../../../Server/Utils/SessionReplay/SessionReplayReadService";
13
16
  import NotFoundException from "../../../Types/Exception/NotFoundException";
14
17
  import {
15
18
  MAX_SESSION_REPLAY_CHUNKS_PER_READ,
@@ -39,6 +42,7 @@ import Permission, {
39
42
  } from "../../../Types/Permission";
40
43
  import PermissionScope from "../../../Types/Database/AccessControl/PermissionScope";
41
44
  import UserType from "../../../Types/UserType";
45
+ import zlib from "zlib";
42
46
  import {
43
47
  afterEach,
44
48
  beforeAll,
@@ -133,6 +137,10 @@ const FOR_EXCEPTION_ROUTE: string =
133
137
  const INGEST_STATUS_ROUTE: string =
134
138
  "/telemetry/rum/session-replay/ingest-status";
135
139
  const VIEWS_ROUTE: string = "/telemetry/rum/session-replay/views";
140
+ const USERS_ROUTE: string = "/telemetry/rum/session-replay/users";
141
+
142
+ /* A visitor id as the recorder mints it: 32 lowercase hex characters. */
143
+ const VISITOR_ID: string = "0123456789abcdef0123456789abcdef";
136
144
 
137
145
  function findRoute(uri: string): RecordedRoute {
138
146
  const route: RecordedRoute | undefined = recordedRoutes.find(
@@ -175,6 +183,12 @@ async function callRoute(data: {
175
183
  uri: string;
176
184
  request: JSONObject;
177
185
  body: JSONObject;
186
+ /*
187
+ * Extra request headers (e.g. accept-encoding). Absent by default so
188
+ * every test that does not name one runs the identity path exactly as
189
+ * before the chunk route learned to gzip.
190
+ */
191
+ headers?: Dictionary<string>;
178
192
  }): Promise<CallResult> {
179
193
  const route: RecordedRoute = findRoute(data.uri);
180
194
 
@@ -186,7 +200,7 @@ async function callRoute(data: {
186
200
  body: data.body,
187
201
  params: {},
188
202
  query: {},
189
- headers: { "user-agent": "jest-agent" },
203
+ headers: { "user-agent": "jest-agent", ...(data.headers ?? {}) },
190
204
  } as unknown as ExpressRequest;
191
205
 
192
206
  const res: ExpressResponse = {
@@ -549,9 +563,10 @@ describe("Session replay playback API", () => {
549
563
  }
550
564
 
551
565
  describe("guard shape", () => {
552
- test("all seven routes are registered and every one carries the three-middleware guard", () => {
566
+ test("all eight routes are registered and every one carries the three-middleware guard", () => {
553
567
  for (const uri of [
554
568
  LIST_ROUTE,
569
+ USERS_ROUTE,
555
570
  MANIFEST_ROUTE,
556
571
  CHUNKS_ROUTE,
557
572
  HEARTBEAT_ROUTE,
@@ -596,6 +611,8 @@ describe("Session replay playback API", () => {
596
611
  }
597
612
 
598
613
  expect(findRoute(FOR_EXCEPTION_ROUTE).handlers[2]).toBe(listGuard);
614
+ /* The rollup projects nothing the list does not: same guard instance. */
615
+ expect(findRoute(USERS_ROUTE).handlers[2]).toBe(listGuard);
599
616
  });
600
617
  });
601
618
 
@@ -1342,6 +1359,152 @@ describe("Session replay playback API", () => {
1342
1359
  * wrong recording, and each opening writes an audit row against a real
1343
1360
  * end user. The Dashboard already reads this field and shows a notice.
1344
1361
  */
1362
+ /*
1363
+ * The visitor id is the recorder's own random per-browser token, and
1364
+ * the list already hands it to every caller on every row - so filtering
1365
+ * by it discloses nothing the caller does not have, and it is NOT gated
1366
+ * like identifiedUserRef. A TelemetryAdmin denied the label can still
1367
+ * ask for "every session from this browser", and the filter is applied,
1368
+ * not dropped and named in ignoredFilters.
1369
+ */
1370
+ test("filters.visitorId is bound into the query for a caller without the identity permission, never dropped", async () => {
1371
+ const principal: {
1372
+ request: JSONObject;
1373
+ databaseProps: DatabaseCommonInteractionProps;
1374
+ } = buildPrincipal({
1375
+ projectId: projectId,
1376
+ userId: userId,
1377
+ permissions: [Permission.TelemetryAdmin],
1378
+ });
1379
+
1380
+ mockProps(principal.databaseProps);
1381
+ mockApplication({ id: applicationAId, labelIds: [] });
1382
+
1383
+ const result: CallResult = await callRoute({
1384
+ uri: LIST_ROUTE,
1385
+ request: principal.request,
1386
+ body: {
1387
+ rumApplicationId: applicationAId.toString(),
1388
+ filters: { visitorId: VISITOR_ID },
1389
+ },
1390
+ });
1391
+
1392
+ expect(result.deniedWith).toBeUndefined();
1393
+ expect(headerQuerySpy).toHaveBeenCalledTimes(1);
1394
+
1395
+ const statement: Statement = headerQuerySpy.mock
1396
+ .calls[0]![0] as Statement;
1397
+ const having: string = statement.query.substring(
1398
+ statement.query.indexOf("HAVING 1 = 1"),
1399
+ );
1400
+
1401
+ expect(having).toMatch(/AND aggVisitorId = \{p\d+:String\}/);
1402
+ expect(statement.query).not.toContain(`'${VISITOR_ID}'`);
1403
+ expect(Object.values(statement.query_params)).toContain(VISITOR_ID);
1404
+ /* Nothing identity-gated was named for it. */
1405
+ expect(statement.query).not.toContain("identifiedUserLabel");
1406
+ expect(result.jsonBody?.["ignoredFilters"]).toEqual([]);
1407
+ });
1408
+
1409
+ /*
1410
+ * A visitor id that is not one can match nothing, and an unfiltered
1411
+ * list with a 200 is the wrong answer to "every session from this
1412
+ * browser" - the same reasoning as an unusable identifiedUserRef.
1413
+ */
1414
+ test.each([
1415
+ "0123456789ABCDEF0123456789ABCDEF",
1416
+ "0123456789abcdef",
1417
+ "",
1418
+ 42,
1419
+ true,
1420
+ { visitorId: VISITOR_ID },
1421
+ [VISITOR_ID],
1422
+ ])(
1423
+ "filters.visitorId %p is a bad request, never an unfiltered list",
1424
+ async (value: unknown) => {
1425
+ const principal: {
1426
+ request: JSONObject;
1427
+ databaseProps: DatabaseCommonInteractionProps;
1428
+ } = ownerPrincipal();
1429
+
1430
+ const result: CallResult = await callRoute({
1431
+ uri: LIST_ROUTE,
1432
+ request: principal.request,
1433
+ body: {
1434
+ rumApplicationId: applicationAId.toString(),
1435
+ filters: { visitorId: value as string },
1436
+ },
1437
+ });
1438
+
1439
+ expect(result.deniedWith).toBeInstanceOf(BadDataException);
1440
+ expect(result.deniedWith?.message).toContain("visitorId");
1441
+ expect(headerQuerySpy).not.toHaveBeenCalled();
1442
+ },
1443
+ );
1444
+
1445
+ test("a null visitorId filter is 'no filter', like an absent one", async () => {
1446
+ const principal: {
1447
+ request: JSONObject;
1448
+ databaseProps: DatabaseCommonInteractionProps;
1449
+ } = ownerPrincipal();
1450
+
1451
+ const result: CallResult = await callRoute({
1452
+ uri: LIST_ROUTE,
1453
+ request: principal.request,
1454
+ body: {
1455
+ rumApplicationId: applicationAId.toString(),
1456
+ filters: { visitorId: null },
1457
+ },
1458
+ });
1459
+
1460
+ expect(result.deniedWith).toBeUndefined();
1461
+
1462
+ const statement: Statement = headerQuerySpy.mock
1463
+ .calls[0]![0] as Statement;
1464
+ expect(statement.query).not.toContain("aggVisitorId =");
1465
+ });
1466
+
1467
+ test("the list projects each session's digest and visitor id for every caller", async () => {
1468
+ const principal: {
1469
+ request: JSONObject;
1470
+ databaseProps: DatabaseCommonInteractionProps;
1471
+ } = buildPrincipal({
1472
+ projectId: projectId,
1473
+ userId: userId,
1474
+ permissions: [Permission.TelemetryAdmin],
1475
+ });
1476
+
1477
+ mockProps(principal.databaseProps);
1478
+ mockApplication({ id: applicationAId, labelIds: [] });
1479
+
1480
+ headerQuerySpy.mockResolvedValue(
1481
+ fakeResultSet([
1482
+ {
1483
+ sessionId: "a",
1484
+ applicationId: applicationAId.toString(),
1485
+ aggIdentifiedUserKey: "hmac-1",
1486
+ aggVisitorId: VISITOR_ID,
1487
+ },
1488
+ { sessionId: "b", applicationId: applicationAId.toString() },
1489
+ ]) as never,
1490
+ );
1491
+
1492
+ const result: CallResult = await callRoute({
1493
+ uri: LIST_ROUTE,
1494
+ request: principal.request,
1495
+ body: { rumApplicationId: applicationAId.toString() },
1496
+ });
1497
+
1498
+ const sessions: Array<JSONObject> = (result.jsonBody as JSONObject)[
1499
+ "sessions"
1500
+ ] as unknown as Array<JSONObject>;
1501
+
1502
+ expect(sessions[0]!["identifiedUserKey"]).toBe("hmac-1");
1503
+ expect(sessions[0]!["visitorId"]).toBe(VISITOR_ID);
1504
+ /* An older recorder's session: "", never undefined. */
1505
+ expect(sessions[1]!["visitorId"]).toBe("");
1506
+ });
1507
+
1345
1508
  test("a dropped identity filter is NAMED in the response, not silently ignored", async () => {
1346
1509
  const principal: {
1347
1510
  request: JSONObject;
@@ -2195,154 +2358,723 @@ describe("Session replay playback API", () => {
2195
2358
  });
2196
2359
  });
2197
2360
 
2198
- describe("manifest", () => {
2199
- test("never names the payload column and writes the read audit row", async () => {
2361
+ /*
2362
+ * The per-user rollup: /list grouped by person. It shares the list's
2363
+ * guard, prologue and identity decision, and pins its own cursor shape.
2364
+ * Everything about the statement itself is pinned in
2365
+ * SessionReplayReadServiceQueries.test.ts; here it is the handler's
2366
+ * contract - what reaches the service and what leaves the route.
2367
+ */
2368
+ describe("users", () => {
2369
+ function rollupRow(overrides: JSONObject = {}): JSONObject {
2370
+ return {
2371
+ rollupKey: "u:hmac-1",
2372
+ rollupIdentifiedUserKey: "hmac-1",
2373
+ rollupVisitorId: VISITOR_ID,
2374
+ rollupSessionCount: 3,
2375
+ rollupLiveSessionCount: 1,
2376
+ rollupFirstSeenUnixMs: 1700000000000,
2377
+ rollupLastSeenUnixMs: 1700000900000,
2378
+ rollupTotalDurationMs: 180000,
2379
+ rollupErrorCount: 2,
2380
+ rollupFrustrationCount: 4,
2381
+ rollupErrorSessionCount: 1,
2382
+ rollupPageCount: 9,
2383
+ rollupLastSessionId: "session-3",
2384
+ rollupLastEntryUrl: "https://example.com/checkout",
2385
+ rollupBrowserName: "Chrome",
2386
+ rollupBrowserVersion: "128",
2387
+ rollupOsName: "macOS",
2388
+ rollupDeviceType: "desktop",
2389
+ rollupCountryCode: "GB",
2390
+ rollupIdentifiedUserLabel: "jane@example.com",
2391
+ rollupIdentifiedUserTraits: { plan: "pro" },
2392
+ ...overrides,
2393
+ };
2394
+ }
2395
+
2396
+ function principalWith(permissions: Array<Permission>): {
2397
+ request: JSONObject;
2398
+ databaseProps: DatabaseCommonInteractionProps;
2399
+ } {
2200
2400
  const principal: {
2201
2401
  request: JSONObject;
2202
2402
  databaseProps: DatabaseCommonInteractionProps;
2203
2403
  } = buildPrincipal({
2204
2404
  projectId: projectId,
2205
2405
  userId: userId,
2206
- permissions: [Permission.ReadRumSessionReplayPayload],
2406
+ permissions: permissions,
2207
2407
  });
2208
2408
 
2209
2409
  mockProps(principal.databaseProps);
2210
- mockSessionHeader(applicationAId);
2211
2410
  mockApplication({ id: applicationAId, labelIds: [] });
2212
- const viewId: ObjectID = mockRecordedView();
2213
2411
 
2214
- chunkQuerySpy.mockResolvedValue(
2215
- fakeResultSet([
2216
- {
2217
- tabId: "tab-1",
2218
- chunkIndex: 0,
2219
- chunkStartOffsetMs: 0,
2220
- chunkEndOffsetMs: 15000,
2221
- eventCount: 100,
2222
- hasFullSnapshot: true,
2223
- chunkPayloadBytes: 1024,
2224
- errorCount: 2,
2225
- rageClickCount: 1,
2226
- deadClickCount: 4,
2227
- errorClickCount: 6,
2228
- refreshRageCount: 5,
2229
- routeCount: 3,
2230
- },
2231
- {
2232
- tabId: "tab-1",
2233
- chunkIndex: 3,
2234
- chunkStartOffsetMs: 45000,
2235
- chunkEndOffsetMs: 60000,
2236
- eventCount: 100,
2237
- hasFullSnapshot: false,
2238
- chunkPayloadBytes: 1024,
2239
- errorCount: 0,
2240
- rageClickCount: 0,
2241
- deadClickCount: 0,
2242
- errorClickCount: 0,
2243
- refreshRageCount: 0,
2244
- routeCount: 0,
2245
- },
2246
- ]) as never,
2247
- );
2412
+ return principal;
2413
+ }
2414
+
2415
+ test("a caller with the list permission reaches the handler and gets the rollup", async () => {
2416
+ const principal: {
2417
+ request: JSONObject;
2418
+ databaseProps: DatabaseCommonInteractionProps;
2419
+ } = principalWith([Permission.ReadRumSessionReplay]);
2420
+
2421
+ headerQuerySpy.mockResolvedValue(fakeResultSet([rollupRow()]) as never);
2248
2422
 
2249
2423
  const result: CallResult = await callRoute({
2250
- uri: MANIFEST_ROUTE,
2424
+ uri: USERS_ROUTE,
2251
2425
  request: principal.request,
2252
- body: { sessionId: "session-1", accessReason: "incident triage" },
2426
+ body: { rumApplicationId: applicationAId.toString() },
2253
2427
  });
2254
2428
 
2255
- const statement: Statement = chunkQuerySpy.mock.calls[0]![0] as Statement;
2256
-
2257
- expect(statement.query).not.toContain("payload,");
2258
- expect(statement.query).not.toMatch(/\bpayload\b(?!Bytes)/);
2259
- expect(statement.query).toContain("LIMIT 1 BY tabId, chunkIndex");
2260
-
2261
- /*
2262
- * The signal counters must be projected: the player's error,
2263
- * frustration and route timeline lanes and the "next error" jump are
2264
- * fed exclusively by these per-chunk columns. Dropping one from the
2265
- * SELECT silently blanks a lane — it parses as 0 client-side.
2266
- */
2267
- for (const counterColumn of [
2268
- "errorCount",
2269
- "rageClickCount",
2270
- "deadClickCount",
2271
- "errorClickCount",
2272
- "refreshRageCount",
2273
- "routeCount",
2274
- ]) {
2275
- expect(statement.query).toContain(counterColumn);
2276
- }
2429
+ expect(result.reachedHandler).toBe(true);
2430
+ expect(result.deniedWith).toBeUndefined();
2431
+ expect(result.thrownToNext).toBeUndefined();
2432
+ expect(headerQuerySpy).toHaveBeenCalledTimes(1);
2277
2433
 
2278
- expect(recordViewSpy).toHaveBeenCalledTimes(1);
2279
- const auditArgs: JSONObject = recordViewSpy.mock
2280
- .calls[0]![0] as JSONObject;
2281
- expect((auditArgs["projectId"] as ObjectID).toString()).toBe(
2282
- projectId.toString(),
2434
+ const statement: Statement = headerQuerySpy.mock
2435
+ .calls[0]![0] as Statement;
2436
+ expect(statement.query).toContain("GROUP BY rollupKey");
2437
+ expect(statement.query).toContain(
2438
+ "GROUP BY projectId, rumApplicationId, sessionId",
2283
2439
  );
2284
- expect((auditArgs["rumApplicationId"] as ObjectID).toString()).toBe(
2440
+ expect(statement.query).toContain("retentionDate >= now()");
2441
+ expect(statement.query).toContain("timeout_overflow_mode = 'throw'");
2442
+ expect(statement.query).not.toContain(" FINAL");
2443
+ expect(Object.values(statement.query_params)).toContain(
2285
2444
  applicationAId.toString(),
2286
2445
  );
2287
- expect(auditArgs["sessionId"]).toBe("session-1");
2288
- expect(auditArgs["accessReason"]).toBe("incident triage");
2289
- expect(auditArgs["ipAddress"]).toBe("203.0.113.7");
2290
2446
 
2291
2447
  const body: JSONObject = result.jsonBody as JSONObject;
2292
- expect(body["viewId"]).toBe(viewId.toString());
2293
-
2294
- const tabs: Array<JSONObject> = body[
2295
- "tabs"
2296
- ] as unknown as Array<JSONObject>;
2297
- expect(tabs).toHaveLength(1);
2298
- expect(tabs[0]!["chunkIndexes"]).toEqual([0, 3]);
2299
- expect(tabs[0]!["fullSnapshotChunkIndexes"]).toEqual([0]);
2300
-
2301
- /*
2302
- * Every counter gets a DISTINCT nonzero value, asserted individually:
2303
- * the client parses missing keys to 0, so a counter dropped from the
2304
- * SELECT (or two counters cross-wired in the row mapping) fails
2305
- * loudly here instead of silently blanking a timeline lane.
2306
- */
2307
- const manifestChunks: Array<JSONObject> = tabs[0]![
2308
- "chunks"
2448
+ const users: Array<JSONObject> = body[
2449
+ "users"
2309
2450
  ] as unknown as Array<JSONObject>;
2310
- expect(manifestChunks[0]!["errorCount"]).toBe(2);
2311
- expect(manifestChunks[0]!["rageClickCount"]).toBe(1);
2312
- expect(manifestChunks[0]!["deadClickCount"]).toBe(4);
2313
- expect(manifestChunks[0]!["errorClickCount"]).toBe(6);
2314
- expect(manifestChunks[0]!["refreshRageCount"]).toBe(5);
2315
- expect(manifestChunks[0]!["routeCount"]).toBe(3);
2316
- expect(manifestChunks[1]!["errorCount"]).toBe(0);
2317
2451
 
2318
- /*
2319
- * The hole between chunk 0 and chunk 3 must be reported. Playback
2320
- * that silently crosses a gap renders a DOM the user never saw.
2321
- */
2322
- const gaps: Array<JSONObject> = tabs[0]![
2323
- "gaps"
2324
- ] as unknown as Array<JSONObject>;
2325
- expect(gaps).toHaveLength(1);
2326
- expect(gaps[0]!["fromIndex"]).toBe(0);
2327
- expect(gaps[0]!["toIndex"]).toBe(3);
2328
- expect(gaps[0]!["missingMs"]).toBe(30000);
2452
+ expect(users).toHaveLength(1);
2453
+ expect(users[0]!["groupKey"]).toBe("u:hmac-1");
2454
+ expect(users[0]!["kind"]).toBe("identified");
2455
+ expect(users[0]!["identifiedUserKey"]).toBe("hmac-1");
2456
+ expect(users[0]!["visitorId"]).toBe(VISITOR_ID);
2457
+ expect(users[0]!["sessionCount"]).toBe(3);
2458
+ expect(users[0]!["liveSessionCount"]).toBe(1);
2459
+ expect(users[0]!["lastSessionId"]).toBe("session-3");
2460
+ expect(users[0]!["lastEntryUrl"]).toBe("https://example.com/checkout");
2461
+ expect(users[0]!["lastSeenUnixMs"]).toBe(1700000900000);
2462
+ expect(body["nextCursor"]).toBeNull();
2329
2463
  });
2330
2464
 
2331
- test("a session that does not exist in this tenant is refused before any audit row", async () => {
2332
- const principal: {
2465
+ test("a caller with only the watch permission can read the rollup, a Viewer cannot", async () => {
2466
+ const watcher: {
2333
2467
  request: JSONObject;
2334
2468
  databaseProps: DatabaseCommonInteractionProps;
2335
- } = buildPrincipal({
2336
- projectId: projectId,
2337
- userId: userId,
2338
- permissions: [Permission.ProjectOwner],
2469
+ } = principalWith([Permission.ReadRumSessionReplayPayload]);
2470
+
2471
+ const allowed: CallResult = await callRoute({
2472
+ uri: USERS_ROUTE,
2473
+ request: watcher.request,
2474
+ body: { rumApplicationId: applicationAId.toString() },
2339
2475
  });
2340
2476
 
2341
- mockProps(principal.databaseProps);
2342
- headerQuerySpy.mockResolvedValue(fakeResultSet([]) as never);
2477
+ expect(allowed.deniedWith).toBeUndefined();
2478
+ expect(headerQuerySpy).toHaveBeenCalledTimes(1);
2343
2479
 
2344
- const result: CallResult = await callRoute({
2345
- uri: MANIFEST_ROUTE,
2480
+ jest.clearAllMocks();
2481
+
2482
+ const viewer: {
2483
+ request: JSONObject;
2484
+ databaseProps: DatabaseCommonInteractionProps;
2485
+ } = principalWith([Permission.Viewer]);
2486
+
2487
+ const denied: CallResult = await callRoute({
2488
+ uri: USERS_ROUTE,
2489
+ request: viewer.request,
2490
+ body: { rumApplicationId: applicationAId.toString() },
2491
+ });
2492
+
2493
+ expect(denied.deniedWith).toBeInstanceOf(NotAuthorizedException);
2494
+ expect(denied.reachedHandler).toBe(false);
2495
+ expect(headerQuerySpy).not.toHaveBeenCalled();
2496
+ });
2497
+
2498
+ test("a caller scoped to another application is refused before any query", async () => {
2499
+ const principal: {
2500
+ request: JSONObject;
2501
+ databaseProps: DatabaseCommonInteractionProps;
2502
+ } = buildPrincipal({
2503
+ projectId: projectId,
2504
+ userId: userId,
2505
+ permissions: [Permission.ReadRumSessionReplay],
2506
+ labelIds: [labelAId],
2507
+ });
2508
+
2509
+ mockProps(principal.databaseProps);
2510
+ mockApplication({ id: applicationBId, labelIds: [labelBId] });
2511
+
2512
+ const result: CallResult = await callRoute({
2513
+ uri: USERS_ROUTE,
2514
+ request: principal.request,
2515
+ body: { rumApplicationId: applicationBId.toString() },
2516
+ });
2517
+
2518
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2519
+ expect(headerQuerySpy).not.toHaveBeenCalled();
2520
+ });
2521
+
2522
+ test("a malformed rumApplicationId is a bad request, not a database error", async () => {
2523
+ const principal: {
2524
+ request: JSONObject;
2525
+ databaseProps: DatabaseCommonInteractionProps;
2526
+ } = principalWith([Permission.ProjectOwner]);
2527
+
2528
+ const result: CallResult = await callRoute({
2529
+ uri: USERS_ROUTE,
2530
+ request: principal.request,
2531
+ body: { rumApplicationId: "not-a-uuid" },
2532
+ });
2533
+
2534
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
2535
+ expect(headerQuerySpy).not.toHaveBeenCalled();
2536
+ });
2537
+
2538
+ /*
2539
+ * A cursor that is present but not {lastSeenUnixMs: finite >= 0,
2540
+ * groupKey: bounded string} is a 400 before any query - a non-finite
2541
+ * or negative clock cannot be bound, and an unbounded key would reach
2542
+ * ClickHouse as a parameter on a per-group predicate.
2543
+ */
2544
+ test.each([
2545
+ { nonsense: true },
2546
+ { lastSeenUnixMs: "1700000000000", groupKey: "u:hmac-1" },
2547
+ { lastSeenUnixMs: -1, groupKey: "u:hmac-1" },
2548
+ { lastSeenUnixMs: Number.NaN, groupKey: "u:hmac-1" },
2549
+ { lastSeenUnixMs: Number.POSITIVE_INFINITY, groupKey: "u:hmac-1" },
2550
+ { lastSeenUnixMs: 1700000000000 },
2551
+ { lastSeenUnixMs: 1700000000000, groupKey: 7 },
2552
+ { lastSeenUnixMs: 1700000000000, groupKey: "k".repeat(129) },
2553
+ "u:hmac-1",
2554
+ [1700000000000, "u:hmac-1"],
2555
+ ])(
2556
+ "cursor %p is a bad request without a query",
2557
+ async (cursor: unknown) => {
2558
+ const principal: {
2559
+ request: JSONObject;
2560
+ databaseProps: DatabaseCommonInteractionProps;
2561
+ } = principalWith([Permission.ProjectOwner]);
2562
+
2563
+ const result: CallResult = await callRoute({
2564
+ uri: USERS_ROUTE,
2565
+ request: principal.request,
2566
+ body: {
2567
+ rumApplicationId: applicationAId.toString(),
2568
+ cursor: cursor as JSONObject,
2569
+ },
2570
+ });
2571
+
2572
+ expect(result.deniedWith).toBeInstanceOf(BadDataException);
2573
+ expect(result.deniedWith?.message).toContain("cursor");
2574
+ expect(headerQuerySpy).not.toHaveBeenCalled();
2575
+ },
2576
+ );
2577
+
2578
+ /*
2579
+ * The anonymous bucket's key is "" and it can be the last row of a
2580
+ * page, so the empty key is a VALID tiebreak - accepted and bound as
2581
+ * itself, not read as "no cursor".
2582
+ */
2583
+ test("the anonymous bucket's empty group key is an accepted cursor", async () => {
2584
+ const principal: {
2585
+ request: JSONObject;
2586
+ databaseProps: DatabaseCommonInteractionProps;
2587
+ } = principalWith([Permission.ProjectOwner]);
2588
+
2589
+ const result: CallResult = await callRoute({
2590
+ uri: USERS_ROUTE,
2591
+ request: principal.request,
2592
+ body: {
2593
+ rumApplicationId: applicationAId.toString(),
2594
+ cursor: { lastSeenUnixMs: 1700000700000, groupKey: "" },
2595
+ },
2596
+ });
2597
+
2598
+ expect(result.deniedWith).toBeUndefined();
2599
+
2600
+ const statement: Statement = headerQuerySpy.mock
2601
+ .calls[0]![0] as Statement;
2602
+ expect(statement.query).toMatch(
2603
+ /AND \(rollupLastSeenUnixMs < \{p\d+:Double\} OR \(rollupLastSeenUnixMs = \{p\d+:Double\} AND rollupKey < \{p\d+:String\}\)\)/,
2604
+ );
2605
+ expect(Object.values(statement.query_params)).toContain("");
2606
+ });
2607
+
2608
+ test("an inverted window, or an unparseable bound, is a bad request without a query", async () => {
2609
+ const principal: {
2610
+ request: JSONObject;
2611
+ databaseProps: DatabaseCommonInteractionProps;
2612
+ } = principalWith([Permission.ProjectOwner]);
2613
+
2614
+ const inverted: CallResult = await callRoute({
2615
+ uri: USERS_ROUTE,
2616
+ request: principal.request,
2617
+ body: {
2618
+ rumApplicationId: applicationAId.toString(),
2619
+ startTime: "2026-08-08T00:00:00.000Z",
2620
+ endTime: "2026-08-01T00:00:00.000Z",
2621
+ },
2622
+ });
2623
+
2624
+ expect(inverted.deniedWith).toBeInstanceOf(BadDataException);
2625
+ expect(inverted.deniedWith?.message).toContain("startTime");
2626
+ expect(headerQuerySpy).not.toHaveBeenCalled();
2627
+
2628
+ jest.clearAllMocks();
2629
+ mockProps(principal.databaseProps);
2630
+ mockApplication({ id: applicationAId, labelIds: [] });
2631
+
2632
+ const garbage: CallResult = await callRoute({
2633
+ uri: USERS_ROUTE,
2634
+ request: principal.request,
2635
+ body: {
2636
+ rumApplicationId: applicationAId.toString(),
2637
+ endTime: "last tuesday",
2638
+ },
2639
+ });
2640
+
2641
+ expect(garbage.thrownToNext).toBeInstanceOf(BadDataException);
2642
+ expect(headerQuerySpy).not.toHaveBeenCalled();
2643
+ });
2644
+
2645
+ test.each([2.5, 0, -1, "20"])(
2646
+ "limit %p is a bad request rather than a ClickHouse error",
2647
+ async (limit: unknown) => {
2648
+ const principal: {
2649
+ request: JSONObject;
2650
+ databaseProps: DatabaseCommonInteractionProps;
2651
+ } = principalWith([Permission.ProjectOwner]);
2652
+
2653
+ const result: CallResult = await callRoute({
2654
+ uri: USERS_ROUTE,
2655
+ request: principal.request,
2656
+ body: {
2657
+ rumApplicationId: applicationAId.toString(),
2658
+ limit: limit as number,
2659
+ },
2660
+ });
2661
+
2662
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
2663
+ expect((result.thrownToNext as Exception).message).toContain("limit");
2664
+ expect(headerQuerySpy).not.toHaveBeenCalled();
2665
+ },
2666
+ );
2667
+
2668
+ /*
2669
+ * The identity decision is made per principal against the application
2670
+ * the access check loaded - exactly as on /list - and handed to the
2671
+ * service, which enforces it by not naming the columns.
2672
+ */
2673
+ test("hands the service includeIdentifiedUserLabel true for an owner and false for a TelemetryAdmin, with the defaults", async () => {
2674
+ const listUsersSpy: jest.SpyInstance = jest
2675
+ .spyOn(SessionReplayReadService, "listUsers")
2676
+ .mockResolvedValue({ users: [], nextCursor: null });
2677
+
2678
+ const owner: {
2679
+ request: JSONObject;
2680
+ databaseProps: DatabaseCommonInteractionProps;
2681
+ } = principalWith([Permission.ProjectOwner]);
2682
+
2683
+ const before: number = Date.now();
2684
+
2685
+ await callRoute({
2686
+ uri: USERS_ROUTE,
2687
+ request: owner.request,
2688
+ body: { rumApplicationId: applicationAId.toString() },
2689
+ });
2690
+
2691
+ expect(listUsersSpy).toHaveBeenCalledTimes(1);
2692
+
2693
+ const ownerRequest: SessionReplayUsersRequest = listUsersSpy.mock
2694
+ .calls[0]![0] as SessionReplayUsersRequest;
2695
+
2696
+ expect(ownerRequest.includeIdentifiedUserLabel).toBe(true);
2697
+ expect(ownerRequest.projectId.toString()).toBe(projectId.toString());
2698
+ expect(ownerRequest.rumApplicationId.toString()).toBe(
2699
+ applicationAId.toString(),
2700
+ );
2701
+ expect(ownerRequest.limit).toBe(DEFAULT_SESSION_REPLAY_USERS_LIMIT);
2702
+ expect(ownerRequest.cursor).toBeUndefined();
2703
+ /* The 7-day default window, like /list (a DST edge may shift it an hour). */
2704
+ const windowMs: number =
2705
+ ownerRequest.endTime.getTime() - ownerRequest.startTime.getTime();
2706
+ expect(windowMs).toBeGreaterThan(6.9 * 24 * 60 * 60 * 1000);
2707
+ expect(windowMs).toBeLessThan(7.1 * 24 * 60 * 60 * 1000);
2708
+ expect(ownerRequest.endTime.getTime()).toBeGreaterThanOrEqual(before);
2709
+
2710
+ jest.clearAllMocks();
2711
+ listUsersSpy.mockResolvedValue({ users: [], nextCursor: null });
2712
+
2713
+ const telemetryAdmin: {
2714
+ request: JSONObject;
2715
+ databaseProps: DatabaseCommonInteractionProps;
2716
+ } = principalWith([Permission.TelemetryAdmin]);
2717
+
2718
+ await callRoute({
2719
+ uri: USERS_ROUTE,
2720
+ request: telemetryAdmin.request,
2721
+ body: { rumApplicationId: applicationAId.toString() },
2722
+ });
2723
+
2724
+ const adminRequest: SessionReplayUsersRequest = listUsersSpy.mock
2725
+ .calls[0]![0] as SessionReplayUsersRequest;
2726
+
2727
+ expect(adminRequest.includeIdentifiedUserLabel).toBe(false);
2728
+ });
2729
+
2730
+ test("includeIdentifiedUserLabel follows the identity grant's label scope", async () => {
2731
+ const listUsersSpy: jest.SpyInstance = jest
2732
+ .spyOn(SessionReplayReadService, "listUsers")
2733
+ .mockResolvedValue({ users: [], nextCursor: null });
2734
+
2735
+ /* Identity grant scoped to label B; the application carries label A. */
2736
+ const principal: {
2737
+ request: JSONObject;
2738
+ databaseProps: DatabaseCommonInteractionProps;
2739
+ } = buildPrincipal({
2740
+ projectId: projectId,
2741
+ userId: userId,
2742
+ permissions: [Permission.ReadRumSessionReplayPayload],
2743
+ labelIds: [labelBId],
2744
+ });
2745
+ grantUnscopedListPermission(principal.databaseProps, projectId);
2746
+
2747
+ mockProps(principal.databaseProps);
2748
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
2749
+
2750
+ await callRoute({
2751
+ uri: USERS_ROUTE,
2752
+ request: principal.request,
2753
+ body: { rumApplicationId: applicationAId.toString() },
2754
+ });
2755
+
2756
+ const request: SessionReplayUsersRequest = listUsersSpy.mock
2757
+ .calls[0]![0] as SessionReplayUsersRequest;
2758
+
2759
+ expect(request.includeIdentifiedUserLabel).toBe(false);
2760
+ });
2761
+
2762
+ test("the window, limit and cursor reach the service as given", async () => {
2763
+ const listUsersSpy: jest.SpyInstance = jest
2764
+ .spyOn(SessionReplayReadService, "listUsers")
2765
+ .mockResolvedValue({ users: [], nextCursor: null });
2766
+
2767
+ const principal: {
2768
+ request: JSONObject;
2769
+ databaseProps: DatabaseCommonInteractionProps;
2770
+ } = principalWith([Permission.ProjectOwner]);
2771
+
2772
+ await callRoute({
2773
+ uri: USERS_ROUTE,
2774
+ request: principal.request,
2775
+ body: {
2776
+ rumApplicationId: applicationAId.toString(),
2777
+ startTime: "2026-08-01T00:00:00.000Z",
2778
+ endTime: "2026-08-08T00:00:00.000Z",
2779
+ limit: 7,
2780
+ cursor: { lastSeenUnixMs: 1700000900000, groupKey: "u:hmac-1" },
2781
+ },
2782
+ });
2783
+
2784
+ const request: SessionReplayUsersRequest = listUsersSpy.mock
2785
+ .calls[0]![0] as SessionReplayUsersRequest;
2786
+
2787
+ expect(request.startTime.toISOString()).toBe("2026-08-01T00:00:00.000Z");
2788
+ expect(request.endTime.toISOString()).toBe("2026-08-08T00:00:00.000Z");
2789
+ expect(request.limit).toBe(7);
2790
+ expect(request.cursor).toEqual({
2791
+ lastSeenUnixMs: 1700000900000,
2792
+ groupKey: "u:hmac-1",
2793
+ });
2794
+ });
2795
+
2796
+ test("omits the label and traits, and never names them, for a caller without the identity permission", async () => {
2797
+ const principal: {
2798
+ request: JSONObject;
2799
+ databaseProps: DatabaseCommonInteractionProps;
2800
+ } = principalWith([Permission.TelemetryAdmin]);
2801
+
2802
+ /* The row carries them; the handler must not have asked for them. */
2803
+ headerQuerySpy.mockResolvedValue(fakeResultSet([rollupRow()]) as never);
2804
+
2805
+ const result: CallResult = await callRoute({
2806
+ uri: USERS_ROUTE,
2807
+ request: principal.request,
2808
+ body: { rumApplicationId: applicationAId.toString() },
2809
+ });
2810
+
2811
+ const statement: Statement = headerQuerySpy.mock
2812
+ .calls[0]![0] as Statement;
2813
+ expect(statement.query).not.toContain("identifiedUserLabel");
2814
+ expect(statement.query).not.toContain("identifiedUserTraits");
2815
+
2816
+ const users: Array<JSONObject> = (result.jsonBody as JSONObject)[
2817
+ "users"
2818
+ ] as unknown as Array<JSONObject>;
2819
+
2820
+ expect(users[0]!["identifiedUserLabel"]).toBeUndefined();
2821
+ expect(users[0]!["identifiedUserTraits"]).toBeUndefined();
2822
+ /* The ungated keys are still there. */
2823
+ expect(users[0]!["identifiedUserKey"]).toBe("hmac-1");
2824
+ expect(users[0]!["visitorId"]).toBe(VISITOR_ID);
2825
+ });
2826
+
2827
+ test("includes the label and traits, named at both levels, for a ProjectOwner", async () => {
2828
+ const principal: {
2829
+ request: JSONObject;
2830
+ databaseProps: DatabaseCommonInteractionProps;
2831
+ } = principalWith([Permission.ProjectOwner]);
2832
+
2833
+ headerQuerySpy.mockResolvedValue(fakeResultSet([rollupRow()]) as never);
2834
+
2835
+ const result: CallResult = await callRoute({
2836
+ uri: USERS_ROUTE,
2837
+ request: principal.request,
2838
+ body: { rumApplicationId: applicationAId.toString() },
2839
+ });
2840
+
2841
+ const statement: Statement = headerQuerySpy.mock
2842
+ .calls[0]![0] as Statement;
2843
+ expect(statement.query).toContain(
2844
+ "argMax(identifiedUserLabel, version) AS aggIdentifiedUserLabel",
2845
+ );
2846
+ expect(statement.query).toContain(
2847
+ "argMax(aggIdentifiedUserLabel, aggStartTime) AS rollupIdentifiedUserLabel",
2848
+ );
2849
+
2850
+ const users: Array<JSONObject> = (result.jsonBody as JSONObject)[
2851
+ "users"
2852
+ ] as unknown as Array<JSONObject>;
2853
+
2854
+ expect(users[0]!["identifiedUserLabel"]).toBe("jane@example.com");
2855
+ expect(users[0]!["identifiedUserTraits"]).toEqual({ plan: "pro" });
2856
+ });
2857
+
2858
+ test("pages by the last row's clock and key, keeping the anonymous bucket as a row", async () => {
2859
+ const principal: {
2860
+ request: JSONObject;
2861
+ databaseProps: DatabaseCommonInteractionProps;
2862
+ } = principalWith([Permission.ProjectOwner]);
2863
+
2864
+ headerQuerySpy.mockResolvedValue(
2865
+ fakeResultSet([
2866
+ rollupRow(),
2867
+ rollupRow({
2868
+ rollupKey: "",
2869
+ rollupIdentifiedUserKey: "",
2870
+ rollupVisitorId: "",
2871
+ rollupLastSeenUnixMs: 1700000700000,
2872
+ rollupSessionCount: 12,
2873
+ }),
2874
+ rollupRow({
2875
+ rollupKey: `v:${VISITOR_ID}`,
2876
+ rollupIdentifiedUserKey: "",
2877
+ rollupLastSeenUnixMs: 1700000600000,
2878
+ }),
2879
+ ]) as never,
2880
+ );
2881
+
2882
+ const result: CallResult = await callRoute({
2883
+ uri: USERS_ROUTE,
2884
+ request: principal.request,
2885
+ body: { rumApplicationId: applicationAId.toString(), limit: 2 },
2886
+ });
2887
+
2888
+ const body: JSONObject = result.jsonBody as JSONObject;
2889
+ const users: Array<JSONObject> = body[
2890
+ "users"
2891
+ ] as unknown as Array<JSONObject>;
2892
+
2893
+ expect(users).toHaveLength(2);
2894
+ expect(users[1]!["kind"]).toBe("anonymous");
2895
+ expect(users[1]!["groupKey"]).toBe("");
2896
+ expect(users[1]!["identifiedUserKey"]).toBe("");
2897
+ expect(users[1]!["sessionCount"]).toBe(12);
2898
+ expect(body["nextCursor"]).toEqual({
2899
+ lastSeenUnixMs: 1700000700000,
2900
+ groupKey: "",
2901
+ });
2902
+ });
2903
+
2904
+ test("the tenant comes from databaseProps, not from the body", async () => {
2905
+ const principal: {
2906
+ request: JSONObject;
2907
+ databaseProps: DatabaseCommonInteractionProps;
2908
+ } = principalWith([Permission.ProjectOwner]);
2909
+
2910
+ const otherProjectId: ObjectID = ObjectID.generate();
2911
+
2912
+ await callRoute({
2913
+ uri: USERS_ROUTE,
2914
+ request: principal.request,
2915
+ body: {
2916
+ rumApplicationId: applicationAId.toString(),
2917
+ projectId: otherProjectId.toString(),
2918
+ },
2919
+ });
2920
+
2921
+ const statement: Statement = headerQuerySpy.mock
2922
+ .calls[0]![0] as Statement;
2923
+ const bound: Array<unknown> = Object.values(statement.query_params);
2924
+
2925
+ expect(bound).toContain(projectId.toString());
2926
+ expect(bound).not.toContain(otherProjectId.toString());
2927
+ });
2928
+ });
2929
+
2930
+ describe("manifest", () => {
2931
+ test("never names the payload column and writes the read audit row", async () => {
2932
+ const principal: {
2933
+ request: JSONObject;
2934
+ databaseProps: DatabaseCommonInteractionProps;
2935
+ } = buildPrincipal({
2936
+ projectId: projectId,
2937
+ userId: userId,
2938
+ permissions: [Permission.ReadRumSessionReplayPayload],
2939
+ });
2940
+
2941
+ mockProps(principal.databaseProps);
2942
+ mockSessionHeader(applicationAId);
2943
+ mockApplication({ id: applicationAId, labelIds: [] });
2944
+ const viewId: ObjectID = mockRecordedView();
2945
+
2946
+ chunkQuerySpy.mockResolvedValue(
2947
+ fakeResultSet([
2948
+ {
2949
+ tabId: "tab-1",
2950
+ chunkIndex: 0,
2951
+ chunkStartOffsetMs: 0,
2952
+ chunkEndOffsetMs: 15000,
2953
+ eventCount: 100,
2954
+ hasFullSnapshot: true,
2955
+ chunkPayloadBytes: 1024,
2956
+ errorCount: 2,
2957
+ rageClickCount: 1,
2958
+ deadClickCount: 4,
2959
+ errorClickCount: 6,
2960
+ refreshRageCount: 5,
2961
+ routeCount: 3,
2962
+ },
2963
+ {
2964
+ tabId: "tab-1",
2965
+ chunkIndex: 3,
2966
+ chunkStartOffsetMs: 45000,
2967
+ chunkEndOffsetMs: 60000,
2968
+ eventCount: 100,
2969
+ hasFullSnapshot: false,
2970
+ chunkPayloadBytes: 1024,
2971
+ errorCount: 0,
2972
+ rageClickCount: 0,
2973
+ deadClickCount: 0,
2974
+ errorClickCount: 0,
2975
+ refreshRageCount: 0,
2976
+ routeCount: 0,
2977
+ },
2978
+ ]) as never,
2979
+ );
2980
+
2981
+ const result: CallResult = await callRoute({
2982
+ uri: MANIFEST_ROUTE,
2983
+ request: principal.request,
2984
+ body: { sessionId: "session-1", accessReason: "incident triage" },
2985
+ });
2986
+
2987
+ const statement: Statement = chunkQuerySpy.mock.calls[0]![0] as Statement;
2988
+
2989
+ expect(statement.query).not.toContain("payload,");
2990
+ expect(statement.query).not.toMatch(/\bpayload\b(?!Bytes)/);
2991
+ expect(statement.query).toContain("LIMIT 1 BY tabId, chunkIndex");
2992
+
2993
+ /*
2994
+ * The signal counters must be projected: the player's error,
2995
+ * frustration and route timeline lanes and the "next error" jump are
2996
+ * fed exclusively by these per-chunk columns. Dropping one from the
2997
+ * SELECT silently blanks a lane — it parses as 0 client-side.
2998
+ */
2999
+ for (const counterColumn of [
3000
+ "errorCount",
3001
+ "rageClickCount",
3002
+ "deadClickCount",
3003
+ "errorClickCount",
3004
+ "refreshRageCount",
3005
+ "routeCount",
3006
+ ]) {
3007
+ expect(statement.query).toContain(counterColumn);
3008
+ }
3009
+
3010
+ expect(recordViewSpy).toHaveBeenCalledTimes(1);
3011
+ const auditArgs: JSONObject = recordViewSpy.mock
3012
+ .calls[0]![0] as JSONObject;
3013
+ expect((auditArgs["projectId"] as ObjectID).toString()).toBe(
3014
+ projectId.toString(),
3015
+ );
3016
+ expect((auditArgs["rumApplicationId"] as ObjectID).toString()).toBe(
3017
+ applicationAId.toString(),
3018
+ );
3019
+ expect(auditArgs["sessionId"]).toBe("session-1");
3020
+ expect(auditArgs["accessReason"]).toBe("incident triage");
3021
+ expect(auditArgs["ipAddress"]).toBe("203.0.113.7");
3022
+
3023
+ const body: JSONObject = result.jsonBody as JSONObject;
3024
+ expect(body["viewId"]).toBe(viewId.toString());
3025
+
3026
+ const tabs: Array<JSONObject> = body[
3027
+ "tabs"
3028
+ ] as unknown as Array<JSONObject>;
3029
+ expect(tabs).toHaveLength(1);
3030
+ expect(tabs[0]!["chunkIndexes"]).toEqual([0, 3]);
3031
+ expect(tabs[0]!["fullSnapshotChunkIndexes"]).toEqual([0]);
3032
+
3033
+ /*
3034
+ * Every counter gets a DISTINCT nonzero value, asserted individually:
3035
+ * the client parses missing keys to 0, so a counter dropped from the
3036
+ * SELECT (or two counters cross-wired in the row mapping) fails
3037
+ * loudly here instead of silently blanking a timeline lane.
3038
+ */
3039
+ const manifestChunks: Array<JSONObject> = tabs[0]![
3040
+ "chunks"
3041
+ ] as unknown as Array<JSONObject>;
3042
+ expect(manifestChunks[0]!["errorCount"]).toBe(2);
3043
+ expect(manifestChunks[0]!["rageClickCount"]).toBe(1);
3044
+ expect(manifestChunks[0]!["deadClickCount"]).toBe(4);
3045
+ expect(manifestChunks[0]!["errorClickCount"]).toBe(6);
3046
+ expect(manifestChunks[0]!["refreshRageCount"]).toBe(5);
3047
+ expect(manifestChunks[0]!["routeCount"]).toBe(3);
3048
+ expect(manifestChunks[1]!["errorCount"]).toBe(0);
3049
+
3050
+ /*
3051
+ * The hole between chunk 0 and chunk 3 must be reported. Playback
3052
+ * that silently crosses a gap renders a DOM the user never saw.
3053
+ */
3054
+ const gaps: Array<JSONObject> = tabs[0]![
3055
+ "gaps"
3056
+ ] as unknown as Array<JSONObject>;
3057
+ expect(gaps).toHaveLength(1);
3058
+ expect(gaps[0]!["fromIndex"]).toBe(0);
3059
+ expect(gaps[0]!["toIndex"]).toBe(3);
3060
+ expect(gaps[0]!["missingMs"]).toBe(30000);
3061
+ });
3062
+
3063
+ test("a session that does not exist in this tenant is refused before any audit row", async () => {
3064
+ const principal: {
3065
+ request: JSONObject;
3066
+ databaseProps: DatabaseCommonInteractionProps;
3067
+ } = buildPrincipal({
3068
+ projectId: projectId,
3069
+ userId: userId,
3070
+ permissions: [Permission.ProjectOwner],
3071
+ });
3072
+
3073
+ mockProps(principal.databaseProps);
3074
+ headerQuerySpy.mockResolvedValue(fakeResultSet([]) as never);
3075
+
3076
+ const result: CallResult = await callRoute({
3077
+ uri: MANIFEST_ROUTE,
2346
3078
  request: principal.request,
2347
3079
  body: { sessionId: "does-not-exist" },
2348
3080
  });
@@ -2499,8 +3231,134 @@ describe("Session replay playback API", () => {
2499
3231
  clickCount: 2,
2500
3232
  url: "https://example.com/help",
2501
3233
  },
2502
- ]) as never,
2503
- );
3234
+ ]) as never,
3235
+ );
3236
+
3237
+ const result: CallResult = await callRoute({
3238
+ uri: MANIFEST_ROUTE,
3239
+ request: principal.request,
3240
+ body: { sessionId: "session-1" },
3241
+ });
3242
+
3243
+ const body: JSONObject = result.jsonBody as JSONObject;
3244
+ const header: JSONObject = body["header"] as JSONObject;
3245
+
3246
+ expect(header["startTimeUnixMs"]).toBe(1700000000000);
3247
+ expect(header["endTimeUnixMs"]).toBe(1700000060000);
3248
+ expect(header["clientReportedStartUnixMs"]).toBe(1699999999500);
3249
+ expect(header["tags"]).toEqual({ build: "1.2.3" });
3250
+ expect(header["expiresAtUnixMs"]).toBe(1700604800000);
3251
+ expect(header["clickCount"]).toBe(41);
3252
+ expect(header["customEventCount"]).toBe(3);
3253
+ expect(header["activeMs"]).toBe(36000);
3254
+ expect(header["firstErrorOffsetMs"]).toBe(12000);
3255
+ expect(header["recorderCapabilities"]).toEqual(["click-events", "tags"]);
3256
+
3257
+ const tabs: Array<JSONObject> = body[
3258
+ "tabs"
3259
+ ] as unknown as Array<JSONObject>;
3260
+ expect(tabs[0]!["firstChunkStartOffsetMs"]).toBe(134000);
3261
+ const chunks: Array<JSONObject> = tabs[0]![
3262
+ "chunks"
3263
+ ] as unknown as Array<JSONObject>;
3264
+ expect(chunks[0]!["clickCount"]).toBe(2);
3265
+ expect(chunks[0]!["url"]).toBe("https://example.com/help");
3266
+ });
3267
+
3268
+ /*
3269
+ * The pseudonymous digest and the visitor id are what the player hands
3270
+ * back to /list for "this person's other sessions". Neither names
3271
+ * anyone and the list already returns both to every list-capable
3272
+ * caller, so they ride on the header read itself for a caller WITHOUT
3273
+ * the identity permission - not on the separate, gated identity read.
3274
+ */
3275
+ test("the header carries identifiedUserKey and visitorId for a caller without the identity permission", async () => {
3276
+ const principal: {
3277
+ request: JSONObject;
3278
+ databaseProps: DatabaseCommonInteractionProps;
3279
+ } = buildPrincipal({
3280
+ projectId: projectId,
3281
+ userId: userId,
3282
+ permissions: [Permission.TelemetryAdmin],
3283
+ });
3284
+
3285
+ mockProps(principal.databaseProps);
3286
+ headerQuerySpy.mockResolvedValue(
3287
+ fakeResultSet([
3288
+ {
3289
+ ...buildHeaderRow({
3290
+ sessionId: "session-1",
3291
+ projectId: projectId,
3292
+ rumApplicationId: applicationAId,
3293
+ }),
3294
+ aggVisitorId: VISITOR_ID,
3295
+ },
3296
+ ]) as never,
3297
+ );
3298
+ mockApplication({ id: applicationAId, labelIds: [] });
3299
+ mockRecordedView();
3300
+
3301
+ const result: CallResult = await callRoute({
3302
+ uri: MANIFEST_ROUTE,
3303
+ request: principal.request,
3304
+ body: { sessionId: "session-1" },
3305
+ });
3306
+
3307
+ /* One header read, and it is the one that projected both keys. */
3308
+ expect(headerQuerySpy).toHaveBeenCalledTimes(1);
3309
+ const statement: Statement = headerQuerySpy.mock
3310
+ .calls[0]![0] as Statement;
3311
+ expect(statement.query).toContain(
3312
+ "argMax(identifiedUserKey, version) AS aggIdentifiedUserKey",
3313
+ );
3314
+ expect(statement.query).toContain(
3315
+ "argMax(visitorId, version) AS aggVisitorId",
3316
+ );
3317
+ expect(statement.query).not.toContain("identifiedUserLabel");
3318
+
3319
+ const header: JSONObject = (result.jsonBody as JSONObject)[
3320
+ "header"
3321
+ ] as JSONObject;
3322
+ expect(header["identifiedUserKey"]).toBe("hmac-key");
3323
+ expect(header["visitorId"]).toBe(VISITOR_ID);
3324
+ expect(header["identifiedUserLabel"]).toBeUndefined();
3325
+ expect(header["identifiedUserTraits"]).toBeUndefined();
3326
+ });
3327
+
3328
+ test("the header keeps identifiedUserKey and visitorId beside the label for an identity-permitted caller", async () => {
3329
+ const principal: {
3330
+ request: JSONObject;
3331
+ databaseProps: DatabaseCommonInteractionProps;
3332
+ } = buildPrincipal({
3333
+ projectId: projectId,
3334
+ userId: userId,
3335
+ permissions: [Permission.ProjectOwner],
3336
+ });
3337
+
3338
+ mockProps(principal.databaseProps);
3339
+ headerQuerySpy.mockImplementation(async (statement: Statement) => {
3340
+ if (statement.query.includes("identifiedUserTraits")) {
3341
+ return fakeResultSet([
3342
+ {
3343
+ aggIdentifiedUserLabel: "jane@example.com",
3344
+ aggIdentifiedUserTraits: { plan: "pro" },
3345
+ },
3346
+ ]);
3347
+ }
3348
+
3349
+ return fakeResultSet([
3350
+ {
3351
+ ...buildHeaderRow({
3352
+ sessionId: "session-1",
3353
+ projectId: projectId,
3354
+ rumApplicationId: applicationAId,
3355
+ }),
3356
+ aggVisitorId: VISITOR_ID,
3357
+ },
3358
+ ]);
3359
+ });
3360
+ mockApplication({ id: applicationAId, labelIds: [] });
3361
+ mockRecordedView();
2504
3362
 
2505
3363
  const result: CallResult = await callRoute({
2506
3364
  uri: MANIFEST_ROUTE,
@@ -2508,29 +3366,13 @@ describe("Session replay playback API", () => {
2508
3366
  body: { sessionId: "session-1" },
2509
3367
  });
2510
3368
 
2511
- const body: JSONObject = result.jsonBody as JSONObject;
2512
- const header: JSONObject = body["header"] as JSONObject;
2513
-
2514
- expect(header["startTimeUnixMs"]).toBe(1700000000000);
2515
- expect(header["endTimeUnixMs"]).toBe(1700000060000);
2516
- expect(header["clientReportedStartUnixMs"]).toBe(1699999999500);
2517
- expect(header["tags"]).toEqual({ build: "1.2.3" });
2518
- expect(header["expiresAtUnixMs"]).toBe(1700604800000);
2519
- expect(header["clickCount"]).toBe(41);
2520
- expect(header["customEventCount"]).toBe(3);
2521
- expect(header["activeMs"]).toBe(36000);
2522
- expect(header["firstErrorOffsetMs"]).toBe(12000);
2523
- expect(header["recorderCapabilities"]).toEqual(["click-events", "tags"]);
2524
-
2525
- const tabs: Array<JSONObject> = body[
2526
- "tabs"
2527
- ] as unknown as Array<JSONObject>;
2528
- expect(tabs[0]!["firstChunkStartOffsetMs"]).toBe(134000);
2529
- const chunks: Array<JSONObject> = tabs[0]![
2530
- "chunks"
2531
- ] as unknown as Array<JSONObject>;
2532
- expect(chunks[0]!["clickCount"]).toBe(2);
2533
- expect(chunks[0]!["url"]).toBe("https://example.com/help");
3369
+ const header: JSONObject = (result.jsonBody as JSONObject)[
3370
+ "header"
3371
+ ] as JSONObject;
3372
+ expect(header["identifiedUserKey"]).toBe("hmac-key");
3373
+ expect(header["visitorId"]).toBe(VISITOR_ID);
3374
+ expect(header["identifiedUserLabel"]).toBe("jane@example.com");
3375
+ expect(header["identifiedUserTraits"]).toEqual({ plan: "pro" });
2534
3376
  });
2535
3377
 
2536
3378
  /*
@@ -3147,6 +3989,322 @@ describe("Session replay playback API", () => {
3147
3989
  );
3148
3990
  });
3149
3991
 
3992
+ /*
3993
+ * The payload column is stored decompressed and the body is
3994
+ * application/octet-stream, which nginx's gzip_types does not cover,
3995
+ * so the route must compress on the wire itself or a page ships as
3996
+ * 1-8 MB of raw JSON. The browser's fetch() inflates transparently,
3997
+ * so the decoded bytes must be the very same frames.
3998
+ */
3999
+ test("gzips the frames when the caller accepts gzip", async () => {
4000
+ const principal: {
4001
+ request: JSONObject;
4002
+ databaseProps: DatabaseCommonInteractionProps;
4003
+ } = payloadPrincipal();
4004
+
4005
+ mockProps(principal.databaseProps);
4006
+ mockSessionHeader(applicationAId);
4007
+ mockApplication({ id: applicationAId, labelIds: [] });
4008
+
4009
+ const payload: string = "[" + "1,".repeat(600) + "1]";
4010
+
4011
+ chunkQuerySpy.mockResolvedValue(
4012
+ fakeResultSet([
4013
+ { chunkIndex: 0, servedPayload: payload, isServed: 1 },
4014
+ { chunkIndex: 1, servedPayload: "[22]", isServed: 1 },
4015
+ ]) as never,
4016
+ );
4017
+
4018
+ const result: CallResult = await callRoute({
4019
+ uri: CHUNKS_ROUTE,
4020
+ request: principal.request,
4021
+ body: {
4022
+ sessionId: "session-1",
4023
+ tabId: "tab-1",
4024
+ chunkIndexes: [0, 1],
4025
+ },
4026
+ headers: { "accept-encoding": "gzip, deflate, br" },
4027
+ });
4028
+
4029
+ expect(result.thrownToNext).toBeUndefined();
4030
+ expect(result.deniedWith).toBeUndefined();
4031
+
4032
+ const sent: Buffer = result.sentBuffer as unknown as Buffer;
4033
+ expect(sent).toBeDefined();
4034
+
4035
+ expect(result.headers["Content-Encoding"]).toBe("gzip");
4036
+ expect(result.headers["Vary"]).toBe("Accept-Encoding");
4037
+ expect(result.headers["Content-Type"]).toBe("application/octet-stream");
4038
+ expect(result.headers["Cache-Control"]).toBe("no-store");
4039
+ // Content-Length describes the bytes actually sent, not the frames.
4040
+ expect(result.headers["Content-Length"]).toBe(String(sent.length));
4041
+ expect(sent.length).toBeLessThan(8 + payload.length);
4042
+
4043
+ const decoded: Buffer = zlib.gunzipSync(sent);
4044
+ expect(decoded.length).toBe(8 + payload.length + 8 + 4);
4045
+ expect(decoded.readUInt32LE(0)).toBe(0);
4046
+ expect(decoded.readUInt32LE(4)).toBe(payload.length);
4047
+ expect(decoded.subarray(8, 8 + payload.length).toString("utf8")).toBe(
4048
+ payload,
4049
+ );
4050
+ expect(decoded.readUInt32LE(8 + payload.length)).toBe(1);
4051
+ expect(decoded.readUInt32LE(12 + payload.length)).toBe(4);
4052
+ expect(decoded.subarray(16 + payload.length).toString("utf8")).toBe(
4053
+ "[22]",
4054
+ );
4055
+ });
4056
+
4057
+ test("honours a positive q-value and the x-gzip alias", async () => {
4058
+ const principal: {
4059
+ request: JSONObject;
4060
+ databaseProps: DatabaseCommonInteractionProps;
4061
+ } = payloadPrincipal();
4062
+
4063
+ mockProps(principal.databaseProps);
4064
+ mockSessionHeader(applicationAId);
4065
+ mockApplication({ id: applicationAId, labelIds: [] });
4066
+
4067
+ const payload: string = "[" + "1,".repeat(600) + "1]";
4068
+
4069
+ for (const acceptEncoding of [
4070
+ "deflate;q=1.0, gzip;q=0.5",
4071
+ "X-GZIP",
4072
+ " br , gzip ; q=0.1 ",
4073
+ ]) {
4074
+ chunkQuerySpy.mockResolvedValue(
4075
+ fakeResultSet([
4076
+ { chunkIndex: 0, servedPayload: payload, isServed: 1 },
4077
+ ]) as never,
4078
+ );
4079
+
4080
+ const result: CallResult = await callRoute({
4081
+ uri: CHUNKS_ROUTE,
4082
+ request: principal.request,
4083
+ body: {
4084
+ sessionId: "session-1",
4085
+ tabId: "tab-1",
4086
+ chunkIndexes: [0],
4087
+ },
4088
+ headers: { "accept-encoding": acceptEncoding },
4089
+ });
4090
+
4091
+ expect(result.thrownToNext).toBeUndefined();
4092
+ expect(result.headers["Content-Encoding"]).toBe("gzip");
4093
+
4094
+ const decoded: Buffer = zlib.gunzipSync(
4095
+ result.sentBuffer as unknown as Buffer,
4096
+ );
4097
+ expect(decoded.readUInt32LE(4)).toBe(payload.length);
4098
+ }
4099
+ });
4100
+
4101
+ test("sends identity frames when Accept-Encoding is absent", async () => {
4102
+ const principal: {
4103
+ request: JSONObject;
4104
+ databaseProps: DatabaseCommonInteractionProps;
4105
+ } = payloadPrincipal();
4106
+
4107
+ mockProps(principal.databaseProps);
4108
+ mockSessionHeader(applicationAId);
4109
+ mockApplication({ id: applicationAId, labelIds: [] });
4110
+
4111
+ const payload: string = "[" + "1,".repeat(600) + "1]";
4112
+
4113
+ chunkQuerySpy.mockResolvedValue(
4114
+ fakeResultSet([
4115
+ { chunkIndex: 0, servedPayload: payload, isServed: 1 },
4116
+ ]) as never,
4117
+ );
4118
+
4119
+ const result: CallResult = await callRoute({
4120
+ uri: CHUNKS_ROUTE,
4121
+ request: principal.request,
4122
+ body: {
4123
+ sessionId: "session-1",
4124
+ tabId: "tab-1",
4125
+ chunkIndexes: [0],
4126
+ },
4127
+ });
4128
+
4129
+ const sent: Buffer = result.sentBuffer as unknown as Buffer;
4130
+
4131
+ expect(result.headers["Content-Encoding"]).toBeUndefined();
4132
+ // The answer still depends on the request header, so Vary is set.
4133
+ expect(result.headers["Vary"]).toBe("Accept-Encoding");
4134
+ expect(result.headers["Content-Length"]).toBe(String(8 + payload.length));
4135
+ expect(sent.length).toBe(8 + payload.length);
4136
+ expect(sent.readUInt32LE(0)).toBe(0);
4137
+ expect(sent.readUInt32LE(4)).toBe(payload.length);
4138
+ });
4139
+
4140
+ test("sends identity frames when only br and deflate are accepted", async () => {
4141
+ const principal: {
4142
+ request: JSONObject;
4143
+ databaseProps: DatabaseCommonInteractionProps;
4144
+ } = payloadPrincipal();
4145
+
4146
+ mockProps(principal.databaseProps);
4147
+ mockSessionHeader(applicationAId);
4148
+ mockApplication({ id: applicationAId, labelIds: [] });
4149
+
4150
+ const payload: string = "[" + "1,".repeat(600) + "1]";
4151
+
4152
+ chunkQuerySpy.mockResolvedValue(
4153
+ fakeResultSet([
4154
+ { chunkIndex: 0, servedPayload: payload, isServed: 1 },
4155
+ ]) as never,
4156
+ );
4157
+
4158
+ const result: CallResult = await callRoute({
4159
+ uri: CHUNKS_ROUTE,
4160
+ request: principal.request,
4161
+ body: {
4162
+ sessionId: "session-1",
4163
+ tabId: "tab-1",
4164
+ chunkIndexes: [0],
4165
+ },
4166
+ headers: { "accept-encoding": "br, deflate" },
4167
+ });
4168
+
4169
+ const sent: Buffer = result.sentBuffer as unknown as Buffer;
4170
+
4171
+ expect(result.headers["Content-Encoding"]).toBeUndefined();
4172
+ expect(result.headers["Vary"]).toBe("Accept-Encoding");
4173
+ expect(sent.length).toBe(8 + payload.length);
4174
+ expect(sent.readUInt32LE(4)).toBe(payload.length);
4175
+ });
4176
+
4177
+ test("gzip;q=0 is an explicit refusal of gzip", async () => {
4178
+ const principal: {
4179
+ request: JSONObject;
4180
+ databaseProps: DatabaseCommonInteractionProps;
4181
+ } = payloadPrincipal();
4182
+
4183
+ mockProps(principal.databaseProps);
4184
+ mockSessionHeader(applicationAId);
4185
+ mockApplication({ id: applicationAId, labelIds: [] });
4186
+
4187
+ const payload: string = "[" + "1,".repeat(600) + "1]";
4188
+
4189
+ for (const acceptEncoding of ["gzip;q=0", "gzip;q=0.0, identity"]) {
4190
+ chunkQuerySpy.mockResolvedValue(
4191
+ fakeResultSet([
4192
+ { chunkIndex: 0, servedPayload: payload, isServed: 1 },
4193
+ ]) as never,
4194
+ );
4195
+
4196
+ const result: CallResult = await callRoute({
4197
+ uri: CHUNKS_ROUTE,
4198
+ request: principal.request,
4199
+ body: {
4200
+ sessionId: "session-1",
4201
+ tabId: "tab-1",
4202
+ chunkIndexes: [0],
4203
+ },
4204
+ headers: { "accept-encoding": acceptEncoding },
4205
+ });
4206
+
4207
+ const sent: Buffer = result.sentBuffer as unknown as Buffer;
4208
+
4209
+ expect(result.headers["Content-Encoding"]).toBeUndefined();
4210
+ expect(sent.length).toBe(8 + payload.length);
4211
+ expect(sent.readUInt32LE(4)).toBe(payload.length);
4212
+ }
4213
+ });
4214
+
4215
+ /* Mirrors nginx's gzip_min_length: a tiny page gains nothing. */
4216
+ test("does not gzip a page below the minimum size even when gzip is accepted", async () => {
4217
+ const principal: {
4218
+ request: JSONObject;
4219
+ databaseProps: DatabaseCommonInteractionProps;
4220
+ } = payloadPrincipal();
4221
+
4222
+ mockProps(principal.databaseProps);
4223
+ mockSessionHeader(applicationAId);
4224
+ mockApplication({ id: applicationAId, labelIds: [] });
4225
+
4226
+ chunkQuerySpy.mockResolvedValue(
4227
+ fakeResultSet([
4228
+ { chunkIndex: 0, servedPayload: "[1]", isServed: 1 },
4229
+ ]) as never,
4230
+ );
4231
+
4232
+ const result: CallResult = await callRoute({
4233
+ uri: CHUNKS_ROUTE,
4234
+ request: principal.request,
4235
+ body: {
4236
+ sessionId: "session-1",
4237
+ tabId: "tab-1",
4238
+ chunkIndexes: [0],
4239
+ },
4240
+ headers: { "accept-encoding": "gzip, deflate, br" },
4241
+ });
4242
+
4243
+ const sent: Buffer = result.sentBuffer as unknown as Buffer;
4244
+
4245
+ expect(result.headers["Content-Encoding"]).toBeUndefined();
4246
+ expect(result.headers["Vary"]).toBe("Accept-Encoding");
4247
+ expect(result.headers["Content-Length"]).toBe("11");
4248
+ expect(sent.length).toBe(11);
4249
+ expect(sent.readUInt32LE(0)).toBe(0);
4250
+ expect(sent.readUInt32LE(4)).toBe(3);
4251
+ expect(sent.subarray(8, 11).toString("utf8")).toBe("[1]");
4252
+ });
4253
+
4254
+ /*
4255
+ * The read cap bounds what the player must hold decoded in memory,
4256
+ * so it and the omitted list are judged on the uncompressed frames:
4257
+ * two 5 MiB chunks of one repeated byte gzip to a few KB, yet only
4258
+ * the first may be served.
4259
+ */
4260
+ test("judges the read cap and the omitted-chunks header on the uncompressed frames when compressing", async () => {
4261
+ const principal: {
4262
+ request: JSONObject;
4263
+ databaseProps: DatabaseCommonInteractionProps;
4264
+ } = payloadPrincipal();
4265
+
4266
+ mockProps(principal.databaseProps);
4267
+ mockSessionHeader(applicationAId);
4268
+ mockApplication({ id: applicationAId, labelIds: [] });
4269
+
4270
+ const fatPayload: string = "a".repeat(5 * 1024 * 1024);
4271
+
4272
+ chunkQuerySpy.mockResolvedValue(
4273
+ fakeResultSet([
4274
+ { chunkIndex: 0, servedPayload: fatPayload, isServed: 1 },
4275
+ { chunkIndex: 1, servedPayload: fatPayload, isServed: 1 },
4276
+ { chunkIndex: 2, servedPayload: "", isServed: 0 },
4277
+ ]) as never,
4278
+ );
4279
+
4280
+ const result: CallResult = await callRoute({
4281
+ uri: CHUNKS_ROUTE,
4282
+ request: principal.request,
4283
+ body: {
4284
+ sessionId: "session-1",
4285
+ tabId: "tab-1",
4286
+ chunkIndexes: [0, 1, 2],
4287
+ },
4288
+ headers: { "accept-encoding": "gzip, deflate, br" },
4289
+ });
4290
+
4291
+ expect(result.thrownToNext).toBeUndefined();
4292
+ expect(result.deniedWith).toBeUndefined();
4293
+
4294
+ const sent: Buffer = result.sentBuffer as unknown as Buffer;
4295
+
4296
+ expect(result.headers["Content-Encoding"]).toBe("gzip");
4297
+ expect(result.headers["Content-Length"]).toBe(String(sent.length));
4298
+ expect(sent.length).toBeLessThan(fatPayload.length);
4299
+ expect(result.headers["X-OneUptime-Replay-Omitted-Chunks"]).toBe("1,2");
4300
+ expect(result.headers["Cache-Control"]).toBe("no-store");
4301
+
4302
+ const decoded: Buffer = zlib.gunzipSync(sent);
4303
+ expect(decoded.readUInt32LE(0)).toBe(0);
4304
+ expect(decoded.readUInt32LE(4)).toBe(fatPayload.length);
4305
+ expect(decoded.length).toBe(8 + fatPayload.length);
4306
+ });
4307
+
3150
4308
  test("rejects a non-integer chunk index", async () => {
3151
4309
  const principal: {
3152
4310
  request: JSONObject;