@oneuptime/common 13.0.6 → 13.0.7

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 (589) hide show
  1. package/Models/DatabaseModels/AIAgentOwnerTeam.ts +6 -0
  2. package/Models/DatabaseModels/AIAgentOwnerUser.ts +6 -0
  3. package/Models/DatabaseModels/Alert.ts +12 -0
  4. package/Models/DatabaseModels/AlertEpisodeOwnerTeam.ts +6 -1
  5. package/Models/DatabaseModels/AlertEpisodeOwnerUser.ts +6 -1
  6. package/Models/DatabaseModels/AlertOwnerTeam.ts +6 -1
  7. package/Models/DatabaseModels/AlertOwnerUser.ts +6 -1
  8. package/Models/DatabaseModels/CephClusterOwnerTeam.ts +6 -0
  9. package/Models/DatabaseModels/CephClusterOwnerUser.ts +6 -0
  10. package/Models/DatabaseModels/CloudResourceOwnerTeam.ts +6 -0
  11. package/Models/DatabaseModels/CloudResourceOwnerUser.ts +6 -0
  12. package/Models/DatabaseModels/DashboardOwnerTeam.ts +6 -1
  13. package/Models/DatabaseModels/DashboardOwnerUser.ts +6 -1
  14. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +8 -0
  15. package/Models/DatabaseModels/DockerHostOwnerTeam.ts +6 -0
  16. package/Models/DatabaseModels/DockerHostOwnerUser.ts +6 -0
  17. package/Models/DatabaseModels/DockerSwarmClusterOwnerTeam.ts +6 -0
  18. package/Models/DatabaseModels/DockerSwarmClusterOwnerUser.ts +6 -0
  19. package/Models/DatabaseModels/HostOwnerTeam.ts +6 -0
  20. package/Models/DatabaseModels/HostOwnerUser.ts +6 -0
  21. package/Models/DatabaseModels/Incident.ts +12 -0
  22. package/Models/DatabaseModels/IncidentEpisodeOwnerTeam.ts +6 -1
  23. package/Models/DatabaseModels/IncidentEpisodeOwnerUser.ts +6 -1
  24. package/Models/DatabaseModels/IncidentOwnerTeam.ts +6 -1
  25. package/Models/DatabaseModels/IncidentOwnerUser.ts +6 -1
  26. package/Models/DatabaseModels/IncidentTemplateOwnerTeam.ts +6 -0
  27. package/Models/DatabaseModels/IncidentTemplateOwnerUser.ts +6 -0
  28. package/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.ts +6 -1
  29. package/Models/DatabaseModels/IncomingCallPolicyOwnerUser.ts +6 -1
  30. package/Models/DatabaseModels/IoTFleetOwnerTeam.ts +6 -0
  31. package/Models/DatabaseModels/IoTFleetOwnerUser.ts +6 -0
  32. package/Models/DatabaseModels/KubernetesClusterOwnerTeam.ts +6 -0
  33. package/Models/DatabaseModels/KubernetesClusterOwnerUser.ts +6 -0
  34. package/Models/DatabaseModels/Monitor.ts +2 -3
  35. package/Models/DatabaseModels/MonitorGroupOwnerTeam.ts +6 -0
  36. package/Models/DatabaseModels/MonitorGroupOwnerUser.ts +6 -0
  37. package/Models/DatabaseModels/MonitorOwnerTeam.ts +6 -1
  38. package/Models/DatabaseModels/MonitorOwnerUser.ts +6 -1
  39. package/Models/DatabaseModels/MonitorTemplate.ts +2 -3
  40. package/Models/DatabaseModels/NetworkDeviceOwnerTeam.ts +6 -0
  41. package/Models/DatabaseModels/NetworkDeviceOwnerUser.ts +6 -0
  42. package/Models/DatabaseModels/OnCallDutyPolicyOwnerTeam.ts +6 -1
  43. package/Models/DatabaseModels/OnCallDutyPolicyOwnerUser.ts +6 -1
  44. package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.ts +6 -1
  45. package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.ts +6 -1
  46. package/Models/DatabaseModels/PodmanHostOwnerTeam.ts +6 -0
  47. package/Models/DatabaseModels/PodmanHostOwnerUser.ts +6 -0
  48. package/Models/DatabaseModels/ProbeOwnerTeam.ts +6 -0
  49. package/Models/DatabaseModels/ProbeOwnerUser.ts +6 -0
  50. package/Models/DatabaseModels/ProxmoxClusterOwnerTeam.ts +6 -0
  51. package/Models/DatabaseModels/ProxmoxClusterOwnerUser.ts +6 -0
  52. package/Models/DatabaseModels/RumApplicationOwnerTeam.ts +6 -0
  53. package/Models/DatabaseModels/RumApplicationOwnerUser.ts +6 -0
  54. package/Models/DatabaseModels/RunbookOwnerTeam.ts +6 -1
  55. package/Models/DatabaseModels/RunbookOwnerUser.ts +6 -1
  56. package/Models/DatabaseModels/RunnerOwnerTeam.ts +6 -1
  57. package/Models/DatabaseModels/RunnerOwnerUser.ts +6 -1
  58. package/Models/DatabaseModels/ScheduledMaintenanceOwnerTeam.ts +6 -1
  59. package/Models/DatabaseModels/ScheduledMaintenanceOwnerUser.ts +6 -1
  60. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam.ts +8 -0
  61. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +8 -0
  62. package/Models/DatabaseModels/ServerlessFunctionOwnerTeam.ts +6 -0
  63. package/Models/DatabaseModels/ServerlessFunctionOwnerUser.ts +6 -0
  64. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.ts +6 -1
  65. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.ts +6 -1
  66. package/Models/DatabaseModels/ServiceOwnerTeam.ts +6 -0
  67. package/Models/DatabaseModels/ServiceOwnerUser.ts +6 -0
  68. package/Models/DatabaseModels/StatusPageOwnerTeam.ts +6 -1
  69. package/Models/DatabaseModels/StatusPageOwnerUser.ts +6 -1
  70. package/Models/DatabaseModels/VMwareVCenterOwnerTeam.ts +6 -0
  71. package/Models/DatabaseModels/VMwareVCenterOwnerUser.ts +6 -0
  72. package/Models/DatabaseModels/WorkflowOwnerTeam.ts +6 -1
  73. package/Models/DatabaseModels/WorkflowOwnerUser.ts +6 -1
  74. package/Scripts/benchmark-fanin-capacity.js +2 -2
  75. package/Server/API/AIAgentDataAPI.ts +1 -1
  76. package/Server/API/AIChatAPI.ts +6 -4
  77. package/Server/API/AIInvestigationAPI.ts +7 -0
  78. package/Server/API/StatusPageAPI.ts +13 -0
  79. package/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts +1 -1
  80. package/Server/Infrastructure/Postgres/SchemaMigrations/1793500000000-MakeOwnerRowsUnique.ts +1057 -0
  81. package/Server/Infrastructure/Postgres/SchemaMigrations/1793700000000-AddAIInvestigationDecision.ts +25 -0
  82. package/Server/Infrastructure/Postgres/SchemaMigrations/1793800000000-RemoveMonitorDescriptionLengthLimit.ts +31 -0
  83. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  84. package/Server/Infrastructure/Postgres/SchemaMigrations/Readme.md +6 -4
  85. package/Server/Middleware/IdentityRateLimit.ts +34 -1
  86. package/Server/Middleware/PublicDashboardRateLimit.ts +169 -39
  87. package/Server/Services/AlertEpisodeService.ts +13 -34
  88. package/Server/Services/AlertGroupingEngineService.ts +7 -4
  89. package/Server/Services/AlertService.ts +13 -32
  90. package/Server/Services/CephClusterOwnerRuleEngineService.ts +18 -10
  91. package/Server/Services/CloudResourceOwnerRuleEngineService.ts +18 -10
  92. package/Server/Services/DashboardOwnerRuleEngineService.ts +18 -10
  93. package/Server/Services/DatabaseService.ts +101 -1
  94. package/Server/Services/DockerHostOwnerRuleEngineService.ts +18 -10
  95. package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +18 -10
  96. package/Server/Services/HostOwnerRuleEngineService.ts +18 -10
  97. package/Server/Services/IncidentEpisodeOwnerRuleEngineService.ts +6 -4
  98. package/Server/Services/IncidentEpisodeService.ts +15 -75
  99. package/Server/Services/IncidentGroupingEngineService.ts +7 -4
  100. package/Server/Services/IncidentService.ts +13 -32
  101. package/Server/Services/IncidentTemplateService.ts +13 -36
  102. package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +18 -10
  103. package/Server/Services/IoTFleetOwnerRuleEngineService.ts +18 -10
  104. package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +18 -10
  105. package/Server/Services/MonitorService.ts +13 -32
  106. package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +18 -10
  107. package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +18 -10
  108. package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +18 -10
  109. package/Server/Services/PodmanHostOwnerRuleEngineService.ts +18 -10
  110. package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +18 -10
  111. package/Server/Services/RumApplicationOwnerRuleEngineService.ts +18 -10
  112. package/Server/Services/RunbookOwnerRuleEngineService.ts +18 -10
  113. package/Server/Services/ScheduledMaintenanceService.ts +13 -34
  114. package/Server/Services/ScheduledMaintenanceTemplateService.ts +12 -34
  115. package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +18 -10
  116. package/Server/Services/ServiceOwnerRuleEngineService.ts +18 -10
  117. package/Server/Services/StatusPagePrivateUserService.ts +38 -29
  118. package/Server/Services/StatusPagePrivateUserSessionService.ts +49 -1
  119. package/Server/Services/StatusPageService.ts +59 -36
  120. package/Server/Services/UserSessionService.ts +6 -0
  121. package/Server/Services/VMwareVCenterOwnerRuleEngineService.ts +18 -10
  122. package/Server/Services/WorkflowOwnerRuleEngineService.ts +18 -10
  123. package/Server/Static/Brand/README.md +7 -7
  124. package/Server/Static/Vendor/README.md +7 -7
  125. package/Server/Types/Database/Permissions/OwnedScopePermission.ts +18 -9
  126. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +82 -0
  127. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +21 -8
  128. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +41 -4
  129. package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +1 -1
  130. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +1 -1
  131. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +13 -5
  132. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +53 -13
  133. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +51 -14
  134. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +292 -0
  135. package/Server/Utils/AI/SRE/InvestigationQueue.ts +25 -0
  136. package/Server/Utils/AI/Toolbox/Index.ts +43 -13
  137. package/Server/Utils/AI/Toolbox/ResourceTools.ts +952 -0
  138. package/Server/Utils/AI/Toolbox/RumTools.ts +424 -0
  139. package/Server/Utils/AI/Toolbox/ToolTypes.ts +10 -0
  140. package/Server/Utils/AI/Toolbox/TraceTools.ts +2 -1
  141. package/Server/Utils/Database/PostgresErrorTranslator.ts +14 -0
  142. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +137 -126
  143. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +4 -1
  144. package/Server/Utils/Rules/OwnerRuleAssignment.ts +157 -6
  145. package/Server/Utils/Telemetry/TelemetryEntity.ts +205 -6
  146. package/SslCertificates/GlobalSign.crt +26 -0
  147. package/SslCertificates/Readme.md +3 -0
  148. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +451 -0
  149. package/Tests/App/Dashboard/AIInsightsChatPanel.test.tsx +881 -0
  150. package/Tests/App/Dashboard/AIInsightsPageContext.test.ts +458 -0
  151. package/Tests/App/Dashboard/AIInsightsResourceCitations.test.ts +124 -0
  152. package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +798 -0
  153. package/Tests/App/Dashboard/AskAiSuggestedPrompts.test.ts +7 -0
  154. package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +104 -0
  155. package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +156 -64
  156. package/Tests/App/Dashboard/DiscoveryScanStatusMessage.test.tsx +970 -0
  157. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +406 -12
  158. package/Tests/App/Dashboard/EmbeddedMetricEventScope.test.tsx +358 -0
  159. package/Tests/App/Dashboard/ErrorPatternEventScope.test.tsx +148 -0
  160. package/Tests/App/Dashboard/EventOverlayAdditionalResourceScope.test.ts +255 -0
  161. package/Tests/App/Dashboard/EventOverlayScope.test.ts +845 -0
  162. package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +903 -0
  163. package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +3 -3
  164. package/Tests/App/Dashboard/InvestigationDrawer.test.tsx +21 -0
  165. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +761 -0
  166. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +7 -4
  167. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +15 -7
  168. package/Tests/App/Dashboard/MetricsViewerEventScopeNavigation.test.tsx +264 -0
  169. package/Tests/App/Dashboard/MonitorSummaryTestMonitorButton.test.tsx +342 -0
  170. package/Tests/App/Dashboard/MonitorTestForm.test.tsx +612 -0
  171. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +301 -0
  172. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +74 -2
  173. package/Tests/App/Dashboard/TextClampLayoutHarness.ts +224 -0
  174. package/Tests/Models/InventoryItemNaming.test.ts +54 -0
  175. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +161 -0
  176. package/Tests/Server/API/AIInvestigationAPI.test.ts +14 -0
  177. package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +260 -0
  178. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +133 -0
  179. package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +482 -0
  180. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +1 -1
  181. package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +1 -1
  182. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +38 -16
  183. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +106 -31
  184. package/Tests/Server/Infrastructure/ExampleDockerfiles.test.ts +46 -12
  185. package/Tests/Server/Infrastructure/Postgres/MonitorDescriptionMigration.test.ts +422 -0
  186. package/Tests/Server/Infrastructure/TelemetryExporterDeploymentConfig.test.ts +1 -1
  187. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +25 -4
  188. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +72 -0
  189. package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +12 -12
  190. package/Tests/Server/Middleware/PublicDashboardRateLimitStatusPage.test.ts +800 -0
  191. package/Tests/Server/Services/AIInvestigationDecisionPostgres.test.ts +346 -0
  192. package/Tests/Server/Services/AddOwnersIdempotency.test.ts +471 -0
  193. package/Tests/Server/Services/DatabaseServiceAutoOwnerOnCreate.test.ts +250 -0
  194. package/Tests/Server/Services/DatabaseServiceUniqueColumnsTogether.test.ts +622 -0
  195. package/Tests/Server/Services/MonitorDescriptionValidation.test.ts +211 -0
  196. package/Tests/Server/Services/MonitorTemplateDescriptionPropagation.test.ts +269 -0
  197. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +504 -0
  198. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupARun.test.ts +43 -0
  199. package/Tests/Server/Services/SessionRetention.test.ts +334 -0
  200. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +172 -0
  201. package/Tests/Server/Services/StatusPagePrivateUserReadAccess.test.ts +178 -0
  202. package/Tests/Server/Services/StatusPagePrivateUserReadAccessPostgres.test.ts +311 -0
  203. package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +216 -1
  204. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1 -1
  205. package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1 -1
  206. package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +626 -0
  207. package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +668 -0
  208. package/Tests/Server/Types/Database/Permissions/SelectRejectionMessageContract.test.ts +1 -0
  209. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1 -1
  210. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +16 -9
  211. package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +137 -1
  212. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +697 -0
  213. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +105 -0
  214. package/Tests/Server/Utils/AI/RemediationHandoff.test.ts +16 -5
  215. package/Tests/Server/Utils/AI/ResourceTools.test.ts +1304 -0
  216. package/Tests/Server/Utils/AI/RumTools.test.ts +769 -0
  217. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +63 -0
  218. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +11 -9
  219. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +62 -45
  220. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +25 -24
  221. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaHumanMessages.test.ts +513 -0
  222. package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +2 -2
  223. package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +1 -1
  224. package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +9 -9
  225. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +7 -5
  226. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +2 -2
  227. package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +4 -3
  228. package/Tests/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.test.ts +2 -2
  229. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +3 -2
  230. package/Tests/Server/Utils/Monitor/MonitorCriteriaHumanMessages.test.ts +529 -0
  231. package/Tests/Server/Utils/Rules/OwnerRuleAssignment.test.ts +412 -4
  232. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingClickhouseIntegration.test.ts +1 -1
  233. package/Tests/Server/Utils/TailwindBuildCopy.test.ts +14 -2
  234. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +235 -0
  235. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +385 -2
  236. package/Tests/Server/Utils/VendorAssets.test.ts +17 -3
  237. package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
  238. package/Tests/Types/AI/AIResourceContext.test.ts +312 -0
  239. package/Tests/Types/Database/UniqueColumnsTogether.test.ts +100 -0
  240. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +4 -4
  241. package/Tests/Types/Monitor/MonitorStepRetryPersistence.test.ts +125 -0
  242. package/Tests/Types/WebsiteRequest.test.ts +44 -0
  243. package/Tests/UI/Components/Forms/MonitorDescriptionForm.test.tsx +410 -0
  244. package/Tests/UI/Components/ModelTable/BaseModelTableReadPermissions.test.tsx +439 -0
  245. package/Tests/UI/Components/NavBarMenuSearch.test.tsx +372 -0
  246. package/Tests/UI/EsbuildConfig.test.ts +1 -1
  247. package/Tests/UI/Monitor/MonitorStepRetrySettings.test.tsx +616 -0
  248. package/Tests/UI/ReactRouterSingletonBuild.test.ts +4 -2
  249. package/Tests/UI/Rum/ReplayEngine.test.ts +179 -0
  250. package/Tests/UI/Rum/ReplayHeader.test.tsx +23 -0
  251. package/Tests/UI/Rum/ReplayScrubber.test.tsx +88 -0
  252. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +83 -0
  253. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +33 -0
  254. package/Tests/Utils/API.test.ts +96 -0
  255. package/Tests/Utils/HTTPResponseBodyReader.test.ts +188 -1
  256. package/Tests/Utils/Metrics/MetricEventScopeUrl.test.ts +101 -0
  257. package/Tests/Utils/Monitor/MonitorTestAvailabilityUtil.test.ts +256 -0
  258. package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +105 -0
  259. package/Types/AI/AIChatPageContext.ts +87 -2
  260. package/Types/AI/AIChatTypes.ts +4 -0
  261. package/Types/AI/AIResourceContext.ts +198 -0
  262. package/Types/AI/InvestigationNotStartedReason.ts +30 -0
  263. package/Types/Database/UniqueColumnsTogether.ts +55 -0
  264. package/Types/Metrics/MetricQueryConfigData.ts +8 -0
  265. package/Types/Rum/WebVitals.ts +71 -0
  266. package/Types/WebsiteRequest.ts +2 -0
  267. package/UI/Components/EditionLabel/EditionLabel.tsx +7 -5
  268. package/UI/Components/KeyboardShortcut/Screenshots/README.md +1 -1
  269. package/UI/Components/ModelTable/BaseModelTable.tsx +8 -0
  270. package/UI/Components/Navbar/NavBar.tsx +2 -0
  271. package/UI/Components/Navbar/NavBarMenuModal.tsx +6 -2
  272. package/UI/esbuild-config.js +4 -4
  273. package/Utils/API.ts +10 -1
  274. package/Utils/Dashboard/HtmlWidgetDocument.ts +1 -1
  275. package/Utils/HTTPResponseBodyReader.ts +64 -8
  276. package/Utils/Metrics/MetricExplorerUrl.ts +19 -0
  277. package/Utils/Monitor/MonitorTestAvailabilityUtil.ts +92 -0
  278. package/Utils/Telemetry/HostIpAddresses.ts +46 -2
  279. package/build/dist/Models/DatabaseModels/AIAgentOwnerTeam.js +4 -1
  280. package/build/dist/Models/DatabaseModels/AIAgentOwnerTeam.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/AIAgentOwnerUser.js +4 -1
  282. package/build/dist/Models/DatabaseModels/AIAgentOwnerUser.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/Alert.js +12 -0
  284. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerTeam.js +3 -1
  286. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerTeam.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerUser.js +3 -1
  288. package/build/dist/Models/DatabaseModels/AlertEpisodeOwnerUser.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/AlertOwnerTeam.js +3 -1
  290. package/build/dist/Models/DatabaseModels/AlertOwnerTeam.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/AlertOwnerUser.js +3 -1
  292. package/build/dist/Models/DatabaseModels/AlertOwnerUser.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/CephClusterOwnerTeam.js +4 -1
  294. package/build/dist/Models/DatabaseModels/CephClusterOwnerTeam.js.map +1 -1
  295. package/build/dist/Models/DatabaseModels/CephClusterOwnerUser.js +4 -1
  296. package/build/dist/Models/DatabaseModels/CephClusterOwnerUser.js.map +1 -1
  297. package/build/dist/Models/DatabaseModels/CloudResourceOwnerTeam.js +4 -1
  298. package/build/dist/Models/DatabaseModels/CloudResourceOwnerTeam.js.map +1 -1
  299. package/build/dist/Models/DatabaseModels/CloudResourceOwnerUser.js +4 -1
  300. package/build/dist/Models/DatabaseModels/CloudResourceOwnerUser.js.map +1 -1
  301. package/build/dist/Models/DatabaseModels/DashboardOwnerTeam.js +3 -1
  302. package/build/dist/Models/DatabaseModels/DashboardOwnerTeam.js.map +1 -1
  303. package/build/dist/Models/DatabaseModels/DashboardOwnerUser.js +3 -1
  304. package/build/dist/Models/DatabaseModels/DashboardOwnerUser.js.map +1 -1
  305. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +3 -0
  306. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  307. package/build/dist/Models/DatabaseModels/DockerHostOwnerTeam.js +4 -1
  308. package/build/dist/Models/DatabaseModels/DockerHostOwnerTeam.js.map +1 -1
  309. package/build/dist/Models/DatabaseModels/DockerHostOwnerUser.js +4 -1
  310. package/build/dist/Models/DatabaseModels/DockerHostOwnerUser.js.map +1 -1
  311. package/build/dist/Models/DatabaseModels/DockerSwarmClusterOwnerTeam.js +4 -1
  312. package/build/dist/Models/DatabaseModels/DockerSwarmClusterOwnerTeam.js.map +1 -1
  313. package/build/dist/Models/DatabaseModels/DockerSwarmClusterOwnerUser.js +4 -1
  314. package/build/dist/Models/DatabaseModels/DockerSwarmClusterOwnerUser.js.map +1 -1
  315. package/build/dist/Models/DatabaseModels/HostOwnerTeam.js +4 -1
  316. package/build/dist/Models/DatabaseModels/HostOwnerTeam.js.map +1 -1
  317. package/build/dist/Models/DatabaseModels/HostOwnerUser.js +4 -1
  318. package/build/dist/Models/DatabaseModels/HostOwnerUser.js.map +1 -1
  319. package/build/dist/Models/DatabaseModels/Incident.js +12 -0
  320. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  321. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerTeam.js +3 -1
  322. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerTeam.js.map +1 -1
  323. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerUser.js +3 -1
  324. package/build/dist/Models/DatabaseModels/IncidentEpisodeOwnerUser.js.map +1 -1
  325. package/build/dist/Models/DatabaseModels/IncidentOwnerTeam.js +3 -1
  326. package/build/dist/Models/DatabaseModels/IncidentOwnerTeam.js.map +1 -1
  327. package/build/dist/Models/DatabaseModels/IncidentOwnerUser.js +3 -1
  328. package/build/dist/Models/DatabaseModels/IncidentOwnerUser.js.map +1 -1
  329. package/build/dist/Models/DatabaseModels/IncidentTemplateOwnerTeam.js +4 -1
  330. package/build/dist/Models/DatabaseModels/IncidentTemplateOwnerTeam.js.map +1 -1
  331. package/build/dist/Models/DatabaseModels/IncidentTemplateOwnerUser.js +4 -1
  332. package/build/dist/Models/DatabaseModels/IncidentTemplateOwnerUser.js.map +1 -1
  333. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.js +3 -1
  334. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.js.map +1 -1
  335. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerUser.js +3 -1
  336. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerUser.js.map +1 -1
  337. package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js +4 -1
  338. package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js.map +1 -1
  339. package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js +4 -1
  340. package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js.map +1 -1
  341. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerTeam.js +4 -1
  342. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerTeam.js.map +1 -1
  343. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerUser.js +4 -1
  344. package/build/dist/Models/DatabaseModels/KubernetesClusterOwnerUser.js.map +1 -1
  345. package/build/dist/Models/DatabaseModels/Monitor.js +2 -3
  346. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  347. package/build/dist/Models/DatabaseModels/MonitorGroupOwnerTeam.js +4 -1
  348. package/build/dist/Models/DatabaseModels/MonitorGroupOwnerTeam.js.map +1 -1
  349. package/build/dist/Models/DatabaseModels/MonitorGroupOwnerUser.js +4 -1
  350. package/build/dist/Models/DatabaseModels/MonitorGroupOwnerUser.js.map +1 -1
  351. package/build/dist/Models/DatabaseModels/MonitorOwnerTeam.js +3 -1
  352. package/build/dist/Models/DatabaseModels/MonitorOwnerTeam.js.map +1 -1
  353. package/build/dist/Models/DatabaseModels/MonitorOwnerUser.js +3 -1
  354. package/build/dist/Models/DatabaseModels/MonitorOwnerUser.js.map +1 -1
  355. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -3
  356. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  357. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerTeam.js +4 -1
  358. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerTeam.js.map +1 -1
  359. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerUser.js +4 -1
  360. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerUser.js.map +1 -1
  361. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerTeam.js +3 -1
  362. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerTeam.js.map +1 -1
  363. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerUser.js +3 -1
  364. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerUser.js.map +1 -1
  365. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.js +3 -1
  366. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.js.map +1 -1
  367. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.js +3 -1
  368. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.js.map +1 -1
  369. package/build/dist/Models/DatabaseModels/PodmanHostOwnerTeam.js +4 -1
  370. package/build/dist/Models/DatabaseModels/PodmanHostOwnerTeam.js.map +1 -1
  371. package/build/dist/Models/DatabaseModels/PodmanHostOwnerUser.js +4 -1
  372. package/build/dist/Models/DatabaseModels/PodmanHostOwnerUser.js.map +1 -1
  373. package/build/dist/Models/DatabaseModels/ProbeOwnerTeam.js +4 -1
  374. package/build/dist/Models/DatabaseModels/ProbeOwnerTeam.js.map +1 -1
  375. package/build/dist/Models/DatabaseModels/ProbeOwnerUser.js +4 -1
  376. package/build/dist/Models/DatabaseModels/ProbeOwnerUser.js.map +1 -1
  377. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerTeam.js +4 -1
  378. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerTeam.js.map +1 -1
  379. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerUser.js +4 -1
  380. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerUser.js.map +1 -1
  381. package/build/dist/Models/DatabaseModels/RumApplicationOwnerTeam.js +4 -1
  382. package/build/dist/Models/DatabaseModels/RumApplicationOwnerTeam.js.map +1 -1
  383. package/build/dist/Models/DatabaseModels/RumApplicationOwnerUser.js +4 -1
  384. package/build/dist/Models/DatabaseModels/RumApplicationOwnerUser.js.map +1 -1
  385. package/build/dist/Models/DatabaseModels/RunbookOwnerTeam.js +3 -1
  386. package/build/dist/Models/DatabaseModels/RunbookOwnerTeam.js.map +1 -1
  387. package/build/dist/Models/DatabaseModels/RunbookOwnerUser.js +3 -1
  388. package/build/dist/Models/DatabaseModels/RunbookOwnerUser.js.map +1 -1
  389. package/build/dist/Models/DatabaseModels/RunnerOwnerTeam.js +3 -1
  390. package/build/dist/Models/DatabaseModels/RunnerOwnerTeam.js.map +1 -1
  391. package/build/dist/Models/DatabaseModels/RunnerOwnerUser.js +3 -1
  392. package/build/dist/Models/DatabaseModels/RunnerOwnerUser.js.map +1 -1
  393. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerTeam.js +3 -1
  394. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerTeam.js.map +1 -1
  395. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerUser.js +3 -1
  396. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceOwnerUser.js.map +1 -1
  397. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam.js +6 -1
  398. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam.js.map +1 -1
  399. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +6 -1
  400. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js.map +1 -1
  401. package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerTeam.js +4 -1
  402. package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerTeam.js.map +1 -1
  403. package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerUser.js +4 -1
  404. package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerUser.js.map +1 -1
  405. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js +3 -1
  406. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js.map +1 -1
  407. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js +3 -1
  408. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js.map +1 -1
  409. package/build/dist/Models/DatabaseModels/ServiceOwnerTeam.js +4 -1
  410. package/build/dist/Models/DatabaseModels/ServiceOwnerTeam.js.map +1 -1
  411. package/build/dist/Models/DatabaseModels/ServiceOwnerUser.js +4 -1
  412. package/build/dist/Models/DatabaseModels/ServiceOwnerUser.js.map +1 -1
  413. package/build/dist/Models/DatabaseModels/StatusPageOwnerTeam.js +3 -1
  414. package/build/dist/Models/DatabaseModels/StatusPageOwnerTeam.js.map +1 -1
  415. package/build/dist/Models/DatabaseModels/StatusPageOwnerUser.js +3 -1
  416. package/build/dist/Models/DatabaseModels/StatusPageOwnerUser.js.map +1 -1
  417. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerTeam.js +4 -1
  418. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerTeam.js.map +1 -1
  419. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerUser.js +4 -1
  420. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerUser.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/WorkflowOwnerTeam.js +3 -1
  422. package/build/dist/Models/DatabaseModels/WorkflowOwnerTeam.js.map +1 -1
  423. package/build/dist/Models/DatabaseModels/WorkflowOwnerUser.js +3 -1
  424. package/build/dist/Models/DatabaseModels/WorkflowOwnerUser.js.map +1 -1
  425. package/build/dist/Server/API/AIAgentDataAPI.js +1 -1
  426. package/build/dist/Server/API/AIChatAPI.js +3 -1
  427. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  428. package/build/dist/Server/API/AIInvestigationAPI.js +3 -0
  429. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  430. package/build/dist/Server/API/StatusPageAPI.js +9 -1
  431. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  432. package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.js +1 -1
  433. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793500000000-MakeOwnerRowsUnique.js +602 -0
  434. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793500000000-MakeOwnerRowsUnique.js.map +1 -0
  435. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793700000000-AddAIInvestigationDecision.js +14 -0
  436. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793700000000-AddAIInvestigationDecision.js.map +1 -0
  437. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793800000000-RemoveMonitorDescriptionLengthLimit.js +20 -0
  438. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793800000000-RemoveMonitorDescriptionLengthLimit.js.map +1 -0
  439. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  440. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  441. package/build/dist/Server/Middleware/IdentityRateLimit.js +22 -1
  442. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  443. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +117 -36
  444. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -1
  445. package/build/dist/Server/Services/AlertEpisodeService.js +13 -31
  446. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  447. package/build/dist/Server/Services/AlertGroupingEngineService.js +7 -4
  448. package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
  449. package/build/dist/Server/Services/AlertService.js +13 -31
  450. package/build/dist/Server/Services/AlertService.js.map +1 -1
  451. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +12 -8
  452. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
  453. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +12 -8
  454. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
  455. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js +12 -8
  456. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js.map +1 -1
  457. package/build/dist/Server/Services/DatabaseService.js +71 -1
  458. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  459. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +12 -8
  460. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
  461. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +12 -8
  462. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
  463. package/build/dist/Server/Services/HostOwnerRuleEngineService.js +12 -8
  464. package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
  465. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js +6 -4
  466. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js.map +1 -1
  467. package/build/dist/Server/Services/IncidentEpisodeService.js +15 -68
  468. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  469. package/build/dist/Server/Services/IncidentGroupingEngineService.js +7 -4
  470. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  471. package/build/dist/Server/Services/IncidentService.js +13 -31
  472. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  473. package/build/dist/Server/Services/IncidentTemplateService.js +13 -31
  474. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  475. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +12 -8
  476. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -1
  477. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +12 -8
  478. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -1
  479. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +12 -8
  480. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
  481. package/build/dist/Server/Services/MonitorService.js +13 -31
  482. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  483. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +12 -8
  484. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
  485. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +12 -8
  486. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -1
  487. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +12 -8
  488. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -1
  489. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +12 -8
  490. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
  491. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +12 -8
  492. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
  493. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js +12 -8
  494. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -1
  495. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js +12 -8
  496. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js.map +1 -1
  497. package/build/dist/Server/Services/ScheduledMaintenanceService.js +13 -31
  498. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  499. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +12 -29
  500. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  501. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +12 -8
  502. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -1
  503. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +12 -8
  504. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
  505. package/build/dist/Server/Services/StatusPagePrivateUserService.js +33 -25
  506. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  507. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +36 -1
  508. package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
  509. package/build/dist/Server/Services/StatusPageService.js +50 -35
  510. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  511. package/build/dist/Server/Services/UserSessionService.js +5 -0
  512. package/build/dist/Server/Services/UserSessionService.js.map +1 -1
  513. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js +12 -8
  514. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js.map +1 -1
  515. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js +12 -8
  516. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js.map +1 -1
  517. package/build/dist/Server/Types/Database/Permissions/OwnedScopePermission.js +15 -10
  518. package/build/dist/Server/Types/Database/Permissions/OwnedScopePermission.js.map +1 -1
  519. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +82 -0
  520. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  521. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +12 -3
  522. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  523. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +32 -4
  524. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  525. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +1 -1
  526. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +1 -1
  527. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +8 -5
  528. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  529. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +33 -13
  530. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  531. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +31 -10
  532. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  533. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +205 -0
  534. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -0
  535. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +13 -0
  536. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  537. package/build/dist/Server/Utils/AI/Toolbox/Index.js +30 -6
  538. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  539. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +721 -0
  540. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -0
  541. package/build/dist/Server/Utils/AI/Toolbox/RumTools.js +330 -0
  542. package/build/dist/Server/Utils/AI/Toolbox/RumTools.js.map +1 -0
  543. package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js.map +1 -1
  544. package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js +1 -1
  545. package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js.map +1 -1
  546. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +11 -0
  547. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
  548. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +106 -101
  549. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  550. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +4 -2
  551. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  552. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +94 -0
  553. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -1
  554. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +171 -6
  555. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  556. package/build/dist/Types/AI/AIChatPageContext.js +68 -2
  557. package/build/dist/Types/AI/AIChatPageContext.js.map +1 -1
  558. package/build/dist/Types/AI/AIChatTypes.js +4 -0
  559. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  560. package/build/dist/Types/AI/AIResourceContext.js +162 -0
  561. package/build/dist/Types/AI/AIResourceContext.js.map +1 -0
  562. package/build/dist/Types/AI/InvestigationNotStartedReason.js +2 -0
  563. package/build/dist/Types/AI/InvestigationNotStartedReason.js.map +1 -0
  564. package/build/dist/Types/Database/UniqueColumnsTogether.js +19 -0
  565. package/build/dist/Types/Database/UniqueColumnsTogether.js.map +1 -0
  566. package/build/dist/Types/Metrics/MetricQueryConfigData.js.map +1 -1
  567. package/build/dist/Types/Rum/WebVitals.js +64 -0
  568. package/build/dist/Types/Rum/WebVitals.js.map +1 -0
  569. package/build/dist/Types/WebsiteRequest.js +2 -0
  570. package/build/dist/Types/WebsiteRequest.js.map +1 -1
  571. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +7 -5
  572. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  573. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +7 -0
  574. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  575. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  576. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +4 -2
  577. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  578. package/build/dist/Utils/API.js +8 -4
  579. package/build/dist/Utils/API.js.map +1 -1
  580. package/build/dist/Utils/HTTPResponseBodyReader.js +50 -6
  581. package/build/dist/Utils/HTTPResponseBodyReader.js.map +1 -1
  582. package/build/dist/Utils/Metrics/MetricExplorerUrl.js +14 -3
  583. package/build/dist/Utils/Metrics/MetricExplorerUrl.js.map +1 -1
  584. package/build/dist/Utils/Monitor/MonitorTestAvailabilityUtil.js +68 -0
  585. package/build/dist/Utils/Monitor/MonitorTestAvailabilityUtil.js.map +1 -0
  586. package/build/dist/Utils/Telemetry/HostIpAddresses.js +42 -3
  587. package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -1
  588. package/package.json +5 -5
  589. package/test-setup.sh +2 -2
@@ -0,0 +1,970 @@
1
+ import "@testing-library/jest-dom";
2
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
3
+ import {
4
+ cleanup,
5
+ fireEvent,
6
+ render,
7
+ RenderResult,
8
+ screen,
9
+ } from "@testing-library/react";
10
+ import userEvent from "@testing-library/user-event";
11
+ import type { Mock, SpyInstance } from "jest-mock";
12
+ import React, { act } from "react";
13
+ import DiscoveryScanStatusMessage, {
14
+ COLLAPSED_LINE_COUNT,
15
+ isMessageTallerThanPreview,
16
+ MessageBoxMetrics,
17
+ readMessageBoxMetrics,
18
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/NetworkDevice/DiscoveryScanStatusMessage";
19
+ import {
20
+ installTextClampLayout,
21
+ LINE_HEIGHT_IN_PIXELS,
22
+ TextClampLayout,
23
+ } from "./TextClampLayoutHarness";
24
+
25
+ /*
26
+ * OneUptime issue #3842: on Network > Discovery Scans, "Show details" and
27
+ * "Hide details" showed the same information. The toggle was always there,
28
+ * the collapsed state was the message clamped to two lines, and the expanded
29
+ * state was the same message in full — so for any message that fits in two
30
+ * lines, which the probe's summary of a healthy sweep does, the two states
31
+ * were identical and clicking only moved the link.
32
+ *
33
+ * These tests hold the fix: the message is rendered once; the toggle exists
34
+ * only when the two-line preview really cuts it short; and when it exists the
35
+ * two states differ.
36
+ */
37
+
38
+ // The message from the report, verbatim.
39
+ const REPORTED_MESSAGE: string =
40
+ "Swept 15360 hosts: 2906 answered ICMP ping, 2888 answered SNMP.";
41
+
42
+ // The other row in the report's recording.
43
+ const REPORTED_ROUTER_MESSAGE: string =
44
+ "Swept 2560 hosts: 917 answered ICMP ping, 460 answered SNMP.";
45
+
46
+ // A summary carrying diagnostics — the shape the preview has to cut short.
47
+ const LONG_MESSAGE: string =
48
+ "Swept 254 hosts: 41 answered ICMP ping, 3 answered SNMP. 12 host(s) replied with an SNMP error rather than silence; most common: Authentication failure (incorrect password, community or key). Answered by credentials: Core v3 on 3. No host answered: Legacy v2c community.";
49
+
50
+ // A running sweep's message, which a poll replaces every ten seconds.
51
+ const PROGRESS_MESSAGE: string =
52
+ "Scan in progress: 6,144 of 15,360 addresses swept so far. Checking SNMP credentials (176 of 256). 3 answered ICMP ping, 2 answered SNMP. These results update as the sweep continues.";
53
+
54
+ const SCAN_LABEL: string =
55
+ "Switch Discovery - WBHQ Unit/Core Switches (10.240-249.0-255.220-225)";
56
+
57
+ /*
58
+ * The width the reporter's recording was taken at: "Swept 15360 hosts: 2906
59
+ * answered ICMP ping, 2888" is the first of its two lines.
60
+ */
61
+ const DESKTOP_CHARACTERS_PER_LINE: number = 48;
62
+
63
+ let layout: TextClampLayout | null = null;
64
+
65
+ function installLayout(
66
+ charactersPerLine: number = DESKTOP_CHARACTERS_PER_LINE,
67
+ withResizeObserver: boolean = true,
68
+ ): TextClampLayout {
69
+ layout = installTextClampLayout({
70
+ charactersPerLine: charactersPerLine,
71
+ withResizeObserver: withResizeObserver,
72
+ });
73
+ return layout;
74
+ }
75
+
76
+ afterEach(() => {
77
+ cleanup();
78
+ layout?.restore();
79
+ layout = null;
80
+ jest.restoreAllMocks();
81
+ });
82
+
83
+ function renderMessage(
84
+ message: string,
85
+ scanLabel: string = SCAN_LABEL,
86
+ ): RenderResult {
87
+ return render(
88
+ <DiscoveryScanStatusMessage message={message} scanLabel={scanLabel} />,
89
+ );
90
+ }
91
+
92
+ function messageParagraph(container: HTMLElement): HTMLParagraphElement {
93
+ const paragraph: HTMLParagraphElement | null =
94
+ container.querySelector("p[id]");
95
+
96
+ if (!paragraph) {
97
+ throw new Error("The status message paragraph was not rendered");
98
+ }
99
+
100
+ return paragraph;
101
+ }
102
+
103
+ function toggle(): HTMLButtonElement {
104
+ return screen.getByRole("button", {
105
+ name: /details for/,
106
+ }) as HTMLButtonElement;
107
+ }
108
+
109
+ function queryToggle(): HTMLElement | null {
110
+ return screen.queryByRole("button", { name: /details for/ });
111
+ }
112
+
113
+ /*
114
+ * user-event dispatches through its own copy of @testing-library/dom, which
115
+ * is not configured to wrap events in React's act() the way the copy inside
116
+ * @testing-library/react is — so its interactions are wrapped here.
117
+ */
118
+ async function asUser(interaction: () => Promise<unknown>): Promise<void> {
119
+ await act(async () => {
120
+ await interaction();
121
+ });
122
+ }
123
+
124
+ function paintedLines(paragraph: HTMLElement): number {
125
+ return paragraph.clientHeight / LINE_HEIGHT_IN_PIXELS;
126
+ }
127
+
128
+ function metrics(
129
+ scrollHeight: number,
130
+ clientHeight: number,
131
+ lineHeight: string = "20px",
132
+ ): MessageBoxMetrics {
133
+ return {
134
+ scrollHeight: scrollHeight,
135
+ clientHeight: clientHeight,
136
+ lineHeight: lineHeight,
137
+ };
138
+ }
139
+
140
+ describe("isMessageTallerThanPreview", () => {
141
+ test("the preview is two lines, matching the line-clamp-2 class the component applies", () => {
142
+ expect(COLLAPSED_LINE_COUNT).toBe(2);
143
+ });
144
+
145
+ test.each([
146
+ ["one line", 20, false],
147
+ ["exactly two lines", 40, false],
148
+ ["three lines", 60, true],
149
+ ["ten lines", 200, true],
150
+ ])(
151
+ "%s of text at a 20px line height (scrollHeight %i) needs a toggle: %s",
152
+ (_label: string, scrollHeight: number, expected: boolean) => {
153
+ expect(isMessageTallerThanPreview(metrics(scrollHeight, 40), false)).toBe(
154
+ expected,
155
+ );
156
+ },
157
+ );
158
+
159
+ test.each([true, false])(
160
+ "with a known line height the answer does not depend on whether the message is expanded (expanded: %s)",
161
+ (isExpanded: boolean) => {
162
+ /*
163
+ * An expanded box is exactly as tall as its text, so clientHeight equals
164
+ * scrollHeight there. The answer must not change because of it —
165
+ * otherwise expanding a message would remove its own "Hide details".
166
+ */
167
+ expect(isMessageTallerThanPreview(metrics(60, 60), isExpanded)).toBe(
168
+ true,
169
+ );
170
+ expect(isMessageTallerThanPreview(metrics(60, 40), isExpanded)).toBe(
171
+ true,
172
+ );
173
+ expect(isMessageTallerThanPreview(metrics(40, 40), isExpanded)).toBe(
174
+ false,
175
+ );
176
+ },
177
+ );
178
+
179
+ test.each([
180
+ [41, false],
181
+ [49, false],
182
+ [50, false],
183
+ [51, true],
184
+ ])(
185
+ "a height rounded near the two-line limit is judged at half a line past it (scrollHeight %i: %s)",
186
+ (scrollHeight: number, expected: boolean) => {
187
+ expect(isMessageTallerThanPreview(metrics(scrollHeight, 40), false)).toBe(
188
+ expected,
189
+ );
190
+ },
191
+ );
192
+
193
+ test("a fractional line height is read as a number, not rounded away", () => {
194
+ // 18.4px lines: two are 36.8px (rounded to 37), three are 55.2px (55).
195
+ expect(isMessageTallerThanPreview(metrics(37, 37, "18.4px"), false)).toBe(
196
+ false,
197
+ );
198
+ expect(isMessageTallerThanPreview(metrics(55, 37, "18.4px"), false)).toBe(
199
+ true,
200
+ );
201
+ });
202
+
203
+ test("a larger line height moves the limit with it", () => {
204
+ expect(isMessageTallerThanPreview(metrics(56, 56, "28px"), false)).toBe(
205
+ false,
206
+ );
207
+ expect(isMessageTallerThanPreview(metrics(84, 56, "28px"), false)).toBe(
208
+ true,
209
+ );
210
+ });
211
+
212
+ test("a different preview length can be asked about", () => {
213
+ expect(isMessageTallerThanPreview(metrics(60, 60), false, 3)).toBe(false);
214
+ expect(isMessageTallerThanPreview(metrics(80, 60), false, 3)).toBe(true);
215
+ expect(isMessageTallerThanPreview(metrics(20, 20), false, 1)).toBe(false);
216
+ expect(isMessageTallerThanPreview(metrics(40, 20), false, 1)).toBe(true);
217
+ });
218
+
219
+ test.each(["normal", "", "0px", "-4px", "inherit"])(
220
+ "without a usable line height (%p) a clamped box is judged by whether its text is taller than it",
221
+ (lineHeight: string) => {
222
+ expect(
223
+ isMessageTallerThanPreview(metrics(60, 40, lineHeight), false),
224
+ ).toBe(true);
225
+ expect(
226
+ isMessageTallerThanPreview(metrics(40, 40, lineHeight), false),
227
+ ).toBe(false);
228
+ // One pixel of rounding is not a hidden line.
229
+ expect(
230
+ isMessageTallerThanPreview(metrics(41, 40, lineHeight), false),
231
+ ).toBe(false);
232
+ },
233
+ );
234
+
235
+ test.each(["normal", "", "0px"])(
236
+ "without a usable line height (%p) an expanded box cannot say, so the answer is unknown",
237
+ (lineHeight: string) => {
238
+ expect(
239
+ isMessageTallerThanPreview(metrics(120, 120, lineHeight), true),
240
+ ).toBeUndefined();
241
+ expect(
242
+ isMessageTallerThanPreview(metrics(20, 20, lineHeight), true),
243
+ ).toBeUndefined();
244
+ },
245
+ );
246
+
247
+ test.each([
248
+ ["with a line height", "20px"],
249
+ ["without a line height", "normal"],
250
+ ])(
251
+ "a box with no size (not displayed) never asks for a toggle, %s",
252
+ (_label: string, lineHeight: string) => {
253
+ expect(isMessageTallerThanPreview(metrics(0, 0, lineHeight), false)).toBe(
254
+ false,
255
+ );
256
+ },
257
+ );
258
+ });
259
+
260
+ describe("readMessageBoxMetrics", () => {
261
+ test("reads the text height, the painted height and the computed line height", () => {
262
+ installLayout(10);
263
+ const { container } = renderMessage("x".repeat(35));
264
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
265
+
266
+ // Four lines of text, clamped to two.
267
+ expect(readMessageBoxMetrics(paragraph)).toEqual({
268
+ scrollHeight: 80,
269
+ clientHeight: 40,
270
+ lineHeight: "20px",
271
+ });
272
+ });
273
+
274
+ test("returns what jsdom reports when nothing is laid out", () => {
275
+ const paragraph: HTMLParagraphElement = document.createElement("p");
276
+
277
+ expect(readMessageBoxMetrics(paragraph)).toEqual({
278
+ scrollHeight: 0,
279
+ clientHeight: 0,
280
+ lineHeight: expect.any(String),
281
+ });
282
+ });
283
+ });
284
+
285
+ describe("a message that fits in the preview has no toggle (issue #3842)", () => {
286
+ test("the reported message is shown in full, with neither Show details nor Hide details", () => {
287
+ installLayout();
288
+ const { container } = renderMessage(REPORTED_MESSAGE);
289
+
290
+ expect(paintedLines(messageParagraph(container))).toBe(2);
291
+ expect(messageParagraph(container)).toHaveTextContent(REPORTED_MESSAGE);
292
+ expect(queryToggle()).not.toBeInTheDocument();
293
+ expect(screen.queryByText("Show details")).not.toBeInTheDocument();
294
+ expect(screen.queryByText("Hide details")).not.toBeInTheDocument();
295
+ expect(container.textContent).toBe(REPORTED_MESSAGE);
296
+ });
297
+
298
+ test("the other row in the report has no toggle either", () => {
299
+ installLayout();
300
+ renderMessage(REPORTED_ROUTER_MESSAGE);
301
+
302
+ expect(screen.getByText(REPORTED_ROUTER_MESSAGE)).toBeInTheDocument();
303
+ expect(queryToggle()).not.toBeInTheDocument();
304
+ });
305
+
306
+ test("a one-line message has no toggle", () => {
307
+ installLayout();
308
+ const { container } = renderMessage("Scan complete.");
309
+
310
+ expect(paintedLines(messageParagraph(container))).toBe(1);
311
+ expect(queryToggle()).not.toBeInTheDocument();
312
+ });
313
+
314
+ test("a message of exactly two full lines has no toggle", () => {
315
+ installLayout();
316
+ renderMessage("y".repeat(DESKTOP_CHARACTERS_PER_LINE * 2));
317
+
318
+ expect(queryToggle()).not.toBeInTheDocument();
319
+ });
320
+
321
+ test("one character past two full lines is a third line, and gets the toggle", () => {
322
+ installLayout();
323
+ renderMessage("y".repeat(DESKTOP_CHARACTERS_PER_LINE * 2 + 1));
324
+
325
+ expect(toggle()).toHaveTextContent("Show details");
326
+ });
327
+
328
+ test("with no layout at all (jsdom's zero sizes) nothing is offered", () => {
329
+ renderMessage(LONG_MESSAGE);
330
+
331
+ expect(screen.getByText(LONG_MESSAGE)).toBeInTheDocument();
332
+ expect(queryToggle()).not.toBeInTheDocument();
333
+ });
334
+
335
+ test("a hidden message offers nothing until it is laid out", () => {
336
+ const harness: TextClampLayout = installLayout();
337
+ harness.setHidden(true);
338
+ renderMessage(LONG_MESSAGE);
339
+
340
+ expect(queryToggle()).not.toBeInTheDocument();
341
+
342
+ harness.setHidden(false);
343
+ harness.resize();
344
+
345
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
346
+ });
347
+
348
+ test.each(["", " ", "\n\t "])(
349
+ "an empty message (%p) renders nothing and observes nothing",
350
+ (message: string) => {
351
+ const harness: TextClampLayout = installLayout();
352
+ const { container } = renderMessage(message);
353
+
354
+ expect(container).toBeEmptyDOMElement();
355
+ expect(harness.createdObserverCount()).toBe(0);
356
+ },
357
+ );
358
+ });
359
+
360
+ describe("a message longer than the preview has two distinct states (issue #3842)", () => {
361
+ test("collapsed, it shows a two-line preview and offers Show details", () => {
362
+ installLayout();
363
+ const { container } = renderMessage(LONG_MESSAGE);
364
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
365
+
366
+ expect(paragraph).toHaveClass("line-clamp-2");
367
+ expect(paintedLines(paragraph)).toBe(2);
368
+ expect(paragraph.scrollHeight).toBeGreaterThan(paragraph.clientHeight);
369
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
370
+ expect(toggle()).toHaveTextContent(/^Show details/);
371
+ });
372
+
373
+ test("expanded, it shows the whole message and offers Hide details", async () => {
374
+ installLayout();
375
+ const { container } = renderMessage(LONG_MESSAGE);
376
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
377
+
378
+ await asUser(() => {
379
+ return userEvent.click(toggle());
380
+ });
381
+
382
+ expect(paragraph).not.toHaveClass("line-clamp-2");
383
+ expect(paintedLines(paragraph)).toBe(
384
+ Math.ceil(LONG_MESSAGE.length / DESKTOP_CHARACTERS_PER_LINE),
385
+ );
386
+ expect(paragraph.clientHeight).toBe(paragraph.scrollHeight);
387
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
388
+ expect(toggle()).toHaveTextContent(/^Hide details/);
389
+ });
390
+
391
+ test("the two states do not display the same thing", async () => {
392
+ installLayout();
393
+ const { container } = renderMessage(LONG_MESSAGE);
394
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
395
+
396
+ const collapsed: { lines: number; label: string; clamped: boolean } = {
397
+ lines: paintedLines(paragraph),
398
+ label: toggle().textContent || "",
399
+ clamped: paragraph.classList.contains("line-clamp-2"),
400
+ };
401
+
402
+ await asUser(() => {
403
+ return userEvent.click(toggle());
404
+ });
405
+
406
+ const expanded: { lines: number; label: string; clamped: boolean } = {
407
+ lines: paintedLines(paragraph),
408
+ label: toggle().textContent || "",
409
+ clamped: paragraph.classList.contains("line-clamp-2"),
410
+ };
411
+
412
+ expect(expanded.lines).toBeGreaterThan(collapsed.lines);
413
+ expect(expanded.label).not.toBe(collapsed.label);
414
+ expect(expanded.clamped).not.toBe(collapsed.clamped);
415
+ });
416
+
417
+ test("Hide details returns to the same preview", async () => {
418
+ installLayout();
419
+ const { container } = renderMessage(LONG_MESSAGE);
420
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
421
+
422
+ await asUser(() => {
423
+ return userEvent.click(toggle());
424
+ });
425
+ await asUser(() => {
426
+ return userEvent.click(toggle());
427
+ });
428
+
429
+ expect(paragraph).toHaveClass("line-clamp-2");
430
+ expect(paintedLines(paragraph)).toBe(2);
431
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
432
+ expect(toggle()).toHaveTextContent(/^Show details/);
433
+ });
434
+
435
+ test("repeated toggling alternates cleanly, with the toggle present throughout", () => {
436
+ installLayout();
437
+ const { container } = renderMessage(LONG_MESSAGE);
438
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
439
+
440
+ for (let click: number = 1; click <= 6; click++) {
441
+ fireEvent.click(toggle());
442
+ const isExpanded: boolean = click % 2 === 1;
443
+
444
+ expect(toggle()).toHaveAttribute("aria-expanded", String(isExpanded));
445
+ expect(paragraph.classList.contains("line-clamp-2")).toBe(!isExpanded);
446
+ expect(paintedLines(paragraph)).toBe(isExpanded ? 6 : 2);
447
+ }
448
+ });
449
+
450
+ test.each([
451
+ ["collapsed", 0],
452
+ ["expanded", 1],
453
+ ["collapsed again", 2],
454
+ ])(
455
+ "the message is rendered exactly once while %s",
456
+ (_state: string, clicks: number) => {
457
+ installLayout();
458
+ const { container } = renderMessage(LONG_MESSAGE);
459
+
460
+ for (let click: number = 0; click < clicks; click++) {
461
+ fireEvent.click(toggle());
462
+ }
463
+
464
+ expect(screen.getAllByText(LONG_MESSAGE)).toHaveLength(1);
465
+ expect(container.querySelectorAll("p")).toHaveLength(1);
466
+ expect(container.textContent?.split(LONG_MESSAGE)).toHaveLength(2);
467
+ },
468
+ );
469
+
470
+ test("the toggle follows the message rather than leading it", async () => {
471
+ installLayout();
472
+ const { container } = renderMessage(LONG_MESSAGE);
473
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
474
+
475
+ for (let click: number = 0; click < 2; click++) {
476
+ expect(
477
+ paragraph.compareDocumentPosition(toggle()) &
478
+ Node.DOCUMENT_POSITION_FOLLOWING,
479
+ ).toBeTruthy();
480
+ await asUser(() => {
481
+ return userEvent.click(toggle());
482
+ });
483
+ }
484
+ });
485
+
486
+ test("it is a plain button, not a native details disclosure", () => {
487
+ installLayout();
488
+ const { container } = renderMessage(LONG_MESSAGE);
489
+
490
+ expect(container.querySelector("details")).toBeNull();
491
+ expect(container.querySelector("summary")).toBeNull();
492
+ expect(toggle().tagName).toBe("BUTTON");
493
+ expect(toggle()).toHaveAttribute("type", "button");
494
+ });
495
+
496
+ test("inside a form, toggling never submits it", async () => {
497
+ installLayout();
498
+ const onSubmit: Mock<(event: React.FormEvent) => void> = jest.fn(
499
+ (event: React.FormEvent) => {
500
+ event.preventDefault();
501
+ },
502
+ );
503
+
504
+ render(
505
+ <form onSubmit={onSubmit}>
506
+ <DiscoveryScanStatusMessage
507
+ message={LONG_MESSAGE}
508
+ scanLabel={SCAN_LABEL}
509
+ />
510
+ </form>,
511
+ );
512
+
513
+ await asUser(() => {
514
+ return userEvent.click(toggle());
515
+ });
516
+ await asUser(() => {
517
+ return userEvent.click(toggle());
518
+ });
519
+
520
+ expect(onSubmit).not.toHaveBeenCalled();
521
+ });
522
+ });
523
+
524
+ describe("the toggle is accessible", () => {
525
+ test("its name is the visible words plus the scan they belong to", async () => {
526
+ installLayout();
527
+ renderMessage(LONG_MESSAGE);
528
+
529
+ expect(
530
+ screen.getByRole("button", { name: `Show details for ${SCAN_LABEL}` }),
531
+ ).toBeInTheDocument();
532
+
533
+ await asUser(() => {
534
+ return userEvent.click(toggle());
535
+ });
536
+
537
+ expect(
538
+ screen.getByRole("button", { name: `Hide details for ${SCAN_LABEL}` }),
539
+ ).toBeInTheDocument();
540
+ });
541
+
542
+ test("the scan's name is for assistive technology only; the visible words stay short", () => {
543
+ installLayout();
544
+ renderMessage(LONG_MESSAGE);
545
+
546
+ const hidden: HTMLElement | null = toggle().querySelector(".sr-only");
547
+
548
+ expect(hidden).not.toBeNull();
549
+ expect(hidden?.textContent).toBe(` for ${SCAN_LABEL}`);
550
+ expect(toggle().textContent?.replace(hidden?.textContent || "", "")).toBe(
551
+ "Show details",
552
+ );
553
+ });
554
+
555
+ test("it controls the message paragraph", () => {
556
+ installLayout();
557
+ const { container } = renderMessage(LONG_MESSAGE);
558
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
559
+
560
+ expect(paragraph.id).not.toBe("");
561
+ expect(toggle()).toHaveAttribute("aria-controls", paragraph.id);
562
+ expect(document.getElementById(paragraph.id)).toBe(paragraph);
563
+ });
564
+
565
+ test("two messages on one page control their own paragraphs", async () => {
566
+ installLayout();
567
+ const { container } = render(
568
+ <>
569
+ <DiscoveryScanStatusMessage
570
+ message={LONG_MESSAGE}
571
+ scanLabel="First scan"
572
+ />
573
+ <DiscoveryScanStatusMessage
574
+ message={PROGRESS_MESSAGE}
575
+ scanLabel="Second scan"
576
+ />
577
+ </>,
578
+ );
579
+ const paragraphs: Array<HTMLParagraphElement> = Array.from(
580
+ container.querySelectorAll("p[id]"),
581
+ );
582
+ const first: HTMLElement = screen.getByRole("button", {
583
+ name: "Show details for First scan",
584
+ });
585
+ const second: HTMLElement = screen.getByRole("button", {
586
+ name: "Show details for Second scan",
587
+ });
588
+
589
+ expect(paragraphs).toHaveLength(2);
590
+ expect(paragraphs[0]!.id).not.toBe(paragraphs[1]!.id);
591
+ expect(first).toHaveAttribute("aria-controls", paragraphs[0]!.id);
592
+ expect(second).toHaveAttribute("aria-controls", paragraphs[1]!.id);
593
+
594
+ // Opening one leaves the other alone.
595
+ await asUser(() => {
596
+ return userEvent.click(first);
597
+ });
598
+
599
+ expect(paragraphs[0]).not.toHaveClass("line-clamp-2");
600
+ expect(paragraphs[1]).toHaveClass("line-clamp-2");
601
+ expect(second).toHaveAttribute("aria-expanded", "false");
602
+ });
603
+
604
+ test("it is reachable and operable from the keyboard", async () => {
605
+ installLayout();
606
+ const { container } = renderMessage(LONG_MESSAGE);
607
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
608
+
609
+ await asUser(() => {
610
+ return userEvent.tab();
611
+ });
612
+ expect(toggle()).toHaveFocus();
613
+
614
+ await asUser(() => {
615
+ return userEvent.keyboard("{Enter}");
616
+ });
617
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
618
+ expect(paragraph).not.toHaveClass("line-clamp-2");
619
+ expect(toggle()).toHaveFocus();
620
+
621
+ await asUser(() => {
622
+ return userEvent.keyboard(" ");
623
+ });
624
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
625
+ expect(paragraph).toHaveClass("line-clamp-2");
626
+ });
627
+
628
+ test("the whole message stays readable to a screen reader while the preview is clamped", () => {
629
+ installLayout();
630
+ const { container } = renderMessage(LONG_MESSAGE);
631
+
632
+ // line-clamp hides text visually only; the paragraph still holds all of it.
633
+ expect(messageParagraph(container).textContent).toBe(LONG_MESSAGE);
634
+ expect(screen.getByText(LONG_MESSAGE)).toBeVisible();
635
+ });
636
+
637
+ test("a message with no toggle leaves no focusable control behind", async () => {
638
+ installLayout();
639
+ renderMessage(REPORTED_MESSAGE);
640
+
641
+ await asUser(() => {
642
+ return userEvent.tab();
643
+ });
644
+
645
+ expect(document.body).toHaveFocus();
646
+ expect(screen.queryAllByRole("button")).toHaveLength(0);
647
+ });
648
+ });
649
+
650
+ describe("the toggle follows the column's width", () => {
651
+ test("a message that fits on a wide column gets the toggle when the column narrows", () => {
652
+ const harness: TextClampLayout = installLayout();
653
+ renderMessage(REPORTED_MESSAGE);
654
+
655
+ expect(queryToggle()).not.toBeInTheDocument();
656
+
657
+ // A phone-width column: the same sentence now needs three lines.
658
+ harness.setCharactersPerLine(24);
659
+ harness.resize();
660
+
661
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
662
+ });
663
+
664
+ test("and loses it again when the column widens", () => {
665
+ const harness: TextClampLayout = installLayout(24);
666
+ renderMessage(REPORTED_MESSAGE);
667
+
668
+ expect(toggle()).toBeInTheDocument();
669
+
670
+ harness.setCharactersPerLine(DESKTOP_CHARACTERS_PER_LINE);
671
+ harness.resize();
672
+
673
+ expect(queryToggle()).not.toBeInTheDocument();
674
+ });
675
+
676
+ test("a message opened on a narrow column closes when widening leaves nothing to hide", async () => {
677
+ const harness: TextClampLayout = installLayout(24);
678
+ const { container } = renderMessage(REPORTED_MESSAGE);
679
+ const paragraph: HTMLParagraphElement = messageParagraph(container);
680
+
681
+ await asUser(() => {
682
+ return userEvent.click(toggle());
683
+ });
684
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
685
+
686
+ harness.setCharactersPerLine(DESKTOP_CHARACTERS_PER_LINE);
687
+ harness.resize();
688
+
689
+ expect(queryToggle()).not.toBeInTheDocument();
690
+ expect(paragraph).toHaveClass("line-clamp-2");
691
+
692
+ // Narrowing again offers the preview, not a leftover "Hide details".
693
+ harness.setCharactersPerLine(24);
694
+ harness.resize();
695
+
696
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
697
+ expect(toggle()).toHaveTextContent(/^Show details/);
698
+ });
699
+
700
+ test("an open message that still overflows after a resize stays open", async () => {
701
+ const harness: TextClampLayout = installLayout();
702
+ const { container } = renderMessage(LONG_MESSAGE);
703
+
704
+ await asUser(() => {
705
+ return userEvent.click(toggle());
706
+ });
707
+
708
+ harness.setCharactersPerLine(64);
709
+ harness.resize();
710
+
711
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
712
+ expect(messageParagraph(container)).not.toHaveClass("line-clamp-2");
713
+ });
714
+
715
+ test("it watches the message paragraph itself", () => {
716
+ const harness: TextClampLayout = installLayout();
717
+ const { container } = renderMessage(LONG_MESSAGE);
718
+
719
+ expect(harness.observedElements()).toEqual([messageParagraph(container)]);
720
+ });
721
+
722
+ test("one observer is connected at a time, however often the message is toggled", () => {
723
+ const harness: TextClampLayout = installLayout();
724
+ renderMessage(LONG_MESSAGE);
725
+
726
+ for (let click: number = 0; click < 5; click++) {
727
+ fireEvent.click(toggle());
728
+ expect(harness.connectedObserverCount()).toBe(1);
729
+ }
730
+ });
731
+
732
+ test("the observer is disconnected on unmount", () => {
733
+ const harness: TextClampLayout = installLayout();
734
+ const { unmount } = renderMessage(LONG_MESSAGE);
735
+
736
+ expect(harness.connectedObserverCount()).toBe(1);
737
+
738
+ unmount();
739
+
740
+ expect(harness.connectedObserverCount()).toBe(0);
741
+ expect(harness.observedElements()).toEqual([]);
742
+ });
743
+
744
+ test("without ResizeObserver, a window resize re-measures", () => {
745
+ const harness: TextClampLayout = installLayout(
746
+ DESKTOP_CHARACTERS_PER_LINE,
747
+ false,
748
+ );
749
+ renderMessage(REPORTED_MESSAGE);
750
+
751
+ expect(typeof window.ResizeObserver).toBe("undefined");
752
+ expect(queryToggle()).not.toBeInTheDocument();
753
+
754
+ harness.setCharactersPerLine(24);
755
+ act(() => {
756
+ window.dispatchEvent(new Event("resize"));
757
+ });
758
+
759
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
760
+
761
+ harness.setCharactersPerLine(DESKTOP_CHARACTERS_PER_LINE);
762
+ act(() => {
763
+ window.dispatchEvent(new Event("resize"));
764
+ });
765
+
766
+ expect(queryToggle()).not.toBeInTheDocument();
767
+ });
768
+
769
+ test("without ResizeObserver, the window listener is removed on unmount", () => {
770
+ installLayout(DESKTOP_CHARACTERS_PER_LINE, false);
771
+ const added: SpyInstance<typeof window.addEventListener> = jest.spyOn(
772
+ window,
773
+ "addEventListener",
774
+ );
775
+ const removed: SpyInstance<typeof window.removeEventListener> = jest.spyOn(
776
+ window,
777
+ "removeEventListener",
778
+ );
779
+
780
+ const { unmount } = renderMessage(LONG_MESSAGE);
781
+
782
+ const resizeListeners: Array<unknown> = added.mock.calls
783
+ .filter((call: Array<unknown>): boolean => {
784
+ return call[0] === "resize";
785
+ })
786
+ .map((call: Array<unknown>): unknown => {
787
+ return call[1];
788
+ });
789
+
790
+ expect(resizeListeners.length).toBeGreaterThan(0);
791
+
792
+ unmount();
793
+
794
+ for (const listener of resizeListeners) {
795
+ expect(removed).toHaveBeenCalledWith("resize", listener);
796
+ }
797
+ });
798
+ });
799
+
800
+ describe("live updates to the message", () => {
801
+ test("a short message replaced by a long one gains the toggle, collapsed", () => {
802
+ installLayout();
803
+ const { rerender } = renderMessage(REPORTED_MESSAGE);
804
+
805
+ expect(queryToggle()).not.toBeInTheDocument();
806
+
807
+ rerender(
808
+ <DiscoveryScanStatusMessage
809
+ message={LONG_MESSAGE}
810
+ scanLabel={SCAN_LABEL}
811
+ />,
812
+ );
813
+
814
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
815
+ });
816
+
817
+ test("an open message replaced by one that fits loses the toggle and its open state", async () => {
818
+ installLayout();
819
+ const { container, rerender } = renderMessage(PROGRESS_MESSAGE);
820
+
821
+ await asUser(() => {
822
+ return userEvent.click(toggle());
823
+ });
824
+
825
+ rerender(
826
+ <DiscoveryScanStatusMessage
827
+ message={REPORTED_MESSAGE}
828
+ scanLabel={SCAN_LABEL}
829
+ />,
830
+ );
831
+
832
+ expect(queryToggle()).not.toBeInTheDocument();
833
+ expect(messageParagraph(container)).toHaveClass("line-clamp-2");
834
+ expect(messageParagraph(container)).toHaveTextContent(REPORTED_MESSAGE);
835
+
836
+ // The next long message arrives as a preview, not already open.
837
+ rerender(
838
+ <DiscoveryScanStatusMessage
839
+ message={LONG_MESSAGE}
840
+ scanLabel={SCAN_LABEL}
841
+ />,
842
+ );
843
+
844
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
845
+ expect(messageParagraph(container)).toHaveClass("line-clamp-2");
846
+ });
847
+
848
+ test("a poll that brings a different long message leaves an open message open", async () => {
849
+ installLayout();
850
+ const { container, rerender } = renderMessage(PROGRESS_MESSAGE);
851
+
852
+ await asUser(() => {
853
+ return userEvent.click(toggle());
854
+ });
855
+
856
+ const nextProgress: string = PROGRESS_MESSAGE.replace(
857
+ "6,144 of 15,360",
858
+ "12,288 of 15,360",
859
+ ).replace("176 of 256", "200 of 256");
860
+
861
+ rerender(
862
+ <DiscoveryScanStatusMessage
863
+ message={nextProgress}
864
+ scanLabel={SCAN_LABEL}
865
+ />,
866
+ );
867
+
868
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
869
+ expect(messageParagraph(container)).toHaveTextContent(nextProgress);
870
+ expect(messageParagraph(container)).not.toHaveClass("line-clamp-2");
871
+ });
872
+
873
+ test("a poll that brings the same message changes nothing", async () => {
874
+ const harness: TextClampLayout = installLayout();
875
+ const { rerender } = renderMessage(LONG_MESSAGE);
876
+
877
+ await asUser(() => {
878
+ return userEvent.click(toggle());
879
+ });
880
+
881
+ for (let poll: number = 0; poll < 3; poll++) {
882
+ rerender(
883
+ <DiscoveryScanStatusMessage
884
+ message={LONG_MESSAGE}
885
+ scanLabel={SCAN_LABEL}
886
+ />,
887
+ );
888
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
889
+ expect(harness.connectedObserverCount()).toBe(1);
890
+ }
891
+ });
892
+
893
+ test("a renamed scan renames its toggle", () => {
894
+ installLayout();
895
+ const { rerender } = renderMessage(LONG_MESSAGE, "Old name");
896
+
897
+ expect(
898
+ screen.getByRole("button", { name: "Show details for Old name" }),
899
+ ).toBeInTheDocument();
900
+
901
+ rerender(
902
+ <DiscoveryScanStatusMessage
903
+ message={LONG_MESSAGE}
904
+ scanLabel="New name"
905
+ />,
906
+ );
907
+
908
+ expect(
909
+ screen.getByRole("button", { name: "Show details for New name" }),
910
+ ).toBeInTheDocument();
911
+ expect(
912
+ screen.queryByRole("button", { name: "Show details for Old name" }),
913
+ ).not.toBeInTheDocument();
914
+ });
915
+ });
916
+
917
+ describe("when the page reports no usable line height", () => {
918
+ test("an overflowing preview still offers the toggle", () => {
919
+ const harness: TextClampLayout = installLayout();
920
+ harness.setLineHeight("normal");
921
+ renderMessage(LONG_MESSAGE);
922
+
923
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
924
+ });
925
+
926
+ test("expanding keeps Hide details rather than flickering back to the preview", async () => {
927
+ const harness: TextClampLayout = installLayout();
928
+ harness.setLineHeight("normal");
929
+ const { container } = renderMessage(LONG_MESSAGE);
930
+
931
+ await asUser(() => {
932
+ return userEvent.click(toggle());
933
+ });
934
+
935
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
936
+ expect(messageParagraph(container)).not.toHaveClass("line-clamp-2");
937
+
938
+ // Nor does a later reflow, which cannot tell from an open box either.
939
+ harness.resize();
940
+
941
+ expect(toggle()).toHaveAttribute("aria-expanded", "true");
942
+
943
+ await asUser(() => {
944
+ return userEvent.click(toggle());
945
+ });
946
+
947
+ expect(toggle()).toHaveAttribute("aria-expanded", "false");
948
+ });
949
+
950
+ test("a preview that fits still offers nothing", () => {
951
+ const harness: TextClampLayout = installLayout();
952
+ harness.setLineHeight("normal");
953
+ renderMessage(REPORTED_MESSAGE);
954
+
955
+ expect(queryToggle()).not.toBeInTheDocument();
956
+ });
957
+
958
+ test("a message that starts fitting while collapsed drops the toggle", () => {
959
+ const harness: TextClampLayout = installLayout(24);
960
+ harness.setLineHeight("normal");
961
+ renderMessage(REPORTED_MESSAGE);
962
+
963
+ expect(toggle()).toBeInTheDocument();
964
+
965
+ harness.setCharactersPerLine(DESKTOP_CHARACTERS_PER_LINE);
966
+ harness.resize();
967
+
968
+ expect(queryToggle()).not.toBeInTheDocument();
969
+ });
970
+ });