@oneuptime/common 14.0.9 → 14.0.10

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 (383) hide show
  1. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  2. package/Models/DatabaseModels/CephCluster.ts +213 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +213 -0
  4. package/Models/DatabaseModels/DockerHost.ts +213 -0
  5. package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
  6. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  7. package/Models/DatabaseModels/Host.ts +213 -0
  8. package/Models/DatabaseModels/Index.ts +2 -0
  9. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  10. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  11. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  12. package/Models/DatabaseModels/RunnerJob.ts +144 -0
  13. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  14. package/Server/API/AutoRemediationAPI.ts +392 -1
  15. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  18. package/Server/Infrastructure/Semaphore.ts +22 -0
  19. package/Server/Middleware/TelemetryIngest.ts +15 -5
  20. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  21. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  22. package/Server/Services/CephClusterService.ts +109 -1
  23. package/Server/Services/DatabaseServerService.ts +116 -1
  24. package/Server/Services/DockerHostService.ts +109 -1
  25. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  26. package/Server/Services/HostService.ts +126 -4
  27. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  28. package/Server/Services/PodmanHostService.ts +109 -1
  29. package/Server/Services/ProxmoxClusterService.ts +110 -1
  30. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  31. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  32. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  33. package/Server/Services/RunnerJobService.ts +611 -4
  34. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  35. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  36. package/Server/Services/VMwareVCenterService.ts +110 -1
  37. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1434 -32
  38. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +935 -21
  39. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  40. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  41. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  42. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  43. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  44. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  45. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  46. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +72 -2
  47. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  48. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  49. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
  50. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  51. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  52. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  53. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  54. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  55. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  56. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  57. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  60. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +366 -143
  61. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  63. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  64. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  65. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  66. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  67. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +93 -0
  68. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  69. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  70. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  71. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  72. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  73. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  74. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  75. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  76. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  77. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  78. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  79. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  80. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  81. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  82. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  83. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  84. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  85. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  86. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  87. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
  88. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
  89. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
  90. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  91. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  92. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  93. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  95. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  96. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  97. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  98. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  99. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  100. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  101. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  102. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  103. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  104. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  105. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
  106. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  107. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  108. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  109. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  110. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  111. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  112. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  113. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  114. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  115. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  116. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  117. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  118. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  119. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  120. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  121. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  122. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  123. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  124. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  125. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  126. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  127. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  128. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
  129. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  130. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  131. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  132. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  133. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  134. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  139. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  140. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  141. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  142. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  143. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  144. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  145. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  146. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  147. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  148. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  149. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  150. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  151. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  152. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  153. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  154. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  155. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  156. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  157. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  158. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  159. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  160. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  161. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  162. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  163. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  164. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  165. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  166. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  167. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  168. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  169. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  170. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  171. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  172. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  173. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  174. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  175. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
  176. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  177. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  178. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  179. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  180. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  181. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  182. package/Types/Runbook/RunbookStepType.ts +29 -0
  183. package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
  184. package/Utils/AI/InvestigationReport.ts +121 -15
  185. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  186. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  187. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  188. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  189. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  190. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  191. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  192. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  193. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  194. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  195. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  196. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  197. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  198. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  199. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  200. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  202. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  204. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  206. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  208. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  210. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  211. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  212. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  214. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  216. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  218. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  219. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  220. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  221. package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
  222. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  223. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  224. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  225. package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
  226. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  227. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  228. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  234. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  235. package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
  236. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  237. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  239. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  240. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  241. package/build/dist/Server/Services/CephClusterService.js +104 -0
  242. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  243. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  244. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  245. package/build/dist/Server/Services/DockerHostService.js +104 -0
  246. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  247. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  248. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  249. package/build/dist/Server/Services/HostService.js +114 -2
  250. package/build/dist/Server/Services/HostService.js.map +1 -1
  251. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  252. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  253. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  254. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  255. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  256. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  257. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  258. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  259. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  260. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  261. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  262. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  263. package/build/dist/Server/Services/RunnerJobService.js +344 -5
  264. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  265. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  266. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  267. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  268. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  269. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  270. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1029 -30
  272. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +633 -28
  274. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  276. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  278. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  279. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  280. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  281. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  282. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  283. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  284. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  285. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  286. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  287. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  288. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +50 -4
  290. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  292. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  293. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  294. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  295. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
  296. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  297. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  298. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  299. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  300. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  301. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  302. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  303. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  304. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  314. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  315. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  316. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  317. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +263 -110
  318. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  319. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  320. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  321. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  322. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  323. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  324. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  325. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  326. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  327. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  328. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  329. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  330. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  331. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  332. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  333. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  334. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  335. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
  336. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  337. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  338. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  339. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  340. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  341. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  342. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  343. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  344. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  345. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  346. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  347. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  348. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  349. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  350. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  351. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
  352. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  353. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  354. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  355. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  356. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  357. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  358. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  359. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  360. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  361. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  362. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  363. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  364. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  365. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  366. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  367. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  368. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  369. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  370. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  371. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  372. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  373. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  374. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  375. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  376. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  377. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  378. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  379. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  380. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  381. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  382. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  383. package/package.json +1 -1
@@ -1,8 +1,11 @@
1
1
  import {
2
2
  RumAlertTemplate,
3
3
  RumAlertTemplateArgs,
4
+ RumAlertTemplateContext,
5
+ RumAlertTemplateRequirement,
4
6
  getAllRumAlertTemplates,
5
7
  getRumAlertTemplateById,
8
+ getRumAlertTemplates,
6
9
  } from "../../../Types/Monitor/RumAlertTemplates";
7
10
  import MonitorStep from "../../../Types/Monitor/MonitorStep";
8
11
  import MonitorType from "../../../Types/Monitor/MonitorType";
@@ -15,7 +18,10 @@ import {
15
18
  } from "../../../Types/Monitor/CriteriaFilter";
16
19
  import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
17
20
  import RollingTime from "../../../Types/RollingTime/RollingTime";
21
+ import RollingTimeUtil from "../../../Types/RollingTime/RollingTimeUtil";
22
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
18
23
  import ObjectID from "../../../Types/ObjectID";
24
+ import { JSONObject } from "../../../Types/JSON";
19
25
  import { SpanStatus } from "../../../Models/AnalyticsModels/Span";
20
26
  import MonitorStepMetricMonitor from "../../../Types/Monitor/MonitorStepMetricMonitor";
21
27
  import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
@@ -25,6 +31,10 @@ import {
25
31
  ServiceAlertTemplate,
26
32
  getAllServiceAlertTemplates,
27
33
  } from "../../../Types/Monitor/ServiceAlertTemplates";
34
+ import SessionReplayBudgetMetricType from "../../../Types/Rum/SessionReplayBudgetMetricType";
35
+ import SessionReplayBudgetMetricTypeUtil, {
36
+ SESSION_REPLAY_BUDGET_METRIC_INTERVAL_MINUTES,
37
+ } from "../../../Utils/SessionReplay/SessionReplayBudgetMetricType";
28
38
 
29
39
  const RUM_APPLICATION_ID: ObjectID = ObjectID.generate();
30
40
 
@@ -136,8 +146,165 @@ const SEVERITY_CASES: Array<{
136
146
  category: "Errors",
137
147
  severity: "Critical",
138
148
  },
149
+ /*
150
+ * "Nearly spent" warns while there is still time to act; "spent" means the
151
+ * recorders have already been told to stop, so nothing more is recorded.
152
+ */
153
+ {
154
+ id: "rum-session-replay-daily-budget-nearly-spent",
155
+ category: "Session Replay",
156
+ severity: "Warning",
157
+ },
158
+ {
159
+ id: "rum-session-replay-daily-budget-spent",
160
+ category: "Session Replay",
161
+ severity: "Critical",
162
+ },
163
+ {
164
+ id: "rum-session-replay-monthly-budget-nearly-spent",
165
+ category: "Session Replay",
166
+ severity: "Warning",
167
+ },
168
+ {
169
+ id: "rum-session-replay-monthly-budget-spent",
170
+ category: "Session Replay",
171
+ severity: "Critical",
172
+ },
173
+ ];
174
+
175
+ /*
176
+ * The four session replay storage budget templates, restated from the spec
177
+ * rather than read back from the module, so a change to either has to be made
178
+ * twice. The alias is shared within a pair on purpose (same series, two
179
+ * thresholds); the pairs read different series.
180
+ */
181
+ interface BudgetCase {
182
+ id: string;
183
+ name: string;
184
+ metricName: SessionReplayBudgetMetricType;
185
+ metricAlias: string;
186
+ threshold: number;
187
+ breachName: string;
188
+ requirement: RumAlertTemplateRequirement;
189
+ incidentTitle: string;
190
+ isProjectScoped: boolean;
191
+ }
192
+
193
+ const BUDGET_CASES: Array<BudgetCase> = [
194
+ {
195
+ id: "rum-session-replay-daily-budget-nearly-spent",
196
+ name: "Session Replay Daily Budget Nearly Spent",
197
+ metricName: SessionReplayBudgetMetricType.ProjectDailyUsedPercent,
198
+ metricAlias: "rum_replay_daily_budget_used",
199
+ threshold: 80,
200
+ breachName: "Session Replay Daily Budget Nearly Spent - 80% or more used",
201
+ requirement: "SessionReplayRecording",
202
+ incidentTitle:
203
+ "[RUM] Project's daily session replay budget nearly spent - Storefront",
204
+ isProjectScoped: true,
205
+ },
206
+ {
207
+ id: "rum-session-replay-daily-budget-spent",
208
+ name: "Session Replay Daily Budget Spent",
209
+ metricName: SessionReplayBudgetMetricType.ProjectDailyUsedPercent,
210
+ metricAlias: "rum_replay_daily_budget_used",
211
+ threshold: 100,
212
+ breachName: "Session Replay Daily Budget Spent - 100% used",
213
+ requirement: "SessionReplayRecording",
214
+ incidentTitle:
215
+ "[RUM] Session replay paused: project's daily budget spent - Storefront",
216
+ isProjectScoped: true,
217
+ },
218
+ {
219
+ id: "rum-session-replay-monthly-budget-nearly-spent",
220
+ name: "Session Replay Monthly Budget Nearly Spent",
221
+ metricName: SessionReplayBudgetMetricType.ApplicationMonthlyUsedPercent,
222
+ metricAlias: "rum_replay_monthly_budget_used",
223
+ threshold: 80,
224
+ breachName: "Session Replay Monthly Budget Nearly Spent - 80% or more used",
225
+ requirement: "SessionReplayMonthlyBudget",
226
+ incidentTitle:
227
+ "[RUM] Monthly session replay budget nearly spent - Storefront",
228
+ isProjectScoped: false,
229
+ },
230
+ {
231
+ id: "rum-session-replay-monthly-budget-spent",
232
+ name: "Session Replay Monthly Budget Spent",
233
+ metricName: SessionReplayBudgetMetricType.ApplicationMonthlyUsedPercent,
234
+ metricAlias: "rum_replay_monthly_budget_used",
235
+ threshold: 100,
236
+ breachName: "Session Replay Monthly Budget Spent - 100% used",
237
+ requirement: "SessionReplayMonthlyBudget",
238
+ incidentTitle:
239
+ "[RUM] Session replay paused: monthly budget spent - Storefront",
240
+ isProjectScoped: false,
241
+ },
242
+ ];
243
+
244
+ const BUDGET_TEMPLATE_IDS: Array<string> = BUDGET_CASES.map(
245
+ (item: BudgetCase) => {
246
+ return item.id;
247
+ },
248
+ );
249
+
250
+ // The seven templates every RUM application was offered before the budgets.
251
+ const CORE_TEMPLATE_IDS: Array<string> = [
252
+ "rum-poor-lcp",
253
+ "rum-poor-inp",
254
+ "rum-poor-cls",
255
+ "rum-slow-fcp",
256
+ "rum-slow-ttfb",
257
+ "rum-failed-user-operations",
258
+ "rum-unhandled-exceptions",
259
+ ];
260
+
261
+ const DAILY_TEMPLATE_IDS: Array<string> = [
262
+ "rum-session-replay-daily-budget-nearly-spent",
263
+ "rum-session-replay-daily-budget-spent",
139
264
  ];
140
265
 
266
+ const MONTHLY_TEMPLATE_IDS: Array<string> = [
267
+ "rum-session-replay-monthly-budget-nearly-spent",
268
+ "rum-session-replay-monthly-budget-spent",
269
+ ];
270
+
271
+ // An application that records replays and has a monthly budget of 10 GiB.
272
+ const FULL_CONTEXT: RumAlertTemplateContext = {
273
+ sessionReplayEnabled: true,
274
+ sessionReplayHasRecorded: true,
275
+ sessionReplayMonthlyBudgetInGB: 10,
276
+ };
277
+
278
+ function offeredIds(
279
+ context?: RumAlertTemplateContext | undefined,
280
+ ): Array<string> {
281
+ return getRumAlertTemplates(context).map((template: RumAlertTemplate) => {
282
+ return template.id;
283
+ });
284
+ }
285
+
286
+ // The first filter of the breach criteria and of the Healthy one.
287
+ interface BudgetFilters {
288
+ breach: CriteriaFilter | undefined;
289
+ recovery: CriteriaFilter | undefined;
290
+ }
291
+
292
+ function getFilters(step: MonitorStep): BudgetFilters {
293
+ const [breach, recovery]: Array<MonitorCriteriaInstance> = getCriteria(step);
294
+
295
+ return {
296
+ breach: breach?.data?.filters[0],
297
+ recovery: recovery?.data?.filters[0],
298
+ };
299
+ }
300
+
301
+ function getWindowInMinutes(rollingTime: RollingTime): number {
302
+ const window: InBetween<Date> =
303
+ RollingTimeUtil.convertToStartAndEndDate(rollingTime);
304
+
305
+ return (window.endValue.getTime() - window.startValue.getTime()) / 60000;
306
+ }
307
+
141
308
  /*
142
309
  * The community spellings `WEB_VITAL_DEFS` in telemetryMetrics.ts probes for,
143
310
  * copied verbatim. The overview card tries all four and keeps whichever has
@@ -180,7 +347,7 @@ describe("RumAlertTemplates", () => {
180
347
  return template.id;
181
348
  });
182
349
 
183
- expect(templates).toHaveLength(7);
350
+ expect(templates).toHaveLength(11);
184
351
  expect(new Set(ids).size).toBe(ids.length);
185
352
  expect(ids).toEqual([
186
353
  ...WEB_VITAL_CASES.map((item: WebVitalCase) => {
@@ -188,6 +355,10 @@ describe("RumAlertTemplates", () => {
188
355
  }),
189
356
  "rum-failed-user-operations",
190
357
  "rum-unhandled-exceptions",
358
+ "rum-session-replay-daily-budget-nearly-spent",
359
+ "rum-session-replay-daily-budget-spent",
360
+ "rum-session-replay-monthly-budget-nearly-spent",
361
+ "rum-session-replay-monthly-budget-spent",
191
362
  ]);
192
363
  });
193
364
 
@@ -195,7 +366,7 @@ describe("RumAlertTemplates", () => {
195
366
  const first: Array<RumAlertTemplate> = getAllRumAlertTemplates();
196
367
  first.pop();
197
368
 
198
- expect(getAllRumAlertTemplates()).toHaveLength(7);
369
+ expect(getAllRumAlertTemplates()).toHaveLength(11);
199
370
  });
200
371
 
201
372
  test("returns undefined for an unknown template id", () => {
@@ -257,6 +428,25 @@ describe("RumAlertTemplates", () => {
257
428
  );
258
429
  expect(healthyFilter?.filterType).toBe(FilterType.LessThan);
259
430
  expect(healthyFilter?.value).toBe(item.threshold);
431
+
432
+ /*
433
+ * The recovery reduces the window exactly as the breach does. The
434
+ * budget templates made the reduction a parameter; the web vitals
435
+ * must keep evaluating the five-minute Average on both sides.
436
+ */
437
+ expect(healthyFilter?.metricMonitorOptions?.metricAlias).toBe(
438
+ item.metricAlias,
439
+ );
440
+ expect(healthyFilter?.metricMonitorOptions?.metricAggregationType).toBe(
441
+ EvaluateOverTimeType.Average,
442
+ );
443
+ expect(healthyFilter?.metricMonitorOptions?.thresholdUnit).toBe(
444
+ item.unit,
445
+ );
446
+ expect(healthy?.data?.description).toBe(
447
+ "The RUM signal for Storefront is within its recommended threshold.",
448
+ );
449
+ expect(template.requirement).toBeUndefined();
260
450
  },
261
451
  );
262
452
 
@@ -419,3 +609,584 @@ describe("RumAlertTemplates", () => {
419
609
  ).toBe(RUM_APPLICATION_ID.toString());
420
610
  });
421
611
  });
612
+
613
+ /*
614
+ * The session replay storage budget templates. Each one is a Metrics monitor
615
+ * over a series the budget sweep posts every five minutes under the RUM
616
+ * application's id, and every shape assertion below is one a mistake in would
617
+ * produce a monitor that looks right on the page and never fires, fires late,
618
+ * or never resolves:
619
+ *
620
+ * - scope: without `telemetryServiceIds` the query reads every application
621
+ * in the project, and the recommendation fingerprint loses its resource;
622
+ * - the reduction: without `metricAlias` no metricMonitorOptions are
623
+ * written and the window silently falls back to All Values, which only
624
+ * fires once EVERY point crossed;
625
+ * - the unit: "%" on the alias, the threshold and the catalog alike, or the
626
+ * evaluator converts the value it compares.
627
+ */
628
+ describe("RumAlertTemplates - session replay storage budget", () => {
629
+ test.each(BUDGET_CASES)(
630
+ "$id watches $metricName for this application alone, at its threshold",
631
+ (item: BudgetCase) => {
632
+ const template: RumAlertTemplate = getTemplate(item.id);
633
+ const step: MonitorStep = template.getMonitorStep(buildArgs());
634
+ const metricMonitor: MonitorStepMetricMonitor | undefined =
635
+ step.data?.metricMonitor;
636
+
637
+ expect(template.name).toBe(item.name);
638
+ expect(template.monitorType).toBe(MonitorType.Metrics);
639
+ expect(template.category).toBe("Session Replay");
640
+ expect(template.requirement).toBe(item.requirement);
641
+
642
+ expect(metricMonitor).toBeDefined();
643
+ expect(metricMonitor?.rollingTime).toBe(RollingTime.Past15Minutes);
644
+ expect(
645
+ metricMonitor?.telemetryServiceIds?.map((id: ObjectID) => {
646
+ return id.toString();
647
+ }),
648
+ ).toEqual([RUM_APPLICATION_ID.toString()]);
649
+
650
+ expect(metricMonitor?.metricViewConfig.queryConfigs).toHaveLength(1);
651
+ expect(metricMonitor?.metricViewConfig.formulaConfigs).toEqual([]);
652
+
653
+ const query: MetricQueryConfigData | undefined =
654
+ metricMonitor?.metricViewConfig.queryConfigs[0];
655
+
656
+ expect(query?.metricQueryData.filterData.metricName).toBe(
657
+ item.metricName,
658
+ );
659
+ expect(query?.metricQueryData.filterData.aggegationType).toBe(
660
+ MetricsAggregationType.Max,
661
+ );
662
+ // Scoped by primaryEntityId alone: no attribute filter, no grouping.
663
+ expect(query?.metricQueryData.filterData.attributes).toEqual({});
664
+ expect(query?.metricQueryData.groupBy).toBeUndefined();
665
+ expect(MonitorStep.getGroupByAttributeKeys(step)).toEqual([]);
666
+ expect(query?.metricAliasData?.metricVariable).toBe(item.metricAlias);
667
+ expect(query?.metricAliasData?.legendUnit).toBe("%");
668
+ expect(query?.metricAliasData?.title).toBe(item.name);
669
+ expect(query?.metricAliasData?.legend).toBe(item.name);
670
+
671
+ const { breach, recovery }: BudgetFilters = getFilters(step);
672
+
673
+ expect(getCriteria(step)).toHaveLength(2);
674
+
675
+ expect(breach?.checkOn).toBe(CheckOn.MetricValue);
676
+ expect(breach?.filterType).toBe(FilterType.GreaterThanOrEqualTo);
677
+ expect(breach?.value).toBe(item.threshold);
678
+
679
+ expect(recovery?.checkOn).toBe(CheckOn.MetricValue);
680
+ expect(recovery?.filterType).toBe(FilterType.LessThan);
681
+ expect(recovery?.value).toBe(item.threshold);
682
+
683
+ for (const filter of [breach, recovery]) {
684
+ expect(filter?.metricMonitorOptions).toEqual({
685
+ metricAggregationType: EvaluateOverTimeType.MaximumValue,
686
+ metricAlias: item.metricAlias,
687
+ thresholdUnit: "%",
688
+ });
689
+ }
690
+ },
691
+ );
692
+
693
+ test.each(BUDGET_CASES)(
694
+ "$id names its criteria and titles its incidents and alerts",
695
+ (item: BudgetCase) => {
696
+ const step: MonitorStep = getTemplate(item.id).getMonitorStep(
697
+ buildArgs(),
698
+ );
699
+ const [breach, recovery]: Array<MonitorCriteriaInstance> =
700
+ getCriteria(step);
701
+
702
+ expect(breach?.data?.name).toBe(item.breachName);
703
+ expect(breach?.data?.description).toMatch(/^Triggers when /);
704
+ expect(breach?.data?.description).toContain(`${item.threshold}%`);
705
+ expect(breach?.data?.description).toMatch(
706
+ / at any point in the last 15 minutes\.$/,
707
+ );
708
+
709
+ expect(recovery?.data?.name).toBe("Healthy");
710
+ expect(recovery?.data?.description).toBe(
711
+ "Session replay budget use for Storefront is below the alert threshold.",
712
+ );
713
+
714
+ expect(breach?.data?.incidents[0]?.title).toBe(item.incidentTitle);
715
+ expect(breach?.data?.alerts[0]?.title).toBe(item.incidentTitle);
716
+ expect(breach?.data?.incidents[0]?.description).toBe(
717
+ breach?.data?.alerts[0]?.description,
718
+ );
719
+ expect(breach?.data?.incidents[0]?.autoResolveIncident).toBe(true);
720
+ expect(breach?.data?.alerts[0]?.autoResolveAlert).toBe(true);
721
+
722
+ // Both criteria move the monitor's status, so a recovery resolves.
723
+ expect(breach?.data?.changeMonitorStatus).toBe(true);
724
+ expect(recovery?.data?.changeMonitorStatus).toBe(true);
725
+ },
726
+ );
727
+
728
+ /*
729
+ * The enum and its util are the contract the sweep writes by. A template
730
+ * that reads the series in another unit gets its values converted before
731
+ * they are compared; one aggregated differently from how the catalog
732
+ * charts it reads a different number from the one on screen.
733
+ */
734
+ test.each(BUDGET_CASES)(
735
+ "$id reads the series in the unit the metric catalog registers and the aggregation it names",
736
+ (item: BudgetCase) => {
737
+ const step: MonitorStep = getTemplate(item.id).getMonitorStep(
738
+ buildArgs(),
739
+ );
740
+ const query: MetricQueryConfigData | undefined =
741
+ step.data?.metricMonitor?.metricViewConfig.queryConfigs[0];
742
+ const { breach }: BudgetFilters = getFilters(step);
743
+
744
+ expect(SessionReplayBudgetMetricTypeUtil.getAll()).toContain(
745
+ item.metricName,
746
+ );
747
+ expect(
748
+ SessionReplayBudgetMetricTypeUtil.isReservedMetricName(item.metricName),
749
+ ).toBe(true);
750
+ expect(query?.metricAliasData?.legendUnit).toBe(
751
+ SessionReplayBudgetMetricTypeUtil.getUnit(item.metricName),
752
+ );
753
+ expect(breach?.metricMonitorOptions?.thresholdUnit).toBe(
754
+ SessionReplayBudgetMetricTypeUtil.getUnit(item.metricName),
755
+ );
756
+ expect(query?.metricQueryData.filterData.aggegationType).toBe(
757
+ SessionReplayBudgetMetricTypeUtil.getAggregationType(item.metricName),
758
+ );
759
+ expect(
760
+ SessionReplayBudgetMetricTypeUtil.isProjectScoped(item.metricName),
761
+ ).toBe(item.isProjectScoped);
762
+ },
763
+ );
764
+
765
+ /*
766
+ * The budgets are GiB while every byte threshold and chart is decimal, so a
767
+ * bytes template would fire at 93% of a 1 GiB limit and call it spent.
768
+ */
769
+ test("alerts on the percent series, never on bytes", () => {
770
+ for (const id of BUDGET_TEMPLATE_IDS) {
771
+ const metricName: unknown =
772
+ getTemplate(id).getMonitorStep(buildArgs()).data?.metricMonitor
773
+ ?.metricViewConfig.queryConfigs[0]?.metricQueryData.filterData
774
+ .metricName;
775
+
776
+ expect(typeof metricName).toBe("string");
777
+ expect(metricName).not.toBe(
778
+ SessionReplayBudgetMetricType.ProjectDailyUsedBytes,
779
+ );
780
+ expect(metricName).not.toBe(
781
+ SessionReplayBudgetMetricType.ApplicationMonthlyUsedBytes,
782
+ );
783
+ expect(String(metricName).endsWith(".percent")).toBe(true);
784
+ }
785
+ });
786
+
787
+ /*
788
+ * An empty window meets no criteria and resolves an open alert, so the
789
+ * window has to hold more than one of the sweep's points - one late sweep
790
+ * must not resolve a budget that is still spent.
791
+ */
792
+ test("looks back far enough to hold three of the sweep's points", () => {
793
+ expect(getWindowInMinutes(RollingTime.Past15Minutes)).toBe(15);
794
+
795
+ for (const id of BUDGET_TEMPLATE_IDS) {
796
+ const rollingTime: RollingTime | undefined =
797
+ getTemplate(id).getMonitorStep(buildArgs()).data?.metricMonitor
798
+ ?.rollingTime;
799
+
800
+ expect(rollingTime).toBeDefined();
801
+ expect(getWindowInMinutes(rollingTime!)).toBeGreaterThanOrEqual(
802
+ 3 * SESSION_REPLAY_BUDGET_METRIC_INTERVAL_MINUTES,
803
+ );
804
+ }
805
+ });
806
+
807
+ test("each pair reads one series under one alias, and the two pairs read different ones", () => {
808
+ const seriesOf: (id: string) => string = (id: string): string => {
809
+ const query: MetricQueryConfigData | undefined =
810
+ getTemplate(id).getMonitorStep(buildArgs()).data?.metricMonitor
811
+ ?.metricViewConfig.queryConfigs[0];
812
+
813
+ return `${query?.metricQueryData.filterData.metricName} as ${query?.metricAliasData?.metricVariable}`;
814
+ };
815
+
816
+ expect(seriesOf(DAILY_TEMPLATE_IDS[0]!)).toBe(
817
+ seriesOf(DAILY_TEMPLATE_IDS[1]!),
818
+ );
819
+ expect(seriesOf(MONTHLY_TEMPLATE_IDS[0]!)).toBe(
820
+ seriesOf(MONTHLY_TEMPLATE_IDS[1]!),
821
+ );
822
+ expect(seriesOf(DAILY_TEMPLATE_IDS[0]!)).not.toBe(
823
+ seriesOf(MONTHLY_TEMPLATE_IDS[0]!),
824
+ );
825
+ });
826
+
827
+ /*
828
+ * The card clamps at three lines, so the sentence that decides whether to
829
+ * click create comes first - and the daily cards have to say the limit is
830
+ * the project's, or every application gets its own copy of the monitor.
831
+ */
832
+ test("cards lead with what they alert on, and the daily ones say one monitor covers the project", () => {
833
+ for (const id of BUDGET_TEMPLATE_IDS) {
834
+ expect(getTemplate(id).description).toMatch(/^Alert when /);
835
+ }
836
+
837
+ for (const id of DAILY_TEMPLATE_IDS) {
838
+ const description: string = getTemplate(id).description;
839
+
840
+ expect(description).toContain("the project's");
841
+ expect(description).toContain(
842
+ "shared by every application in the project",
843
+ );
844
+ expect(description).toContain("one of these monitors covers them all");
845
+ expect(description).toContain("00:00 UTC");
846
+ }
847
+
848
+ for (const id of MONTHLY_TEMPLATE_IDS) {
849
+ const description: string = getTemplate(id).description;
850
+
851
+ expect(description).toContain("this application's");
852
+ expect(description).toContain("(UTC)");
853
+ expect(description).not.toContain("shared by every application");
854
+ }
855
+ });
856
+
857
+ /*
858
+ * What the incident and the alert say, verbatim. Every lever named is one
859
+ * the ingest path refuses BEFORE it charges a byte (allowed origins,
860
+ * sampling, the upload trigger), and the daily limit is only ever raised by
861
+ * the deployment - so the text never sends anyone to a setting that cannot
862
+ * help.
863
+ */
864
+ test("incident and alert descriptions name the levers that act before bytes are charged", () => {
865
+ const descriptionOf: (id: string, monitorName: string) => string = (
866
+ id: string,
867
+ monitorName: string,
868
+ ): string => {
869
+ const [breach]: Array<MonitorCriteriaInstance> = getCriteria(
870
+ getTemplate(id).getMonitorStep({
871
+ ...buildArgs(),
872
+ monitorName: monitorName,
873
+ }),
874
+ );
875
+
876
+ expect(breach?.data?.alerts[0]?.description).toBe(
877
+ breach?.data?.incidents[0]?.description,
878
+ );
879
+
880
+ return breach?.data?.incidents[0]?.description || "";
881
+ };
882
+
883
+ expect(
884
+ descriptionOf("rum-session-replay-daily-budget-nearly-spent", "Shop"),
885
+ ).toBe(
886
+ "This project has used 80% of today's session replay upload limit, which all of its RUM applications share. At 100% every recorder in the project is told to stop until 00:00 UTC. To make the rest of the day last, open the Replay Policy page of the busiest applications and lower the Sample percentage, upload only On error or frustration, or narrow the Allowed origins so staging traffic stops spending it. Self-hosted: the limit is SESSION_REPLAY_MAX_BYTES_PER_PROJECT_PER_DAY.",
887
+ );
888
+ expect(descriptionOf("rum-session-replay-daily-budget-spent", "Shop")).toBe(
889
+ "This project has spent today's session replay upload limit, which all of its RUM applications share: every recorder in the project has been told to stop, and nothing more is recorded until 00:00 UTC. This resolves on its own at about 00:10-00:15 UTC, once the last reading over the limit has left the monitor's 15-minute window. To make tomorrow's limit last, lower the Sample percentage, upload only On error or frustration, or narrow the Allowed origins on the busiest applications' Replay Policy page. Self-hosted: the limit is SESSION_REPLAY_MAX_BYTES_PER_PROJECT_PER_DAY.",
890
+ );
891
+ expect(
892
+ descriptionOf("rum-session-replay-monthly-budget-nearly-spent", "Shop"),
893
+ ).toBe(
894
+ "Shop has used 80% of its monthly session replay budget. At 100% its recorders are told to stop until the 1st of next month (UTC). Raise the Monthly budget on its Replay Policy page, lower the Sample percentage, or upload only On error or frustration.",
895
+ );
896
+ expect(
897
+ descriptionOf("rum-session-replay-monthly-budget-spent", "Shop"),
898
+ ).toBe(
899
+ "Shop has spent its monthly session replay budget: its recorders have been told to stop, and nothing more is recorded until the 1st of next month (UTC) or until the budget is raised on its Replay Policy page. This resolves on its own.",
900
+ );
901
+
902
+ // The daily text is about the project, so no application is named in it.
903
+ for (const id of DAILY_TEMPLATE_IDS) {
904
+ expect(descriptionOf(id, "Checkout Web")).not.toContain("Checkout Web");
905
+ }
906
+
907
+ for (const id of MONTHLY_TEMPLATE_IDS) {
908
+ expect(descriptionOf(id, "Checkout Web")).toMatch(/^Checkout Web has /);
909
+ }
910
+ });
911
+
912
+ test("every title carries the application it was created on", () => {
913
+ for (const id of BUDGET_TEMPLATE_IDS) {
914
+ const [breach]: Array<MonitorCriteriaInstance> = getCriteria(
915
+ getTemplate(id).getMonitorStep({
916
+ ...buildArgs(),
917
+ monitorName: "Checkout Web",
918
+ }),
919
+ );
920
+
921
+ expect(breach?.data?.incidents[0]?.title).toMatch(
922
+ /^\[RUM\] .+ - Checkout Web$/,
923
+ );
924
+ expect(breach?.data?.alerts[0]?.title).toBe(
925
+ breach?.data?.incidents[0]?.title,
926
+ );
927
+ }
928
+ });
929
+
930
+ /*
931
+ * The Replay Health page's "Set up alerts" link opens the Recommendations
932
+ * tab searching for "budget", and that search matches a card's name,
933
+ * description, category and severity (RecommendationFilterUtil). So every
934
+ * budget template has to carry the word, and no other RUM template may, or
935
+ * the link lands on the wrong cards.
936
+ */
937
+ test("the word the Health page searches for finds exactly the budget templates", () => {
938
+ const matching: Array<string> = getAllRumAlertTemplates()
939
+ .filter((template: RumAlertTemplate) => {
940
+ return [
941
+ template.name,
942
+ template.description,
943
+ template.category,
944
+ template.severity,
945
+ ].some((field: string) => {
946
+ return field.toLowerCase().includes("budget");
947
+ });
948
+ })
949
+ .map((template: RumAlertTemplate) => {
950
+ return template.id;
951
+ });
952
+
953
+ expect(matching).toEqual(BUDGET_TEMPLATE_IDS);
954
+ });
955
+
956
+ /*
957
+ * A requirement is what keeps a template off applications its series is
958
+ * never posted for. The budget templates must all carry one, and nothing
959
+ * else may: a web vital gated by accident would vanish from every page.
960
+ */
961
+ test("only the session replay templates carry a requirement, and all of them do", () => {
962
+ for (const template of getAllRumAlertTemplates()) {
963
+ expect(`${template.id}: ${Boolean(template.requirement)}`).toBe(
964
+ `${template.id}: ${template.category === "Session Replay"}`,
965
+ );
966
+ }
967
+ });
968
+
969
+ test.each(BUDGET_CASES)(
970
+ "$id survives the JSON round trip a saved monitor takes, scope and alias included",
971
+ (item: BudgetCase) => {
972
+ const original: MonitorStep = getTemplate(item.id).getMonitorStep(
973
+ buildArgs(),
974
+ );
975
+ const restored: MonitorStep = MonitorStep.fromJSON(
976
+ JSON.parse(JSON.stringify(original.toJSON())) as JSONObject,
977
+ );
978
+ const metricMonitor: MonitorStepMetricMonitor | undefined =
979
+ restored.data?.metricMonitor;
980
+
981
+ expect(
982
+ metricMonitor?.telemetryServiceIds?.map((id: ObjectID) => {
983
+ return id.toString();
984
+ }),
985
+ ).toEqual([RUM_APPLICATION_ID.toString()]);
986
+ expect(metricMonitor?.rollingTime).toBe(RollingTime.Past15Minutes);
987
+ expect(
988
+ metricMonitor?.metricViewConfig.queryConfigs[0]?.metricAliasData
989
+ ?.metricVariable,
990
+ ).toBe(item.metricAlias);
991
+ expect(
992
+ metricMonitor?.metricViewConfig.queryConfigs[0]?.metricQueryData
993
+ .filterData.metricName,
994
+ ).toBe(item.metricName);
995
+
996
+ const { breach, recovery }: BudgetFilters = getFilters(restored);
997
+
998
+ for (const filter of [breach, recovery]) {
999
+ expect(filter?.metricMonitorOptions?.metricAlias).toBe(
1000
+ item.metricAlias,
1001
+ );
1002
+ expect(filter?.metricMonitorOptions?.metricAggregationType).toBe(
1003
+ EvaluateOverTimeType.MaximumValue,
1004
+ );
1005
+ expect(filter?.metricMonitorOptions?.thresholdUnit).toBe("%");
1006
+ }
1007
+ },
1008
+ );
1009
+ });
1010
+
1011
+ /*
1012
+ * The gate. The sweep only posts the daily series for an application that has
1013
+ * session replay on and has recorded a replay, and the monthly series only
1014
+ * while it also has a monthly budget; a monitor over a series nobody posts
1015
+ * never fires and never says why. So each pair is offered only once its
1016
+ * series can exist - and "not known" never counts as yes.
1017
+ */
1018
+ describe("getRumAlertTemplates", () => {
1019
+ test("offers the original seven when nothing is known about the application", () => {
1020
+ expect(offeredIds()).toEqual(CORE_TEMPLATE_IDS);
1021
+ expect(offeredIds(undefined)).toEqual(CORE_TEMPLATE_IDS);
1022
+ expect(offeredIds({})).toEqual(CORE_TEMPLATE_IDS);
1023
+ expect(
1024
+ offeredIds({
1025
+ sessionReplayEnabled: null,
1026
+ sessionReplayHasRecorded: null,
1027
+ sessionReplayMonthlyBudgetInGB: null,
1028
+ }),
1029
+ ).toEqual(CORE_TEMPLATE_IDS);
1030
+ expect(
1031
+ offeredIds({
1032
+ sessionReplayEnabled: undefined,
1033
+ sessionReplayHasRecorded: undefined,
1034
+ sessionReplayMonthlyBudgetInGB: undefined,
1035
+ }),
1036
+ ).toEqual(CORE_TEMPLATE_IDS);
1037
+ });
1038
+
1039
+ test("adds the daily pair once the application has replay on and has recorded", () => {
1040
+ expect(
1041
+ offeredIds({
1042
+ sessionReplayEnabled: true,
1043
+ sessionReplayHasRecorded: true,
1044
+ }),
1045
+ ).toEqual([...CORE_TEMPLATE_IDS, ...DAILY_TEMPLATE_IDS]);
1046
+ });
1047
+
1048
+ test("adds the monthly pair once it also has a monthly budget", () => {
1049
+ const offered: Array<string> = offeredIds(FULL_CONTEXT);
1050
+
1051
+ expect(offered).toHaveLength(11);
1052
+ expect(offered).toEqual([
1053
+ ...CORE_TEMPLATE_IDS,
1054
+ ...DAILY_TEMPLATE_IDS,
1055
+ ...MONTHLY_TEMPLATE_IDS,
1056
+ ]);
1057
+ expect(offered).toEqual(
1058
+ getAllRumAlertTemplates().map((template: RumAlertTemplate) => {
1059
+ return template.id;
1060
+ }),
1061
+ );
1062
+ });
1063
+
1064
+ test("counts a fractional budget - 0.5 is half a GiB, not none", () => {
1065
+ expect(
1066
+ offeredIds({ ...FULL_CONTEXT, sessionReplayMonthlyBudgetInGB: 0.5 }),
1067
+ ).toHaveLength(11);
1068
+ });
1069
+
1070
+ test.each([0, null, undefined, NaN, -1, Infinity, -Infinity])(
1071
+ "withholds only the monthly pair for a budget of %p",
1072
+ (budget: number | null | undefined) => {
1073
+ expect(
1074
+ offeredIds({
1075
+ sessionReplayEnabled: true,
1076
+ sessionReplayHasRecorded: true,
1077
+ sessionReplayMonthlyBudgetInGB: budget,
1078
+ }),
1079
+ ).toEqual([...CORE_TEMPLATE_IDS, ...DAILY_TEMPLATE_IDS]);
1080
+ },
1081
+ );
1082
+
1083
+ test.each([false, null, undefined])(
1084
+ "withholds both pairs while session replay is %p for the application",
1085
+ (enabled: boolean | null | undefined) => {
1086
+ expect(
1087
+ offeredIds({ ...FULL_CONTEXT, sessionReplayEnabled: enabled }),
1088
+ ).toEqual(CORE_TEMPLATE_IDS);
1089
+ },
1090
+ );
1091
+
1092
+ test.each([false, null, undefined])(
1093
+ "withholds both pairs, budget or not, while has-recorded is %p",
1094
+ (hasRecorded: boolean | null | undefined) => {
1095
+ expect(
1096
+ offeredIds({ ...FULL_CONTEXT, sessionReplayHasRecorded: hasRecorded }),
1097
+ ).toEqual(CORE_TEMPLATE_IDS);
1098
+ expect(
1099
+ offeredIds({
1100
+ sessionReplayEnabled: true,
1101
+ sessionReplayHasRecorded: hasRecorded,
1102
+ }),
1103
+ ).toEqual(CORE_TEMPLATE_IDS);
1104
+ },
1105
+ );
1106
+
1107
+ test("a budget alone offers nothing: the application also has to be recording", () => {
1108
+ expect(offeredIds({ sessionReplayMonthlyBudgetInGB: 10 })).toEqual(
1109
+ CORE_TEMPLATE_IDS,
1110
+ );
1111
+ expect(
1112
+ offeredIds({
1113
+ sessionReplayEnabled: false,
1114
+ sessionReplayHasRecorded: true,
1115
+ sessionReplayMonthlyBudgetInGB: 10,
1116
+ }),
1117
+ ).toEqual(CORE_TEMPLATE_IDS);
1118
+ });
1119
+
1120
+ /*
1121
+ * Only an explicit `true` and a real number count. Parsing what the API
1122
+ * hands back (a date string for has-recorded, a numeric string for the
1123
+ * budget) is the Dashboard registry's job, before it ever gets here.
1124
+ */
1125
+ test("reads only an explicit true and a real number, never something truthy", () => {
1126
+ expect(
1127
+ offeredIds({
1128
+ sessionReplayEnabled: 1 as unknown as boolean,
1129
+ sessionReplayHasRecorded: true,
1130
+ }),
1131
+ ).toEqual(CORE_TEMPLATE_IDS);
1132
+ expect(
1133
+ offeredIds({
1134
+ sessionReplayEnabled: true,
1135
+ sessionReplayHasRecorded:
1136
+ "2026-09-01T00:00:00.000Z" as unknown as boolean,
1137
+ }),
1138
+ ).toEqual(CORE_TEMPLATE_IDS);
1139
+ expect(
1140
+ offeredIds({
1141
+ ...FULL_CONTEXT,
1142
+ sessionReplayMonthlyBudgetInGB: "10" as unknown as number,
1143
+ }),
1144
+ ).toEqual([...CORE_TEMPLATE_IDS, ...DAILY_TEMPLATE_IDS]);
1145
+ });
1146
+
1147
+ test("offers every template without a requirement, whatever the context says", () => {
1148
+ const contexts: Array<RumAlertTemplateContext | undefined> = [
1149
+ undefined,
1150
+ {},
1151
+ FULL_CONTEXT,
1152
+ { sessionReplayEnabled: false, sessionReplayHasRecorded: false },
1153
+ ];
1154
+
1155
+ for (const context of contexts) {
1156
+ const offered: Array<string> = offeredIds(context);
1157
+
1158
+ for (const id of CORE_TEMPLATE_IDS) {
1159
+ expect(offered).toContain(id);
1160
+ }
1161
+ }
1162
+ });
1163
+
1164
+ test("does not expose its internal template array", () => {
1165
+ const first: Array<RumAlertTemplate> = getRumAlertTemplates(FULL_CONTEXT);
1166
+ first.length = 0;
1167
+
1168
+ expect(getRumAlertTemplates(FULL_CONTEXT)).toHaveLength(11);
1169
+ expect(getAllRumAlertTemplates()).toHaveLength(11);
1170
+ });
1171
+ });
1172
+
1173
+ /*
1174
+ * A dismissal, and the created-monitor diff, resolve a template from its id
1175
+ * alone - with no idea what the application is offered today. The by-id
1176
+ * lookup therefore searches every template, gated or not.
1177
+ */
1178
+ describe("getRumAlertTemplateById", () => {
1179
+ test.each(BUDGET_TEMPLATE_IDS)(
1180
+ "resolves %s even where it is not offered",
1181
+ (id: string) => {
1182
+ expect(offeredIds()).not.toContain(id);
1183
+
1184
+ const template: RumAlertTemplate | undefined =
1185
+ getRumAlertTemplateById(id);
1186
+
1187
+ expect(template).toBeDefined();
1188
+ expect(template?.id).toBe(id);
1189
+ expect(template?.category).toBe("Session Replay");
1190
+ },
1191
+ );
1192
+ });