@oneuptime/common 12.0.32 → 12.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1191) hide show
  1. package/Models/AnalyticsModels/RumSession.ts +117 -1
  2. package/Models/AnalyticsModels/RumSessionChunk.ts +13 -0
  3. package/Models/AnalyticsModels/Span.ts +14 -0
  4. package/Models/DatabaseModels/AlertCustomField.ts +77 -3
  5. package/Models/DatabaseModels/ApiKeyPermission.ts +3 -27
  6. package/Models/DatabaseModels/EnterpriseLicense.ts +56 -0
  7. package/Models/DatabaseModels/IncidentCustomField.ts +77 -3
  8. package/Models/DatabaseModels/Index.ts +10 -0
  9. package/Models/DatabaseModels/InventoryItemCustomField.ts +77 -3
  10. package/Models/DatabaseModels/Monitor.ts +76 -0
  11. package/Models/DatabaseModels/MonitorCustomField.ts +77 -3
  12. package/Models/DatabaseModels/NetworkAlertPolicy.ts +690 -0
  13. package/Models/DatabaseModels/NetworkDevice.ts +176 -1
  14. package/Models/DatabaseModels/NetworkSite.ts +201 -2
  15. package/Models/DatabaseModels/NetworkSiteType.ts +102 -3
  16. package/Models/DatabaseModels/NetworkSnmpCredentialProfile.ts +841 -0
  17. package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +77 -3
  18. package/Models/DatabaseModels/RumApplication.ts +6 -6
  19. package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +77 -3
  20. package/Models/DatabaseModels/StatusPageCustomField.ts +77 -3
  21. package/Models/DatabaseModels/TeamCustomField.ts +77 -3
  22. package/Models/DatabaseModels/TeamMember.ts +0 -9
  23. package/Models/DatabaseModels/TeamMemberCustomField.ts +77 -3
  24. package/Models/DatabaseModels/TeamPermission.ts +3 -24
  25. package/Models/DatabaseModels/TelemetryException.ts +119 -0
  26. package/Models/DatabaseModels/TelemetryIngestionKey.ts +260 -0
  27. package/Models/DatabaseModels/UserNotificationEmailRollupBatch.ts +334 -0
  28. package/Models/DatabaseModels/UserNotificationEmailRollupItem.ts +370 -0
  29. package/Models/DatabaseModels/UserNotificationEmailRollupSetting.ts +282 -0
  30. package/Models/DatabaseModels/UserTelegram.ts +0 -4
  31. package/Scripts/benchmark-fanin-capacity.js +181 -0
  32. package/Server/API/EnterpriseLicenseAPI.ts +391 -64
  33. package/Server/API/LlmProviderAPI.ts +137 -22
  34. package/Server/API/TelemetryAPI.ts +1284 -164
  35. package/Server/API/UserNotificationSettingAPI.ts +55 -0
  36. package/Server/API/UserTelegramAPI.ts +48 -4
  37. package/Server/EnvironmentConfig.ts +49 -2
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.ts +55 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.ts +31 -0
  40. package/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.ts +124 -0
  41. package/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.ts +132 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.ts +79 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.ts +191 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.ts +91 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.ts +67 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.ts +139 -0
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.ts +121 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  49. package/Server/Infrastructure/QueueWorker.ts +59 -23
  50. package/Server/Infrastructure/Semaphore.ts +2 -0
  51. package/Server/Middleware/BearerTokenAuthorization.ts +12 -0
  52. package/Server/Middleware/ProjectAuthorization.ts +11 -0
  53. package/Server/Middleware/SCIMAuthorization.ts +12 -0
  54. package/Server/Middleware/TelemetryIngest.ts +464 -7
  55. package/Server/Services/AIBillingService.ts +101 -27
  56. package/Server/Services/AccessTokenService.ts +1 -0
  57. package/Server/Services/AlertCustomFieldService.ts +98 -0
  58. package/Server/Services/AlertService.ts +37 -0
  59. package/Server/Services/ApiKeyPermissionService.ts +461 -29
  60. package/Server/Services/CustomFieldMappingService.ts +879 -0
  61. package/Server/Services/EnterpriseLicenseInstanceService.ts +197 -0
  62. package/Server/Services/EnterpriseLicenseService.ts +55 -0
  63. package/Server/Services/GlobalConfigService.ts +194 -0
  64. package/Server/Services/IncidentCustomFieldService.ts +98 -0
  65. package/Server/Services/IncidentService.ts +35 -0
  66. package/Server/Services/Index.ts +10 -0
  67. package/Server/Services/MetricService.ts +194 -0
  68. package/Server/Services/MonitorService.ts +175 -21
  69. package/Server/Services/MonitorStatusTimelineService.ts +150 -8
  70. package/Server/Services/MonitorTemplateService.ts +217 -1
  71. package/Server/Services/NetworkAlertPolicyEngineService.ts +2159 -0
  72. package/Server/Services/NetworkAlertPolicyService.ts +944 -0
  73. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +87 -35
  74. package/Server/Services/NetworkDeviceAutoImportRuleService.ts +60 -15
  75. package/Server/Services/NetworkDeviceService.ts +1032 -73
  76. package/Server/Services/NetworkSiteService.ts +1502 -129
  77. package/Server/Services/NetworkSiteTypeService.ts +1216 -0
  78. package/Server/Services/NetworkSnmpCredentialProfileService.ts +238 -0
  79. package/Server/Services/NotificationService.ts +118 -28
  80. package/Server/Services/ProjectService.ts +127 -48
  81. package/Server/Services/RoutineEmailSettingsService.ts +73 -0
  82. package/Server/Services/RumSessionReplayViewService.ts +104 -24
  83. package/Server/Services/ScheduledMaintenanceCustomFieldService.ts +98 -0
  84. package/Server/Services/ScheduledMaintenanceService.ts +32 -0
  85. package/Server/Services/StatusPagePrivateUserService.ts +103 -3
  86. package/Server/Services/TeamMemberService.ts +78 -0
  87. package/Server/Services/TeamPermissionService.ts +235 -3
  88. package/Server/Services/TelemetryIngestionKeyService.ts +722 -19
  89. package/Server/Services/UserNotificationEmailRollupBatchService.ts +78 -0
  90. package/Server/Services/UserNotificationEmailRollupItemService.ts +79 -0
  91. package/Server/Services/UserNotificationEmailRollupSettingService.ts +113 -0
  92. package/Server/Services/UserNotificationSettingService.ts +90 -25
  93. package/Server/Services/UserService.ts +95 -0
  94. package/Server/Services/UserTelegramService.ts +326 -5
  95. package/Server/Types/Database/QueryHelper.ts +4 -2
  96. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +14 -3
  97. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +10 -0
  98. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +10 -0
  99. package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +62 -1
  100. package/Server/Utils/AI/Toolbox/AlertTools.ts +15 -1
  101. package/Server/Utils/APIKey/AccessPermission.ts +5 -2
  102. package/Server/Utils/Billing/BillingFailureNoticeThrottle.ts +80 -0
  103. package/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.ts +54 -0
  104. package/Server/Utils/CustomField/CustomFieldMappingRegistry.ts +415 -0
  105. package/Server/Utils/CustomField/CustomFieldMappingValidator.ts +335 -0
  106. package/Server/Utils/DataSource/EgressGuard.ts +177 -8
  107. package/Server/Utils/DataSource/HttpFetch.ts +9 -2
  108. package/Server/Utils/EmailRollup/EmailRollupConstants.ts +131 -0
  109. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +925 -0
  110. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +781 -0
  111. package/Server/Utils/EmailRollup/EmailRollupWriter.ts +368 -0
  112. package/Server/Utils/FrontendEnvironment.ts +40 -0
  113. package/Server/Utils/LLM/LLMService.ts +78 -0
  114. package/Server/Utils/LogRedaction.ts +28 -0
  115. package/Server/Utils/Logger.ts +90 -1
  116. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +338 -77
  117. package/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.ts +190 -0
  118. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +9 -2
  119. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +30 -4
  120. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +61 -1
  121. package/Server/Utils/Monitor/MonitorAlert.ts +29 -6
  122. package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +14 -0
  123. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +159 -15
  124. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +255 -4
  125. package/Server/Utils/Monitor/MonitorIncident.ts +22 -6
  126. package/Server/Utils/Monitor/MonitorMetricUtil.ts +41 -0
  127. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +72 -0
  128. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +499 -19
  129. package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +66 -10
  130. package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +238 -44
  131. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +146 -39
  132. package/Server/Utils/Monitor/SeriesContextEnricher.ts +304 -0
  133. package/Server/Utils/NetworkDevice/DeviceHealthAggregation.ts +31 -18
  134. package/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.ts +213 -0
  135. package/Server/Utils/OutboundUserAgent.ts +152 -0
  136. package/Server/Utils/SSRFProtection.ts +217 -10
  137. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +239 -138
  138. package/Server/Utils/SessionReplay/SessionReplayHealthCounters.ts +305 -0
  139. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1430 -289
  140. package/Server/Utils/StartServer.ts +53 -36
  141. package/Server/Utils/TelegramVerificationToken.ts +185 -0
  142. package/Server/Utils/Telemetry/CaptureSpan.ts +32 -8
  143. package/Server/Utils/Telemetry/ErrorClassResolver.ts +120 -0
  144. package/Server/Utils/Telemetry/PinServiceName.ts +197 -0
  145. package/Server/Utils/Telemetry/SpanUtil.ts +33 -0
  146. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +38 -16
  147. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +92 -0
  148. package/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.ts +217 -0
  149. package/Server/Utils/Telemetry.ts +198 -51
  150. package/Server/Utils/VM/VMAPI.ts +1 -0
  151. package/Server/Utils/VM/VMRunner.ts +975 -95
  152. package/Server/Views/Partials/SensitiveUrlToken.ejs +140 -0
  153. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +202 -0
  154. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +134 -33
  155. package/Tests/App/Dashboard/AskAiSuggestedPrompts.test.ts +279 -0
  156. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +517 -0
  157. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +404 -0
  158. package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +26 -0
  159. package/Tests/App/Dashboard/DeviceStatusHero.test.tsx +588 -0
  160. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +486 -9
  161. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +632 -0
  162. package/Tests/App/Dashboard/MonitorRecommendationCreateProgress.test.tsx +682 -0
  163. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +4 -2
  164. package/Tests/App/Dashboard/NetworkDeviceCreateFormMonitorBinding.test.tsx +676 -0
  165. package/Tests/App/Dashboard/NetworkDeviceCreateFormPingMonitor.test.tsx +791 -0
  166. package/Tests/App/Dashboard/NetworkDeviceCreateFormProbeAndSnmp.test.tsx +833 -0
  167. package/Tests/App/Dashboard/NetworkDeviceCriteriaFilter.test.ts +161 -0
  168. package/Tests/App/Dashboard/NetworkDeviceSettingsMonitoringSection.test.tsx +458 -0
  169. package/Tests/App/Dashboard/NetworkSideMenu.test.tsx +41 -5
  170. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +399 -0
  171. package/Tests/App/Dashboard/PingMonitorProvisioning.test.ts +368 -0
  172. package/Tests/App/Dashboard/RecommendationCard.test.tsx +796 -0
  173. package/Tests/App/Dashboard/RecommendationToolbar.test.tsx +519 -0
  174. package/Tests/App/Dashboard/RecommendationsList.test.tsx +661 -0
  175. package/Tests/App/Dashboard/UseBulkCreatePingMonitors.test.tsx +899 -0
  176. package/Tests/App/Dashboard/UseBulkSnmpCredentialProfileActions.test.tsx +732 -0
  177. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +256 -0
  178. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +11 -1
  179. package/Tests/App/StatusPage/StatusPageLastUpdated.test.tsx +263 -0
  180. package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +9 -4
  181. package/Tests/App/StatusPage/StatusPageOverviewLiveAndSearch.test.tsx +753 -0
  182. package/Tests/App/StatusPage/StatusPageResourceSearchBox.test.tsx +224 -0
  183. package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +262 -0
  184. package/Tests/Models/CustomFieldMappingColumns.test.ts +166 -0
  185. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +20 -6
  186. package/Tests/Models/NetworkSiteHierarchy.test.ts +41 -4
  187. package/Tests/ResponsiveVisibility.test.ts +115 -0
  188. package/Tests/ResponsiveVisibility.ts +183 -0
  189. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +951 -7
  190. package/Tests/Server/API/LlmProviderConnectionTest.test.ts +522 -0
  191. package/Tests/Server/API/SessionReplayAPI.test.ts +1940 -216
  192. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +186 -0
  193. package/Tests/Server/API/UserTelegramAPISecurity.test.ts +231 -0
  194. package/Tests/Server/EnvironmentConfigFrontendSecurity.test.ts +234 -0
  195. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +202 -0
  196. package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +41 -0
  197. package/Tests/Server/Infrastructure/Postgres/NetworkSnmpCredentialProfilesAndAlertPoliciesMigration.test.ts +854 -0
  198. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +18 -0
  199. package/Tests/Server/Infrastructure/Postgres/SessionReplayRecordEverySessionByDefaultMigration.test.ts +169 -0
  200. package/Tests/Server/Infrastructure/Postgres/UserNotificationEmailRollupTableMigration.test.ts +535 -0
  201. package/Tests/Server/Infrastructure/QueueWorkerTimeout.test.ts +210 -0
  202. package/Tests/Server/Infrastructure/TelemetryExporterDeploymentConfig.test.ts +130 -0
  203. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -1
  204. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +1163 -0
  205. package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +5 -3
  206. package/Tests/Server/Services/AIChatPrivacyPin.test.ts +560 -0
  207. package/Tests/Server/Services/AddTelemetryIngestionKeyTypeMigration.test.ts +555 -0
  208. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +678 -0
  209. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +276 -28
  210. package/Tests/Server/Services/BillingRechargeOwnerEmailGuards.test.ts +783 -0
  211. package/Tests/Server/Services/CustomFieldDropdownOptionsColumnWidth.test.ts +431 -0
  212. package/Tests/Server/Services/CustomFieldMappingService.test.ts +850 -0
  213. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +32 -0
  214. package/Tests/Server/Services/EnterpriseLicenseInstanceServiceDeletionUsage.test.ts +707 -0
  215. package/Tests/Server/Services/EnterpriseLicenseServiceUsageAggregationLock.test.ts +329 -0
  216. package/Tests/Server/Services/GlobalConfigService.test.ts +414 -1
  217. package/Tests/Server/Services/MetricRawEntityKeyPrune.test.ts +592 -0
  218. package/Tests/Server/Services/MonitorServiceDeleteNetworkDeviceCleanup.test.ts +294 -0
  219. package/Tests/Server/Services/MonitorStatusBridgeProbePath.test.ts +802 -0
  220. package/Tests/Server/Services/MonitorStatusTimelineService.test.ts +72 -2
  221. package/Tests/Server/Services/NetworkAlertPolicyEngineHooks.test.ts +752 -0
  222. package/Tests/Server/Services/NetworkAlertPolicyEngineService.test.ts +1849 -0
  223. package/Tests/Server/Services/NetworkAlertPolicyModel.test.ts +1793 -0
  224. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +273 -5
  225. package/Tests/Server/Services/NetworkDeviceAutoImportRuleService.test.ts +97 -12
  226. package/Tests/Server/Services/NetworkDeviceAutoMonitorLifecycle.test.ts +148 -3
  227. package/Tests/Server/Services/NetworkDeviceMonitorBindingUpdateGuard.test.ts +514 -0
  228. package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +447 -58
  229. package/Tests/Server/Services/NetworkDeviceMonitoringMethodTransition.test.ts +739 -0
  230. package/Tests/Server/Services/NetworkDevicePollingTenancy.test.ts +501 -0
  231. package/Tests/Server/Services/NetworkDeviceSiteDefaultProbe.test.ts +432 -0
  232. package/Tests/Server/Services/NetworkSiteMonitoringDefaults.test.ts +498 -0
  233. package/Tests/Server/Services/NetworkSiteRollupPolicyAndMaintenance.test.ts +337 -55
  234. package/Tests/Server/Services/NetworkSiteService.test.ts +1868 -131
  235. package/Tests/Server/Services/NetworkSiteTypeService.test.ts +1109 -0
  236. package/Tests/Server/Services/NetworkSnmpCredentialProfileModel.test.ts +926 -0
  237. package/Tests/Server/Services/NetworkSnmpCredentialProfileService.test.ts +796 -0
  238. package/Tests/Server/Services/ProjectServiceNetworkSiteTypeDefaults.test.ts +214 -0
  239. package/Tests/Server/Services/RoutineEmailSettingsPostgres.test.ts +329 -0
  240. package/Tests/Server/Services/RumSessionReplayViewService.test.ts +267 -0
  241. package/Tests/Server/Services/StatusPagePrivateUserEmailChangePasswordResetExpiry.test.ts +269 -0
  242. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +9 -0
  243. package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +9 -0
  244. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +834 -0
  245. package/Tests/Server/Services/TelemetryIngestionKeyPolicyResolution.test.ts +814 -0
  246. package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +998 -0
  247. package/Tests/Server/Services/UserEmailChangePasswordResetExpiry.test.ts +472 -0
  248. package/Tests/Server/Services/UserNotificationEmailRollupModels.test.ts +569 -0
  249. package/Tests/Server/Services/UserNotificationEmailRollupSettingModel.test.ts +314 -0
  250. package/Tests/Server/Services/UserNotificationEmailRollupSettingService.test.ts +409 -0
  251. package/Tests/Server/Services/UserNotificationSettingRollupRouting.test.ts +997 -0
  252. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +110 -1
  253. package/Tests/Server/Services/UserTelegramVerificationSecurity.test.ts +688 -0
  254. package/Tests/Server/Types/Database/Permissions/DashboardAccessPermission.test.ts +321 -0
  255. package/Tests/Server/Types/Database/QueryHelperOperators.test.ts +17 -0
  256. package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +124 -0
  257. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +208 -0
  258. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +235 -0
  259. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +253 -0
  260. package/Tests/Server/Utils/AI/SRE/Insights/FixRouting.test.ts +497 -0
  261. package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +383 -0
  262. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +7 -3
  263. package/Tests/Server/Utils/AnalyticsDatabase/ClusterConfig.test.ts +315 -0
  264. package/Tests/Server/Utils/Captcha.test.ts +422 -0
  265. package/Tests/Server/Utils/CustomField/CustomFieldMappingValidator.test.ts +437 -0
  266. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +300 -11
  267. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +96 -2
  268. package/Tests/Server/Utils/Database/MigrationFailureLog.test.ts +466 -0
  269. package/Tests/Server/Utils/EmailRollup/EmailRollupBurstWindow.test.ts +83 -0
  270. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1096 -0
  271. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerClaim.test.ts +466 -0
  272. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerPreferences.test.ts +362 -0
  273. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +1428 -0
  274. package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +828 -0
  275. package/Tests/Server/Utils/EmailRollup/EmailRollupWriter.test.ts +864 -0
  276. package/Tests/Server/Utils/FrontendEnvironment.test.ts +190 -0
  277. package/Tests/Server/Utils/LogRedaction.test.ts +26 -0
  278. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +23 -9
  279. package/Tests/Server/Utils/LoggerFaultDemotion.test.ts +346 -0
  280. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +584 -0
  281. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +633 -0
  282. package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestHeaderCriteria.test.ts +236 -0
  283. package/Tests/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.test.ts +196 -0
  284. package/Tests/Server/Utils/Monitor/DatabaseMetricWrite.test.ts +367 -0
  285. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +437 -0
  286. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +100 -0
  287. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +5 -0
  288. package/Tests/Server/Utils/Monitor/MonitorTemplateUtilSeriesContext.test.ts +176 -0
  289. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +512 -31
  290. package/Tests/Server/Utils/Monitor/NetworkDeviceMetricUtil.test.ts +252 -0
  291. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +479 -42
  292. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +351 -5
  293. package/Tests/Server/Utils/Monitor/SeriesContextEnricher.test.ts +356 -0
  294. package/Tests/Server/Utils/NetworkDevice/DeviceHealthAggregation.test.ts +136 -12
  295. package/Tests/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.test.ts +306 -0
  296. package/Tests/Server/Utils/OutboundUserAgent.test.ts +266 -0
  297. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +595 -0
  298. package/Tests/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClientUserAgent.test.ts +141 -0
  299. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +198 -1
  300. package/Tests/Server/Utils/SessionReplay/SessionReplayHealthCounters.test.ts +453 -0
  301. package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1378 -0
  302. package/Tests/Server/Utils/SessionReplayOriginAllowListRefactor.test.ts +420 -0
  303. package/Tests/Server/Utils/TelegramVerificationToken.test.ts +253 -0
  304. package/Tests/Server/Utils/Telemetry/ErrorClassResolver.test.ts +298 -0
  305. package/Tests/Server/Utils/Telemetry/PinServiceName.test.ts +666 -0
  306. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriterCapacity.test.ts +458 -0
  307. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +424 -0
  308. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.test.ts +528 -0
  309. package/Tests/Server/Utils/Telemetry.test.ts +456 -66
  310. package/Tests/Server/Utils/TelemetryExporterEnvironment.test.ts +56 -0
  311. package/Tests/Server/Utils/VM/VMRunnerHostBridgeLatency.test.ts +74 -0
  312. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +100 -7
  313. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +1176 -6
  314. package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +220 -0
  315. package/Tests/Types/CustomField/CustomFieldValueMapping.test.ts +348 -0
  316. package/Tests/Types/JSONFunctions.test.ts +260 -0
  317. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +422 -53
  318. package/Tests/Types/Monitor/DatabaseMetricCatalog.test.ts +388 -0
  319. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +495 -15
  320. package/Tests/Types/Monitor/DockerSwarmAlertTemplates.test.ts +191 -45
  321. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +370 -42
  322. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +375 -20
  323. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +636 -42
  324. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +189 -0
  325. package/Tests/Types/Monitor/KubernetesTemplateGroupByKeys.test.ts +95 -14
  326. package/Tests/Types/Monitor/MonitorStepDatabaseMonitor.test.ts +243 -0
  327. package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +1 -0
  328. package/Tests/Types/Monitor/MonitorType.test.ts +1 -0
  329. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +16 -7
  330. package/Tests/Types/Monitor/NetworkDeviceAlertPack.test.ts +60 -0
  331. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +190 -21
  332. package/Tests/Types/Monitor/ProxmoxAlertTemplates.test.ts +352 -22
  333. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationAlertDebuggability.test.ts +363 -0
  334. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +93 -18
  335. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +259 -0
  336. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +476 -0
  337. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +136 -0
  338. package/Tests/Types/Monitor/SeriesContext/SeriesDebugHints.test.ts +661 -0
  339. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +507 -0
  340. package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +270 -0
  341. package/Tests/Types/Monitor/TemplateGroupByKeys.test.ts +38 -10
  342. package/Tests/Types/Monitor/Utils/RecommendationCriteriaAssertions.ts +102 -0
  343. package/Tests/Types/NetworkDevice/NetworkAlertPolicyScope.test.ts +655 -0
  344. package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +115 -26
  345. package/Tests/Types/NetworkSite/DefaultNetworkSiteType.test.ts +39 -0
  346. package/Tests/Types/NotificationSetting/NotificationEmailRollupPolicy.test.ts +299 -0
  347. package/Tests/Types/NotificationSetting/RoutineEmailEvents.test.ts +47 -0
  348. package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +608 -0
  349. package/Tests/Types/Rum/SessionReplayCustomEvents.test.ts +434 -0
  350. package/Tests/Types/Telemetry/ErrorClass.test.ts +483 -0
  351. package/Tests/Types/WebsiteRequest.test.ts +285 -3
  352. package/Tests/UI/Components/BasicRadioButtons.test.tsx +336 -0
  353. package/Tests/UI/Components/Charts/ChartBucketIdentity.test.ts +16 -3
  354. package/Tests/UI/Components/Charts/ChartTrailingBucketGap.test.ts +691 -0
  355. package/Tests/UI/Components/ComponentsModal.test.tsx +12 -4
  356. package/Tests/UI/Components/ComponentsModalUsability.test.tsx +518 -0
  357. package/Tests/UI/Components/CustomFields/CustomFieldsDetailMapping.test.tsx +324 -0
  358. package/Tests/UI/Components/CustomFields/MapFromCustomFieldInput.test.tsx +176 -0
  359. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +484 -0
  360. package/Tests/UI/Components/HeaderRightRail.test.tsx +157 -0
  361. package/Tests/UI/Components/IconDropdownItem.test.tsx +102 -0
  362. package/Tests/UI/Components/JSONTablePrototypePollution.test.tsx +117 -0
  363. package/Tests/UI/Components/KeyboardShortcutsModal.test.tsx +214 -0
  364. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +561 -0
  365. package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +302 -0
  366. package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +43 -12
  367. package/Tests/UI/Components/ProgressBar.test.tsx +38 -2
  368. package/Tests/UI/Components/ShortcutDialogGuard.test.tsx +74 -0
  369. package/Tests/UI/Components/SideMenuItem.test.tsx +226 -4
  370. package/Tests/UI/Components/SideMenuSection.test.tsx +310 -0
  371. package/Tests/UI/Components/SideOverSubmitState.test.tsx +335 -0
  372. package/Tests/UI/Components/StatusPage/ResourceGroupSectionAutoExpand.test.tsx +248 -0
  373. package/Tests/UI/Components/TableCellWrapping.test.tsx +625 -0
  374. package/Tests/UI/Components/TableLoadingStates.test.tsx +205 -0
  375. package/Tests/UI/Components/Workflow/NodePlacement.test.ts +148 -0
  376. package/Tests/UI/Components/Workflow/Workflow.test.tsx +981 -0
  377. package/Tests/UI/ConfigBrowserTelemetry.test.ts +70 -0
  378. package/Tests/UI/Rum/ChunkLoader.test.ts +1036 -2
  379. package/Tests/UI/Rum/FidelityNotices.test.ts +187 -0
  380. package/Tests/UI/Rum/InactivityMap.test.ts +341 -0
  381. package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +259 -0
  382. package/Tests/UI/Rum/RecordingHealthCard.test.tsx +855 -0
  383. package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +594 -0
  384. package/Tests/UI/Rum/ReplayCard.test.tsx +451 -0
  385. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +536 -0
  386. package/Tests/UI/Rum/ReplayEngine.test.ts +2503 -0
  387. package/Tests/UI/Rum/ReplayEngineTypes.test.ts +171 -0
  388. package/Tests/UI/Rum/ReplayHeader.test.tsx +711 -0
  389. package/Tests/UI/Rum/ReplayLink.test.tsx +119 -0
  390. package/Tests/UI/Rum/ReplayPinControl.test.tsx +408 -0
  391. package/Tests/UI/Rum/ReplayPlaybackIntent.test.ts +152 -0
  392. package/Tests/UI/Rum/ReplayRail.test.tsx +1296 -0
  393. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +900 -0
  394. package/Tests/UI/Rum/ReplayScrubber.test.tsx +763 -147
  395. package/Tests/UI/Rum/ReplaySignalTypes.test.ts +189 -0
  396. package/Tests/UI/Rum/ReplaySignals.test.ts +1579 -0
  397. package/Tests/UI/Rum/ReplayStage.test.tsx +396 -473
  398. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +918 -0
  399. package/Tests/UI/Rum/ReplayTimeline.test.tsx +696 -0
  400. package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +499 -0
  401. package/Tests/UI/Rum/SessionReplaySearchBar.test.tsx +346 -0
  402. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +545 -0
  403. package/Tests/UI/Rum/SessionReplayTable.test.tsx +991 -0
  404. package/Tests/UI/Rum/TargetedCapturePanel.test.tsx +254 -0
  405. package/Tests/UI/Telemetry/BrowserExporterIsolation.test.ts +71 -0
  406. package/Tests/UI/Utils/GlobalKeyboardShortcut.test.ts +344 -0
  407. package/Tests/UI/Utils/PageScrollLock.test.tsx +377 -0
  408. package/Tests/UI/Utils/PermissionGate.test.ts +78 -0
  409. package/Tests/UI/Utils/SensitiveUrlToken.test.ts +140 -0
  410. package/Tests/Utils/API.test.ts +308 -0
  411. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +69 -0
  412. package/Tests/Utils/EnterpriseLicenseSync.test.ts +7 -1
  413. package/Tests/Utils/EnterpriseLicenseUsage.test.ts +433 -2
  414. package/Tests/Utils/HTTPResponseBodyReader.test.ts +323 -0
  415. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +280 -1
  416. package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +1 -0
  417. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +38 -0
  418. package/Tests/Utils/NetworkDevice/DeviceHealthStateUtil.test.ts +284 -23
  419. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +177 -0
  420. package/Tests/Utils/NetworkDevice/MonitoringMethodThreadedClassifiers.test.ts +992 -0
  421. package/Tests/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.test.ts +427 -0
  422. package/Tests/Utils/NetworkDevice/ReachabilityStampConsistency.test.ts +185 -0
  423. package/Tests/Utils/NetworkDevice/SnmpCredentialUtil.test.ts +268 -0
  424. package/Tests/Utils/NetworkDiscovery/AutoImportRuleMatcher.test.ts +55 -5
  425. package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +98 -21
  426. package/Tests/Utils/NetworkDiscovery/DiscoveryImportEligibility.test.ts +98 -20
  427. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +8 -7
  428. package/Tests/Utils/NetworkDiscovery/DiscoveryScanStatus.test.ts +117 -0
  429. package/Tests/Utils/NetworkDiscovery/PingMonitorBuilderForAddress.test.ts +274 -0
  430. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +230 -51
  431. package/Tests/Utils/NetworkSite/TypeHierarchyUtil.test.ts +209 -0
  432. package/Tests/Utils/Rum/ChunkMath.test.ts +83 -0
  433. package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +993 -0
  434. package/Tests/Utils/Rum/SessionReplayStringMap.test.ts +245 -0
  435. package/Tests/Utils/StatusPage/ResourceSearch.test.ts +631 -0
  436. package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +145 -0
  437. package/Tests/Utils/Telemetry/OriginAllowList.test.ts +633 -0
  438. package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +461 -0
  439. package/Tests/Utils/ValueFormatter.test.ts +148 -0
  440. package/Types/AI/ExceptionAIClassification.ts +10 -24
  441. package/Types/CustomField/CustomFieldMappingCatalog.ts +161 -0
  442. package/Types/CustomField/CustomFieldMappingSourceResource.ts +19 -0
  443. package/Types/CustomField/CustomFieldValueMapping.ts +288 -0
  444. package/Types/Email/EmailTemplateType.ts +2 -0
  445. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +5 -0
  446. package/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.ts +13 -0
  447. package/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.ts +6 -0
  448. package/Types/Exception/ApiException.ts +10 -0
  449. package/Types/Exception/BadDataException.ts +11 -0
  450. package/Types/Exception/BadRequestException.ts +11 -0
  451. package/Types/Exception/Exception.ts +65 -0
  452. package/Types/Exception/ForbiddenException.ts +10 -0
  453. package/Types/Exception/NotAuthenticatedException.ts +10 -0
  454. package/Types/Exception/NotAuthorizedException.ts +10 -0
  455. package/Types/Exception/NotFoundException.ts +11 -0
  456. package/Types/Exception/PayloadTooLargeException.ts +11 -0
  457. package/Types/Exception/PaymentRequiredException.ts +10 -0
  458. package/Types/Exception/ServiceUnavailableException.ts +10 -0
  459. package/Types/Exception/SsoAuthorizationException.ts +10 -0
  460. package/Types/Exception/TenantNotFoundException.ts +11 -0
  461. package/Types/Exception/TimeoutException.ts +10 -0
  462. package/Types/Exception/TooManyRequestsException.ts +10 -0
  463. package/Types/Exception/UnableToReachServer.ts +10 -0
  464. package/Types/Exception/WebsiteRequestException.ts +10 -0
  465. package/Types/Icon/IconProp.ts +1 -0
  466. package/Types/JSONFunctions.ts +189 -43
  467. package/Types/Monitor/CephAlertTemplates.ts +157 -195
  468. package/Types/Monitor/CriteriaFilter.ts +60 -2
  469. package/Types/Monitor/DatabaseMetricCatalog.ts +693 -0
  470. package/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.ts +83 -0
  471. package/Types/Monitor/DockerAlertTemplates.ts +338 -128
  472. package/Types/Monitor/DockerSwarmAlertTemplates.ts +95 -112
  473. package/Types/Monitor/HostAlertTemplates.ts +302 -130
  474. package/Types/Monitor/IotAlertTemplates.ts +112 -104
  475. package/Types/Monitor/KubernetesAlertTemplates.ts +415 -224
  476. package/Types/Monitor/KubernetesMetricCatalog.ts +23 -19
  477. package/Types/Monitor/MonitorCriteriaInstance.ts +76 -0
  478. package/Types/Monitor/MonitorMetricType.ts +71 -0
  479. package/Types/Monitor/MonitorStep.ts +76 -0
  480. package/Types/Monitor/MonitorStepDatabaseMonitor.ts +171 -0
  481. package/Types/Monitor/MonitorStepSqlMonitor.ts +2 -20
  482. package/Types/Monitor/MonitorType.ts +47 -1
  483. package/Types/Monitor/PodmanAlertTemplates.ts +116 -163
  484. package/Types/Monitor/ProxmoxAlertTemplates.ts +217 -99
  485. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +98 -6
  486. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +44 -7
  487. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +383 -0
  488. package/Types/Monitor/RumAlertTemplates.ts +46 -7
  489. package/Types/Monitor/SeriesContext/SeriesDebugHints.ts +596 -0
  490. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +554 -0
  491. package/Types/Monitor/ServiceAlertTemplates.ts +98 -11
  492. package/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.ts +22 -1
  493. package/Types/Monitor/SqlConnectionConfig.ts +31 -0
  494. package/Types/Monitor/UptimeHistoryLabels.ts +56 -0
  495. package/Types/NetworkDevice/NetworkAlertPolicyScope.ts +343 -0
  496. package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +42 -22
  497. package/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.ts +30 -0
  498. package/Types/NotificationSetting/NotificationEmailRollupCategory.ts +255 -0
  499. package/Types/NotificationSetting/NotificationEmailRollupPolicy.ts +104 -0
  500. package/Types/NotificationSetting/RoutineEmailEvents.ts +30 -0
  501. package/Types/Permission.ts +90 -0
  502. package/Types/Probe/ProbeMonitorResponse.ts +16 -0
  503. package/Types/Rum/SessionReplay.ts +209 -0
  504. package/Types/Rum/SessionReplayApi.ts +722 -0
  505. package/Types/Rum/SessionReplayCaptureTrigger.ts +21 -12
  506. package/Types/Rum/SessionReplayConsentMode.ts +11 -7
  507. package/Types/Rum/SessionReplayCustomEvents.ts +549 -0
  508. package/Types/Rum/SessionReplayHealth.ts +238 -0
  509. package/Types/Telemetry/ErrorClass.ts +274 -0
  510. package/Types/Telemetry/TelemetryIngestSurface.ts +95 -0
  511. package/Types/Telemetry/TelemetryIngestionKeyPolicy.ts +75 -0
  512. package/Types/Telemetry/TelemetryIngestionKeyType.ts +29 -0
  513. package/Types/Telemetry/UnitOfWork.ts +59 -0
  514. package/Types/WebsiteRequest.ts +85 -3
  515. package/UI/Components/Charts/Area/AreaChart.tsx +13 -2
  516. package/UI/Components/Charts/Bar/BarChart.tsx +6 -2
  517. package/UI/Components/Charts/Line/LineChart.tsx +13 -2
  518. package/UI/Components/Charts/Types/XAxis/XAxis.ts +29 -0
  519. package/UI/Components/Charts/Utils/DataPoint.ts +22 -3
  520. package/UI/Components/Charts/Utils/TimeAnnotation.ts +76 -9
  521. package/UI/Components/Charts/Utils/XAxis.ts +217 -0
  522. package/UI/Components/CommandPalette/CommandPalette.tsx +2 -8
  523. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +143 -25
  524. package/UI/Components/CustomFields/MapFromCustomFieldInput.tsx +216 -0
  525. package/UI/Components/EditionLabel/EditionLabel.tsx +4 -0
  526. package/UI/Components/Graphs/DayUptimeGraph.tsx +173 -8
  527. package/UI/Components/Graphs/UptimeBarTooltip.tsx +22 -278
  528. package/UI/Components/Graphs/UptimeDaySummary.tsx +327 -0
  529. package/UI/Components/Header/Header.tsx +25 -9
  530. package/UI/Components/Header/IconDropdown/IconDropdownItem.tsx +10 -1
  531. package/UI/Components/Header/IconDropdown/IconDropdownMenu.tsx +6 -1
  532. package/UI/Components/Header/ProjectPicker/ProjectPicker.tsx +6 -1
  533. package/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.tsx +6 -1
  534. package/UI/Components/Icon/Icon.tsx +11 -0
  535. package/UI/Components/JSONTable/JSONTable.tsx +2 -2
  536. package/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.tsx +125 -0
  537. package/UI/Components/KeyboardShortcut/Screenshots/README.md +19 -0
  538. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-command-palette.png +0 -0
  539. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog-dark.png +0 -0
  540. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog.png +0 -0
  541. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-help-menu.png +0 -0
  542. package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-narrow.png +0 -0
  543. package/UI/Components/Modal/Modal.tsx +2 -8
  544. package/UI/Components/ModelTable/Column.ts +15 -0
  545. package/UI/Components/Monitor/SeriesDebugCommandsViewer.tsx +64 -0
  546. package/UI/Components/Monitor/SeriesLabelsViewer.tsx +93 -0
  547. package/UI/Components/MonitorGraphs/Uptime.tsx +14 -2
  548. package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +63 -10
  549. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +88 -0
  550. package/UI/Components/ProgressBar/ProgressBar.tsx +12 -1
  551. package/UI/Components/RadioButtons/BasicRadioButtons.tsx +37 -7
  552. package/UI/Components/SideMenu/SideMenu.tsx +123 -5
  553. package/UI/Components/SideMenu/SideMenuItem.tsx +76 -84
  554. package/UI/Components/SideMenu/SideMenuSection.tsx +55 -23
  555. package/UI/Components/SideOver/SideOver.tsx +21 -20
  556. package/UI/Components/StatusPage/ResourceGroupSection.tsx +40 -0
  557. package/UI/Components/Table/CellClassName.ts +81 -0
  558. package/UI/Components/Table/TableRow.tsx +20 -14
  559. package/UI/Components/Table/TableSkeletonRows.tsx +9 -9
  560. package/UI/Components/Table/Types/Column.ts +36 -0
  561. package/UI/Components/Tooltip/Tooltip.tsx +11 -1
  562. package/UI/Components/Workflow/ComponentsModal.tsx +60 -39
  563. package/UI/Components/Workflow/NodePlacement.ts +57 -0
  564. package/UI/Components/Workflow/Workflow.tsx +47 -18
  565. package/UI/Config.ts +15 -33
  566. package/UI/Utils/GlobalKeyboardShortcut.ts +208 -0
  567. package/UI/Utils/PageScrollLock.ts +79 -3
  568. package/UI/Utils/PermissionGate.ts +61 -0
  569. package/UI/Utils/SensitiveUrlToken.ts +112 -0
  570. package/UI/Utils/Telemetry/BrowserTelemetryConfig.ts +28 -0
  571. package/UI/Utils/Telemetry/Telemetry.ts +10 -8
  572. package/UI/Utils/TestLLMProvider.ts +11 -0
  573. package/Utils/API.ts +129 -3
  574. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +16 -0
  575. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +191 -11
  576. package/Utils/HTTPResponseBodyReader.ts +221 -0
  577. package/Utils/Monitor/MonitorMetricType.ts +153 -1
  578. package/Utils/Monitor/NetworkTopologyUtil.ts +23 -3
  579. package/Utils/NetworkDevice/DeviceHealthStateUtil.ts +65 -14
  580. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +43 -14
  581. package/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.ts +247 -0
  582. package/Utils/NetworkDevice/SnmpCredentialUtil.ts +68 -0
  583. package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +15 -7
  584. package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +52 -17
  585. package/Utils/NetworkDiscovery/DiscoveryImportEligibility.ts +55 -23
  586. package/Utils/NetworkDiscovery/DiscoveryScanStatus.ts +77 -0
  587. package/Utils/NetworkDiscovery/PingMonitorBuilder.ts +115 -13
  588. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +66 -20
  589. package/Utils/NetworkSite/TypeHierarchyUtil.ts +313 -0
  590. package/Utils/Rum/ChunkMath.ts +106 -0
  591. package/Utils/Rum/SessionReplayHealth.ts +732 -0
  592. package/Utils/Rum/SessionReplayStringMap.ts +226 -0
  593. package/Utils/Schema/ModelSchema.ts +1 -0
  594. package/Utils/StatusPage/ResourceSearch.ts +290 -0
  595. package/Utils/Telemetry/OriginAllowList.ts +355 -0
  596. package/Utils/Uptime/DayUptimeGraphUtil.ts +205 -0
  597. package/Utils/ValueFormatter.ts +35 -1
  598. package/build/dist/Models/AnalyticsModels/RumSession.js +102 -19
  599. package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -1
  600. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +11 -9
  601. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
  602. package/build/dist/Models/AnalyticsModels/Span.js +14 -0
  603. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  604. package/build/dist/Models/DatabaseModels/AlertCustomField.js +79 -3
  605. package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
  606. package/build/dist/Models/DatabaseModels/ApiKeyPermission.js +3 -27
  607. package/build/dist/Models/DatabaseModels/ApiKeyPermission.js.map +1 -1
  608. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +59 -0
  609. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  610. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +79 -3
  611. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  612. package/build/dist/Models/DatabaseModels/Index.js +10 -0
  613. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  614. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +79 -3
  615. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -1
  616. package/build/dist/Models/DatabaseModels/Monitor.js +75 -0
  617. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  618. package/build/dist/Models/DatabaseModels/MonitorCustomField.js +79 -3
  619. package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
  620. package/build/dist/Models/DatabaseModels/NetworkAlertPolicy.js +713 -0
  621. package/build/dist/Models/DatabaseModels/NetworkAlertPolicy.js.map +1 -0
  622. package/build/dist/Models/DatabaseModels/NetworkDevice.js +180 -1
  623. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  624. package/build/dist/Models/DatabaseModels/NetworkSite.js +200 -2
  625. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  626. package/build/dist/Models/DatabaseModels/NetworkSiteType.js +102 -3
  627. package/build/dist/Models/DatabaseModels/NetworkSiteType.js.map +1 -1
  628. package/build/dist/Models/DatabaseModels/NetworkSnmpCredentialProfile.js +878 -0
  629. package/build/dist/Models/DatabaseModels/NetworkSnmpCredentialProfile.js.map +1 -0
  630. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +79 -3
  631. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
  632. package/build/dist/Models/DatabaseModels/RumApplication.js +6 -6
  633. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  634. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +79 -3
  635. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
  636. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +79 -3
  637. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
  638. package/build/dist/Models/DatabaseModels/TeamCustomField.js +79 -3
  639. package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
  640. package/build/dist/Models/DatabaseModels/TeamMember.js +0 -9
  641. package/build/dist/Models/DatabaseModels/TeamMember.js.map +1 -1
  642. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +79 -3
  643. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
  644. package/build/dist/Models/DatabaseModels/TeamPermission.js +3 -24
  645. package/build/dist/Models/DatabaseModels/TeamPermission.js.map +1 -1
  646. package/build/dist/Models/DatabaseModels/TelemetryException.js +123 -0
  647. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  648. package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +267 -0
  649. package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
  650. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupBatch.js +363 -0
  651. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupBatch.js.map +1 -0
  652. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js +402 -0
  653. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js.map +1 -0
  654. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupSetting.js +296 -0
  655. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupSetting.js.map +1 -0
  656. package/build/dist/Models/DatabaseModels/UserTelegram.js +0 -4
  657. package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
  658. package/build/dist/Server/API/EnterpriseLicenseAPI.js +273 -60
  659. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  660. package/build/dist/Server/API/LlmProviderAPI.js +108 -13
  661. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  662. package/build/dist/Server/API/TelemetryAPI.js +769 -109
  663. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  664. package/build/dist/Server/API/UserNotificationSettingAPI.js +35 -0
  665. package/build/dist/Server/API/UserNotificationSettingAPI.js.map +1 -0
  666. package/build/dist/Server/API/UserTelegramAPI.js +28 -6
  667. package/build/dist/Server/API/UserTelegramAPI.js.map +1 -1
  668. package/build/dist/Server/EnvironmentConfig.js +41 -2
  669. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  670. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.js +24 -0
  671. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.js.map +1 -0
  672. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.js +16 -0
  673. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.js.map +1 -0
  674. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.js +111 -0
  675. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.js.map +1 -0
  676. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.js +77 -0
  677. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.js.map +1 -0
  678. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.js +50 -0
  679. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.js.map +1 -0
  680. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.js +83 -0
  681. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.js.map +1 -0
  682. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js +60 -0
  683. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js.map +1 -0
  684. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js +53 -0
  685. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js.map +1 -0
  686. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js +82 -0
  687. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js.map +1 -0
  688. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js +46 -0
  689. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js.map +1 -0
  690. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  691. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  692. package/build/dist/Server/Infrastructure/QueueWorker.js +52 -12
  693. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  694. package/build/dist/Server/Infrastructure/Semaphore.js +1 -0
  695. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  696. package/build/dist/Server/Middleware/BearerTokenAuthorization.js +12 -0
  697. package/build/dist/Server/Middleware/BearerTokenAuthorization.js.map +1 -1
  698. package/build/dist/Server/Middleware/ProjectAuthorization.js +11 -0
  699. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  700. package/build/dist/Server/Middleware/SCIMAuthorization.js +12 -0
  701. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +1 -1
  702. package/build/dist/Server/Middleware/TelemetryIngest.js +326 -8
  703. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  704. package/build/dist/Server/Services/AIBillingService.js +70 -16
  705. package/build/dist/Server/Services/AIBillingService.js.map +1 -1
  706. package/build/dist/Server/Services/AccessTokenService.js +1 -0
  707. package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
  708. package/build/dist/Server/Services/AlertCustomFieldService.js +93 -0
  709. package/build/dist/Server/Services/AlertCustomFieldService.js.map +1 -1
  710. package/build/dist/Server/Services/AlertService.js +34 -0
  711. package/build/dist/Server/Services/AlertService.js.map +1 -1
  712. package/build/dist/Server/Services/ApiKeyPermissionService.js +331 -28
  713. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  714. package/build/dist/Server/Services/CustomFieldMappingService.js +614 -0
  715. package/build/dist/Server/Services/CustomFieldMappingService.js.map +1 -0
  716. package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js +167 -0
  717. package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js.map +1 -1
  718. package/build/dist/Server/Services/EnterpriseLicenseService.js +39 -0
  719. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  720. package/build/dist/Server/Services/GlobalConfigService.js +133 -0
  721. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  722. package/build/dist/Server/Services/IncidentCustomFieldService.js +93 -0
  723. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  724. package/build/dist/Server/Services/IncidentService.js +32 -0
  725. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  726. package/build/dist/Server/Services/Index.js +10 -0
  727. package/build/dist/Server/Services/Index.js.map +1 -1
  728. package/build/dist/Server/Services/MetricService.js +163 -0
  729. package/build/dist/Server/Services/MetricService.js.map +1 -1
  730. package/build/dist/Server/Services/MonitorService.js +148 -21
  731. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  732. package/build/dist/Server/Services/MonitorStatusTimelineService.js +127 -8
  733. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  734. package/build/dist/Server/Services/MonitorTemplateService.js +162 -0
  735. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  736. package/build/dist/Server/Services/NetworkAlertPolicyEngineService.js +1683 -0
  737. package/build/dist/Server/Services/NetworkAlertPolicyEngineService.js.map +1 -0
  738. package/build/dist/Server/Services/NetworkAlertPolicyService.js +732 -0
  739. package/build/dist/Server/Services/NetworkAlertPolicyService.js.map +1 -0
  740. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +89 -36
  741. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  742. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +54 -11
  743. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
  744. package/build/dist/Server/Services/NetworkDeviceService.js +841 -70
  745. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  746. package/build/dist/Server/Services/NetworkSiteService.js +1145 -107
  747. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  748. package/build/dist/Server/Services/NetworkSiteTypeService.js +863 -0
  749. package/build/dist/Server/Services/NetworkSiteTypeService.js.map +1 -1
  750. package/build/dist/Server/Services/NetworkSnmpCredentialProfileService.js +217 -0
  751. package/build/dist/Server/Services/NetworkSnmpCredentialProfileService.js.map +1 -0
  752. package/build/dist/Server/Services/NotificationService.js +80 -17
  753. package/build/dist/Server/Services/NotificationService.js.map +1 -1
  754. package/build/dist/Server/Services/ProjectService.js +88 -30
  755. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  756. package/build/dist/Server/Services/RoutineEmailSettingsService.js +69 -0
  757. package/build/dist/Server/Services/RoutineEmailSettingsService.js.map +1 -0
  758. package/build/dist/Server/Services/RumSessionReplayViewService.js +86 -24
  759. package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -1
  760. package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js +93 -0
  761. package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js.map +1 -1
  762. package/build/dist/Server/Services/ScheduledMaintenanceService.js +29 -0
  763. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  764. package/build/dist/Server/Services/StatusPagePrivateUserService.js +92 -2
  765. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  766. package/build/dist/Server/Services/TeamMemberService.js +62 -2
  767. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  768. package/build/dist/Server/Services/TeamPermissionService.js +166 -3
  769. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  770. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +562 -18
  771. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  772. package/build/dist/Server/Services/UserNotificationEmailRollupBatchService.js +76 -0
  773. package/build/dist/Server/Services/UserNotificationEmailRollupBatchService.js.map +1 -0
  774. package/build/dist/Server/Services/UserNotificationEmailRollupItemService.js +77 -0
  775. package/build/dist/Server/Services/UserNotificationEmailRollupItemService.js.map +1 -0
  776. package/build/dist/Server/Services/UserNotificationEmailRollupSettingService.js +111 -0
  777. package/build/dist/Server/Services/UserNotificationEmailRollupSettingService.js.map +1 -0
  778. package/build/dist/Server/Services/UserNotificationSettingService.js +66 -20
  779. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  780. package/build/dist/Server/Services/UserService.js +90 -0
  781. package/build/dist/Server/Services/UserService.js.map +1 -1
  782. package/build/dist/Server/Services/UserTelegramService.js +257 -4
  783. package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
  784. package/build/dist/Server/Types/Database/QueryHelper.js +1 -1
  785. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  786. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +14 -3
  787. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  788. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +10 -0
  789. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  790. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +10 -0
  791. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  792. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +54 -1
  793. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -1
  794. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +18 -4
  795. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
  796. package/build/dist/Server/Utils/APIKey/AccessPermission.js +2 -2
  797. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  798. package/build/dist/Server/Utils/Billing/BillingFailureNoticeThrottle.js +59 -0
  799. package/build/dist/Server/Utils/Billing/BillingFailureNoticeThrottle.js.map +1 -0
  800. package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js +27 -0
  801. package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js.map +1 -0
  802. package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js +226 -0
  803. package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js.map +1 -0
  804. package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js +189 -0
  805. package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js.map +1 -0
  806. package/build/dist/Server/Utils/DataSource/EgressGuard.js +121 -7
  807. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  808. package/build/dist/Server/Utils/DataSource/HttpFetch.js +2 -1
  809. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
  810. package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js +124 -0
  811. package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js.map +1 -0
  812. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +733 -0
  813. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -0
  814. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +497 -0
  815. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -0
  816. package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js +257 -0
  817. package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js.map +1 -0
  818. package/build/dist/Server/Utils/FrontendEnvironment.js +33 -0
  819. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -0
  820. package/build/dist/Server/Utils/LLM/LLMService.js +64 -1
  821. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  822. package/build/dist/Server/Utils/LogRedaction.js +28 -0
  823. package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
  824. package/build/dist/Server/Utils/Logger.js +73 -1
  825. package/build/dist/Server/Utils/Logger.js.map +1 -1
  826. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +287 -68
  827. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  828. package/build/dist/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.js +141 -0
  829. package/build/dist/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.js.map +1 -0
  830. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  831. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  832. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +20 -4
  833. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  834. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +51 -1
  835. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  836. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +29 -6
  837. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  838. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +7 -0
  839. package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
  840. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +125 -13
  841. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  842. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +158 -6
  843. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  844. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  845. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  846. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
  847. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  848. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +54 -0
  849. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  850. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +330 -21
  851. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  852. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +55 -10
  853. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -1
  854. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +178 -49
  855. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -1
  856. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +119 -29
  857. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  858. package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js +237 -0
  859. package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js.map +1 -0
  860. package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js +30 -17
  861. package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js.map +1 -1
  862. package/build/dist/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.js +158 -0
  863. package/build/dist/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.js.map +1 -0
  864. package/build/dist/Server/Utils/OutboundUserAgent.js +123 -0
  865. package/build/dist/Server/Utils/OutboundUserAgent.js.map +1 -0
  866. package/build/dist/Server/Utils/SSRFProtection.js +136 -9
  867. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  868. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +174 -89
  869. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  870. package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js +223 -0
  871. package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js.map +1 -0
  872. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +945 -164
  873. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  874. package/build/dist/Server/Utils/StartServer.js +41 -28
  875. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  876. package/build/dist/Server/Utils/TelegramVerificationToken.js +129 -0
  877. package/build/dist/Server/Utils/TelegramVerificationToken.js.map +1 -0
  878. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +28 -8
  879. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
  880. package/build/dist/Server/Utils/Telemetry/ErrorClassResolver.js +95 -0
  881. package/build/dist/Server/Utils/Telemetry/ErrorClassResolver.js.map +1 -0
  882. package/build/dist/Server/Utils/Telemetry/PinServiceName.js +163 -0
  883. package/build/dist/Server/Utils/Telemetry/PinServiceName.js.map +1 -0
  884. package/build/dist/Server/Utils/Telemetry/SpanUtil.js +29 -0
  885. package/build/dist/Server/Utils/Telemetry/SpanUtil.js.map +1 -1
  886. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +28 -15
  887. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
  888. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +64 -0
  889. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -0
  890. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js +171 -0
  891. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js.map +1 -0
  892. package/build/dist/Server/Utils/Telemetry.js +162 -46
  893. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  894. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  895. package/build/dist/Server/Utils/VM/VMRunner.js +780 -69
  896. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  897. package/build/dist/Types/AI/ExceptionAIClassification.js +10 -24
  898. package/build/dist/Types/AI/ExceptionAIClassification.js.map +1 -1
  899. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +60 -0
  900. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -0
  901. package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js +20 -0
  902. package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js.map +1 -0
  903. package/build/dist/Types/CustomField/CustomFieldValueMapping.js +141 -0
  904. package/build/dist/Types/CustomField/CustomFieldValueMapping.js.map +1 -0
  905. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  906. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  907. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.js +2 -0
  908. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.js.map +1 -0
  909. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.js +7 -0
  910. package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.js.map +1 -0
  911. package/build/dist/Types/Exception/ApiException.js +9 -0
  912. package/build/dist/Types/Exception/ApiException.js.map +1 -1
  913. package/build/dist/Types/Exception/BadDataException.js +10 -0
  914. package/build/dist/Types/Exception/BadDataException.js.map +1 -1
  915. package/build/dist/Types/Exception/BadRequestException.js +10 -0
  916. package/build/dist/Types/Exception/BadRequestException.js.map +1 -1
  917. package/build/dist/Types/Exception/Exception.js +56 -0
  918. package/build/dist/Types/Exception/Exception.js.map +1 -1
  919. package/build/dist/Types/Exception/ForbiddenException.js +9 -0
  920. package/build/dist/Types/Exception/ForbiddenException.js.map +1 -1
  921. package/build/dist/Types/Exception/NotAuthenticatedException.js +9 -0
  922. package/build/dist/Types/Exception/NotAuthenticatedException.js.map +1 -1
  923. package/build/dist/Types/Exception/NotAuthorizedException.js +9 -0
  924. package/build/dist/Types/Exception/NotAuthorizedException.js.map +1 -1
  925. package/build/dist/Types/Exception/NotFoundException.js +10 -0
  926. package/build/dist/Types/Exception/NotFoundException.js.map +1 -1
  927. package/build/dist/Types/Exception/PayloadTooLargeException.js +10 -0
  928. package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -1
  929. package/build/dist/Types/Exception/PaymentRequiredException.js +9 -0
  930. package/build/dist/Types/Exception/PaymentRequiredException.js.map +1 -1
  931. package/build/dist/Types/Exception/ServiceUnavailableException.js +9 -0
  932. package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -1
  933. package/build/dist/Types/Exception/SsoAuthorizationException.js +9 -0
  934. package/build/dist/Types/Exception/SsoAuthorizationException.js.map +1 -1
  935. package/build/dist/Types/Exception/TenantNotFoundException.js +10 -0
  936. package/build/dist/Types/Exception/TenantNotFoundException.js.map +1 -1
  937. package/build/dist/Types/Exception/TimeoutException.js +9 -0
  938. package/build/dist/Types/Exception/TimeoutException.js.map +1 -1
  939. package/build/dist/Types/Exception/TooManyRequestsException.js +9 -0
  940. package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -1
  941. package/build/dist/Types/Exception/UnableToReachServer.js +9 -0
  942. package/build/dist/Types/Exception/UnableToReachServer.js.map +1 -1
  943. package/build/dist/Types/Exception/WebsiteRequestException.js +9 -0
  944. package/build/dist/Types/Exception/WebsiteRequestException.js.map +1 -1
  945. package/build/dist/Types/Icon/IconProp.js +1 -0
  946. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  947. package/build/dist/Types/JSONFunctions.js +118 -37
  948. package/build/dist/Types/JSONFunctions.js.map +1 -1
  949. package/build/dist/Types/Monitor/CephAlertTemplates.js +130 -171
  950. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  951. package/build/dist/Types/Monitor/CriteriaFilter.js +45 -2
  952. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  953. package/build/dist/Types/Monitor/DatabaseMetricCatalog.js +594 -0
  954. package/build/dist/Types/Monitor/DatabaseMetricCatalog.js.map +1 -0
  955. package/build/dist/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.js +21 -0
  956. package/build/dist/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.js.map +1 -0
  957. package/build/dist/Types/Monitor/DockerAlertTemplates.js +286 -121
  958. package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -1
  959. package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js +84 -105
  960. package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js.map +1 -1
  961. package/build/dist/Types/Monitor/HostAlertTemplates.js +250 -122
  962. package/build/dist/Types/Monitor/HostAlertTemplates.js.map +1 -1
  963. package/build/dist/Types/Monitor/IotAlertTemplates.js +81 -81
  964. package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -1
  965. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +375 -215
  966. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  967. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +19 -19
  968. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  969. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +70 -0
  970. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  971. package/build/dist/Types/Monitor/MonitorMetricType.js +61 -0
  972. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  973. package/build/dist/Types/Monitor/MonitorStep.js +54 -0
  974. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  975. package/build/dist/Types/Monitor/MonitorStepDatabaseMonitor.js +122 -0
  976. package/build/dist/Types/Monitor/MonitorStepDatabaseMonitor.js.map +1 -0
  977. package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js.map +1 -1
  978. package/build/dist/Types/Monitor/MonitorType.js +45 -1
  979. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  980. package/build/dist/Types/Monitor/PodmanAlertTemplates.js +104 -153
  981. package/build/dist/Types/Monitor/PodmanAlertTemplates.js.map +1 -1
  982. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +156 -91
  983. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  984. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +67 -6
  985. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  986. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +38 -7
  987. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  988. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +261 -0
  989. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -0
  990. package/build/dist/Types/Monitor/RumAlertTemplates.js +46 -7
  991. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  992. package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js +422 -0
  993. package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js.map +1 -0
  994. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +441 -0
  995. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -0
  996. package/build/dist/Types/Monitor/ServiceAlertTemplates.js +79 -15
  997. package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -1
  998. package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js +21 -1
  999. package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js.map +1 -1
  1000. package/build/dist/Types/Monitor/SqlConnectionConfig.js +2 -0
  1001. package/build/dist/Types/Monitor/SqlConnectionConfig.js.map +1 -0
  1002. package/build/dist/Types/Monitor/UptimeHistoryLabels.js +17 -0
  1003. package/build/dist/Types/Monitor/UptimeHistoryLabels.js.map +1 -0
  1004. package/build/dist/Types/NetworkDevice/NetworkAlertPolicyScope.js +172 -0
  1005. package/build/dist/Types/NetworkDevice/NetworkAlertPolicyScope.js.map +1 -0
  1006. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +40 -22
  1007. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -1
  1008. package/build/dist/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.js +26 -0
  1009. package/build/dist/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.js.map +1 -0
  1010. package/build/dist/Types/NotificationSetting/NotificationEmailRollupCategory.js +186 -0
  1011. package/build/dist/Types/NotificationSetting/NotificationEmailRollupCategory.js.map +1 -0
  1012. package/build/dist/Types/NotificationSetting/NotificationEmailRollupPolicy.js +87 -0
  1013. package/build/dist/Types/NotificationSetting/NotificationEmailRollupPolicy.js.map +1 -0
  1014. package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js +29 -0
  1015. package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js.map +1 -0
  1016. package/build/dist/Types/Permission.js +80 -0
  1017. package/build/dist/Types/Permission.js.map +1 -1
  1018. package/build/dist/Types/Rum/SessionReplay.js +117 -0
  1019. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  1020. package/build/dist/Types/Rum/SessionReplayApi.js +181 -0
  1021. package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -0
  1022. package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +21 -12
  1023. package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -1
  1024. package/build/dist/Types/Rum/SessionReplayConsentMode.js +11 -7
  1025. package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -1
  1026. package/build/dist/Types/Rum/SessionReplayCustomEvents.js +172 -0
  1027. package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -0
  1028. package/build/dist/Types/Rum/SessionReplayHealth.js +37 -0
  1029. package/build/dist/Types/Rum/SessionReplayHealth.js.map +1 -0
  1030. package/build/dist/Types/Telemetry/ErrorClass.js +216 -0
  1031. package/build/dist/Types/Telemetry/ErrorClass.js.map +1 -0
  1032. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +85 -0
  1033. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -0
  1034. package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js +15 -0
  1035. package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js.map +1 -0
  1036. package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js +30 -0
  1037. package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js.map +1 -0
  1038. package/build/dist/Types/Telemetry/UnitOfWork.js +59 -0
  1039. package/build/dist/Types/Telemetry/UnitOfWork.js.map +1 -0
  1040. package/build/dist/Types/WebsiteRequest.js +55 -3
  1041. package/build/dist/Types/WebsiteRequest.js.map +1 -1
  1042. package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -2
  1043. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  1044. package/build/dist/UI/Components/Charts/Bar/BarChart.js +6 -2
  1045. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  1046. package/build/dist/UI/Components/Charts/Line/LineChart.js +13 -2
  1047. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  1048. package/build/dist/UI/Components/Charts/Types/XAxis/XAxis.js.map +1 -1
  1049. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -3
  1050. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  1051. package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +40 -9
  1052. package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -1
  1053. package/build/dist/UI/Components/Charts/Utils/XAxis.js +186 -0
  1054. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  1055. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +2 -7
  1056. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  1057. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +80 -6
  1058. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  1059. package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js +118 -0
  1060. package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js.map +1 -0
  1061. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +3 -0
  1062. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  1063. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +112 -7
  1064. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  1065. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -162
  1066. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
  1067. package/build/dist/UI/Components/Graphs/UptimeDaySummary.js +181 -0
  1068. package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -0
  1069. package/build/dist/UI/Components/Header/Header.js +2 -3
  1070. package/build/dist/UI/Components/Header/Header.js.map +1 -1
  1071. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js +2 -1
  1072. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js.map +1 -1
  1073. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js +7 -1
  1074. package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js.map +1 -1
  1075. package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js +7 -1
  1076. package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js.map +1 -1
  1077. package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js +7 -1
  1078. package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js.map +1 -1
  1079. package/build/dist/UI/Components/Icon/Icon.js +5 -0
  1080. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  1081. package/build/dist/UI/Components/JSONTable/JSONTable.js +2 -2
  1082. package/build/dist/UI/Components/JSONTable/JSONTable.js.map +1 -1
  1083. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js +35 -0
  1084. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js.map +1 -0
  1085. package/build/dist/UI/Components/Modal/Modal.js +2 -7
  1086. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  1087. package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js +34 -0
  1088. package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js.map +1 -0
  1089. package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js +49 -0
  1090. package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js.map +1 -0
  1091. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +1 -1
  1092. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  1093. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +32 -6
  1094. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
  1095. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +77 -0
  1096. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  1097. package/build/dist/UI/Components/ProgressBar/ProgressBar.js +12 -1
  1098. package/build/dist/UI/Components/ProgressBar/ProgressBar.js.map +1 -1
  1099. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +6 -6
  1100. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
  1101. package/build/dist/UI/Components/SideMenu/SideMenu.js +67 -5
  1102. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  1103. package/build/dist/UI/Components/SideMenu/SideMenuItem.js +49 -53
  1104. package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
  1105. package/build/dist/UI/Components/SideMenu/SideMenuSection.js +25 -7
  1106. package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
  1107. package/build/dist/UI/Components/SideOver/SideOver.js +5 -20
  1108. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  1109. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +22 -1
  1110. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -1
  1111. package/build/dist/UI/Components/Table/CellClassName.js +55 -0
  1112. package/build/dist/UI/Components/Table/CellClassName.js.map +1 -0
  1113. package/build/dist/UI/Components/Table/TableRow.js +16 -13
  1114. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1115. package/build/dist/UI/Components/Table/TableSkeletonRows.js +9 -8
  1116. package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -1
  1117. package/build/dist/UI/Components/Tooltip/Tooltip.js +25 -1
  1118. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  1119. package/build/dist/UI/Components/Workflow/ComponentsModal.js +47 -30
  1120. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  1121. package/build/dist/UI/Components/Workflow/NodePlacement.js +37 -0
  1122. package/build/dist/UI/Components/Workflow/NodePlacement.js.map +1 -0
  1123. package/build/dist/UI/Components/Workflow/Workflow.js +35 -18
  1124. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  1125. package/build/dist/UI/Config.js +10 -20
  1126. package/build/dist/UI/Config.js.map +1 -1
  1127. package/build/dist/UI/Utils/GlobalKeyboardShortcut.js +138 -0
  1128. package/build/dist/UI/Utils/GlobalKeyboardShortcut.js.map +1 -0
  1129. package/build/dist/UI/Utils/PageScrollLock.js +31 -3
  1130. package/build/dist/UI/Utils/PageScrollLock.js.map +1 -1
  1131. package/build/dist/UI/Utils/PermissionGate.js +42 -1
  1132. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  1133. package/build/dist/UI/Utils/SensitiveUrlToken.js +97 -0
  1134. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -0
  1135. package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js +14 -0
  1136. package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js.map +1 -0
  1137. package/build/dist/UI/Utils/Telemetry/Telemetry.js +7 -5
  1138. package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
  1139. package/build/dist/UI/Utils/TestLLMProvider.js +4 -5
  1140. package/build/dist/UI/Utils/TestLLMProvider.js.map +1 -1
  1141. package/build/dist/Utils/API.js +67 -5
  1142. package/build/dist/Utils/API.js.map +1 -1
  1143. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +15 -0
  1144. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +1 -1
  1145. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +111 -9
  1146. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  1147. package/build/dist/Utils/HTTPResponseBodyReader.js +157 -0
  1148. package/build/dist/Utils/HTTPResponseBodyReader.js.map +1 -0
  1149. package/build/dist/Utils/Monitor/MonitorMetricType.js +109 -1
  1150. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  1151. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +8 -3
  1152. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  1153. package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js +27 -7
  1154. package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js.map +1 -1
  1155. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +7 -5
  1156. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
  1157. package/build/dist/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.js +180 -0
  1158. package/build/dist/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.js.map +1 -0
  1159. package/build/dist/Utils/NetworkDevice/SnmpCredentialUtil.js +12 -0
  1160. package/build/dist/Utils/NetworkDevice/SnmpCredentialUtil.js.map +1 -0
  1161. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js +15 -5
  1162. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
  1163. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +44 -15
  1164. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
  1165. package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js +52 -23
  1166. package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js.map +1 -1
  1167. package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js +69 -0
  1168. package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js.map +1 -0
  1169. package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js +85 -13
  1170. package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js.map +1 -1
  1171. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +30 -15
  1172. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  1173. package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js +191 -0
  1174. package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js.map +1 -0
  1175. package/build/dist/Utils/Rum/ChunkMath.js +76 -0
  1176. package/build/dist/Utils/Rum/ChunkMath.js.map +1 -1
  1177. package/build/dist/Utils/Rum/SessionReplayHealth.js +534 -0
  1178. package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -0
  1179. package/build/dist/Utils/Rum/SessionReplayStringMap.js +165 -0
  1180. package/build/dist/Utils/Rum/SessionReplayStringMap.js.map +1 -0
  1181. package/build/dist/Utils/Schema/ModelSchema.js +1 -0
  1182. package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
  1183. package/build/dist/Utils/StatusPage/ResourceSearch.js +200 -0
  1184. package/build/dist/Utils/StatusPage/ResourceSearch.js.map +1 -0
  1185. package/build/dist/Utils/Telemetry/OriginAllowList.js +299 -0
  1186. package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -0
  1187. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +147 -0
  1188. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -0
  1189. package/build/dist/Utils/ValueFormatter.js +32 -1
  1190. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  1191. package/package.json +2 -2
@@ -1,7 +1,12 @@
1
1
  import NetworkSiteService from "../../../Server/Services/NetworkSiteService";
2
+ import DatabaseService from "../../../Server/Services/DatabaseService";
3
+ import NetworkSiteTypeService from "../../../Server/Services/NetworkSiteTypeService";
2
4
  import NetworkSiteStatusTimelineService from "../../../Server/Services/NetworkSiteStatusTimelineService";
3
5
  import NetworkDeviceService from "../../../Server/Services/NetworkDeviceService";
4
6
  import NetworkSiteMaintenanceSuppression from "../../../Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression";
7
+ import NetworkSiteHierarchyLock, {
8
+ NETWORK_SITE_HIERARCHY_ROOT_SCOPE_ERROR_MESSAGE,
9
+ } from "../../../Server/Utils/NetworkSite/NetworkSiteHierarchyLock";
5
10
  import MonitorService from "../../../Server/Services/MonitorService";
6
11
  import MonitorStatusService from "../../../Server/Services/MonitorStatusService";
7
12
  import NetworkSite from "../../../Models/DatabaseModels/NetworkSite";
@@ -10,6 +15,8 @@ import Monitor from "../../../Models/DatabaseModels/Monitor";
10
15
  import MonitorStatus from "../../../Models/DatabaseModels/MonitorStatus";
11
16
  import NetworkSiteStatusTimeline from "../../../Models/DatabaseModels/NetworkSiteStatusTimeline";
12
17
  import MonitorType from "../../../Types/Monitor/MonitorType";
18
+ import NetworkDeviceMonitoringMethod from "../../../Types/NetworkDevice/NetworkDeviceMonitoringMethod";
19
+ import logger from "../../../Server/Utils/Logger";
13
20
  import BadDataException from "../../../Types/Exception/BadDataException";
14
21
  import ObjectID from "../../../Types/ObjectID";
15
22
  import UpdateBy from "../../../Server/Types/Database/UpdateBy";
@@ -22,7 +29,26 @@ import {
22
29
  DeviceHealthGroup,
23
30
  } from "../../../Server/Utils/NetworkDevice/DeviceHealthAggregation";
24
31
  import { AggregateColumn } from "../../../Server/Types/Database/AggregateBy";
25
- import { describe, expect, it, afterEach } from "@jest/globals";
32
+ import { describe, expect, it, afterEach, beforeEach } from "@jest/globals";
33
+
34
+ /*
35
+ * NetworkSiteService only needs the type service's findOneById boundary.
36
+ * Replacing that boundary keeps this focused unit suite from loading the
37
+ * reciprocal NetworkSiteTypeService -> NetworkSiteService graph (and all of
38
+ * its monitoring dependencies) before each isolated hierarchy scenario.
39
+ */
40
+ jest.mock("../../../Server/Services/NetworkSiteTypeService", () => {
41
+ return {
42
+ __esModule: true,
43
+ default: {
44
+ findOneById: jest.fn(),
45
+ },
46
+ };
47
+ });
48
+
49
+ afterEach(() => {
50
+ jest.clearAllMocks();
51
+ });
26
52
 
27
53
  /*
28
54
  * Contract under test - the persisted rollup engine and the hierarchy
@@ -61,6 +87,18 @@ const DEVICE_ID: ObjectID = new ObjectID(
61
87
  const OTHER_PROJECT_ID: ObjectID = new ObjectID(
62
88
  "44444444-4444-4444-8444-444444444444",
63
89
  );
90
+ const ROOT_SITE_TYPE_ID: ObjectID = new ObjectID(
91
+ "55555555-5555-4555-8555-555555555555",
92
+ );
93
+ const CHILD_SITE_TYPE_ID: ObjectID = new ObjectID(
94
+ "66666666-6666-4666-8666-666666666666",
95
+ );
96
+ const ALTERNATE_ROOT_SITE_TYPE_ID: ObjectID = new ObjectID(
97
+ "77777777-7777-4777-8777-777777777777",
98
+ );
99
+ const GRANDCHILD_SITE_TYPE_ID: ObjectID = new ObjectID(
100
+ "88888888-8888-4888-8888-888888888888",
101
+ );
64
102
 
65
103
  function fakeSite(overrides: Record<string, unknown>): NetworkSite {
66
104
  return {
@@ -71,6 +109,33 @@ function fakeSite(overrides: Record<string, unknown>): NetworkSite {
71
109
  } as unknown as NetworkSite;
72
110
  }
73
111
 
112
+ function fakeNetworkSiteType(data: {
113
+ id: ObjectID;
114
+ parentNetworkSiteTypeId?: ObjectID | undefined;
115
+ projectId?: ObjectID | undefined;
116
+ }): any {
117
+ return {
118
+ id: data.id,
119
+ _id: data.id.toString(),
120
+ projectId: data.projectId || PROJECT_ID,
121
+ parentNetworkSiteTypeId: data.parentNetworkSiteTypeId,
122
+ };
123
+ }
124
+
125
+ function mockNetworkSiteTypes(types: Array<any>): jest.SpyInstance {
126
+ const typesById: Map<string, any> = new Map(
127
+ types.map((type: any): [string, any] => {
128
+ return [type.id.toString(), type];
129
+ }),
130
+ );
131
+
132
+ return jest
133
+ .spyOn(NetworkSiteTypeService, "findOneById")
134
+ .mockImplementation((input: any) => {
135
+ return Promise.resolve(typesById.get(input.id.toString()) || null);
136
+ });
137
+ }
138
+
74
139
  function fakeStatuses(): Array<MonitorStatus> {
75
140
  return [
76
141
  {
@@ -216,9 +281,18 @@ describe("NetworkSiteService.recomputeRollupForSite", () => {
216
281
  const spies: RollupSpies = setupRollup({
217
282
  site: fakeSite({ currentMonitorStatusId: OPERATIONAL_STATUS_ID }),
218
283
  devices: [
284
+ /*
285
+ * An ordinary probe-polled device - no monitoringMethod, which parses
286
+ * to Probe - whose last poll got nothing back. Its poll outcome is
287
+ * its vote, so the site's worst-of lands on the project's offline row
288
+ * and the timeline has to roll.
289
+ */
219
290
  {
220
291
  id: DEVICE_ID,
221
- currentMonitorStatusId: OFFLINE_STATUS_ID,
292
+ isReachable: false,
293
+ lastPolledAt: new Date(Date.now() - 60 * 1000),
294
+ lastSeenAt: new Date(Date.now() - 60 * 60 * 1000),
295
+ pollingIntervalInMinutes: 5,
222
296
  },
223
297
  ] as unknown as Array<NetworkDevice>,
224
298
  });
@@ -255,9 +329,20 @@ describe("NetworkSiteService.recomputeRollupForSite", () => {
255
329
  const spies: RollupSpies = setupRollup({
256
330
  site: fakeSite({ currentMonitorStatusId: OFFLINE_STATUS_ID }),
257
331
  devices: [
332
+ /*
333
+ * The device votes offline (its poll got nothing back) and the site
334
+ * already reads offline. The device has to CONTRIBUTE a verdict for
335
+ * this to be the "unchanged" case rather than the "nothing to say"
336
+ * case - a device with no poll columns at all would be Pending, which
337
+ * takes the same branch for an entirely different reason and would
338
+ * leave this assertion passing while proving nothing.
339
+ */
258
340
  {
259
341
  id: DEVICE_ID,
260
- currentMonitorStatusId: OFFLINE_STATUS_ID,
342
+ isReachable: false,
343
+ lastPolledAt: new Date(Date.now() - 60 * 1000),
344
+ lastSeenAt: new Date(Date.now() - 60 * 60 * 1000),
345
+ pollingIntervalInMinutes: 5,
261
346
  },
262
347
  ] as unknown as Array<NetworkDevice>,
263
348
  });
@@ -285,13 +370,78 @@ describe("NetworkSiteService.recomputeRollupForSite", () => {
285
370
  expect(spies.timelineCreate).not.toHaveBeenCalled();
286
371
  });
287
372
 
288
- it("uses the SNMP fallback for devices without a stamped status", async () => {
373
+ it("a probe-polled device with no stamped status votes with its poll", async () => {
374
+ const spies: RollupSpies = setupRollup({
375
+ site: fakeSite({ currentMonitorStatusId: OPERATIONAL_STATUS_ID }),
376
+ devices: [
377
+ {
378
+ id: DEVICE_ID,
379
+ // Nothing has ever stamped it, and its last poll got nothing back.
380
+ isReachable: false,
381
+ lastPolledAt: new Date(Date.now() - 60 * 1000),
382
+ lastSeenAt: new Date(Date.now() - 60 * 60 * 1000),
383
+ pollingIntervalInMinutes: 5,
384
+ },
385
+ ] as unknown as Array<NetworkDevice>,
386
+ });
387
+
388
+ await NetworkSiteService.recomputeRollupForSite(SITE_ID);
389
+
390
+ const updateArgs: any = spies.updateColumns.mock.calls[0]![0];
391
+ expect(updateArgs.data.currentMonitorStatusId.toString()).toBe(
392
+ OFFLINE_STATUS_ID.toString(),
393
+ );
394
+ });
395
+
396
+ /*
397
+ * Health precedence, and which device the site card is describing.
398
+ *
399
+ * A stamped MonitorStatus is a device's verdict only on a MONITOR-BACKED
400
+ * device. A Network Device monitor watching a switch's SNMP walk stamps the
401
+ * device it watches, so an "interface down -> Offline" criterion stamps
402
+ * Offline on a switch that answers every ping - and letting that vote
403
+ * turned this site card and the topology node above it red while every
404
+ * device row underneath read Up. The pill, the card and the map now read
405
+ * the same rule: for a probe-polled device the poll decides.
406
+ */
407
+ it("a probe-polled device stamped Offline does not turn the site offline while it answers", async () => {
408
+ const spies: RollupSpies = setupRollup({
409
+ site: fakeSite({ currentMonitorStatusId: OFFLINE_STATUS_ID }),
410
+ devices: [
411
+ {
412
+ id: DEVICE_ID,
413
+ // The stamp its own monitor wrote for a dark interface...
414
+ currentMonitorStatusId: OFFLINE_STATUS_ID,
415
+ // ...on a switch that answered its last poll.
416
+ isReachable: true,
417
+ lastPolledAt: new Date(Date.now() - 60 * 1000),
418
+ lastSeenAt: new Date(Date.now() - 60 * 1000),
419
+ pollingIntervalInMinutes: 5,
420
+ },
421
+ ] as unknown as Array<NetworkDevice>,
422
+ });
423
+
424
+ await NetworkSiteService.recomputeRollupForSite(SITE_ID);
425
+
426
+ const updateArgs: any = spies.updateColumns.mock.calls[0]![0];
427
+ expect(updateArgs.data.currentMonitorStatusId.toString()).toBe(
428
+ OPERATIONAL_STATUS_ID.toString(),
429
+ );
430
+ });
431
+
432
+ it("the same probe-polled device DOES turn the site offline once its poll fails", async () => {
433
+ /*
434
+ * The stamp is unchanged; only the poll outcome moved, and the site
435
+ * follows it. This is the half of the rule that stops "the stamp does not
436
+ * vote" from quietly becoming "a probe-polled device can never take its
437
+ * site offline" - which would be the same bug with the colours swapped.
438
+ */
289
439
  const spies: RollupSpies = setupRollup({
290
440
  site: fakeSite({ currentMonitorStatusId: OPERATIONAL_STATUS_ID }),
291
441
  devices: [
292
442
  {
293
443
  id: DEVICE_ID,
294
- // Unmonitored, and its last poll could not reach it.
444
+ currentMonitorStatusId: OFFLINE_STATUS_ID,
295
445
  isReachable: false,
296
446
  lastPolledAt: new Date(Date.now() - 60 * 1000),
297
447
  lastSeenAt: new Date(Date.now() - 60 * 60 * 1000),
@@ -308,6 +458,32 @@ describe("NetworkSiteService.recomputeRollupForSite", () => {
308
458
  );
309
459
  });
310
460
 
461
+ it("a monitor-backed device still votes with its stamped status", async () => {
462
+ /*
463
+ * The override, and the reason the stamp is carried at all: nothing polls
464
+ * this device, so it has no poll columns whatsoever. Reachability alone
465
+ * would call it Pending and the site would keep whatever it said before;
466
+ * its bound Monitor's status is its entire verdict, and it is offline.
467
+ */
468
+ const spies: RollupSpies = setupRollup({
469
+ site: fakeSite({ currentMonitorStatusId: OPERATIONAL_STATUS_ID }),
470
+ devices: [
471
+ {
472
+ id: DEVICE_ID,
473
+ monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
474
+ currentMonitorStatusId: OFFLINE_STATUS_ID,
475
+ },
476
+ ] as unknown as Array<NetworkDevice>,
477
+ });
478
+
479
+ await NetworkSiteService.recomputeRollupForSite(SITE_ID);
480
+
481
+ const updateArgs: any = spies.updateColumns.mock.calls[0]![0];
482
+ expect(updateArgs.data.currentMonitorStatusId.toString()).toBe(
483
+ OFFLINE_STATUS_ID.toString(),
484
+ );
485
+ });
486
+
311
487
  /*
312
488
  * Issue #3220 at the site card. A probe behind on a large fleet leaves
313
489
  * every device's last successful poll well outside the old fixed
@@ -428,9 +604,13 @@ describe("NetworkSiteService.recomputeRollupForSite", () => {
428
604
  const spies: RollupSpies = setupRollup({
429
605
  site: fakeSite({ currentMonitorStatusId: OPERATIONAL_STATUS_ID }),
430
606
  devices: [
607
+ // One ordinary probe-polled device, and its last poll got nothing back.
431
608
  {
432
609
  id: DEVICE_ID,
433
- currentMonitorStatusId: OFFLINE_STATUS_ID,
610
+ isReachable: false,
611
+ lastPolledAt: new Date(Date.now() - 60 * 1000),
612
+ lastSeenAt: new Date(Date.now() - 60 * 60 * 1000),
613
+ pollingIntervalInMinutes: 5,
434
614
  },
435
615
  ] as unknown as Array<NetworkDevice>,
436
616
  maintainedSiteIds: [],
@@ -656,15 +836,23 @@ describe("NetworkSiteService.onBeforeUpdate (cycle rejection)", () => {
656
836
  });
657
837
 
658
838
  it("allows a legal move and carries the previous state forward", async () => {
659
- jest
660
- .spyOn(NetworkSiteService, "findBy")
661
- .mockResolvedValue([
662
- fakeSite({ materializedPath: `/${SITE_ID.toString()}/` }),
663
- ]);
839
+ mockNetworkSiteTypes([
840
+ fakeNetworkSiteType({
841
+ id: CHILD_SITE_TYPE_ID,
842
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
843
+ }),
844
+ ]);
845
+ jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([
846
+ fakeSite({
847
+ materializedPath: `/${SITE_ID.toString()}/`,
848
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
849
+ }),
850
+ ]);
664
851
  jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
665
852
  fakeSite({
666
853
  id: PARENT_SITE_ID,
667
854
  _id: PARENT_SITE_ID.toString(),
855
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
668
856
  }),
669
857
  );
670
858
  jest
@@ -736,15 +924,23 @@ describe("NetworkSiteService.onBeforeUpdate (cycle rejection)", () => {
736
924
  });
737
925
 
738
926
  it("carries a legal move made through the `parentSite` relation key", async () => {
739
- jest
740
- .spyOn(NetworkSiteService, "findBy")
741
- .mockResolvedValue([
742
- fakeSite({ materializedPath: `/${SITE_ID.toString()}/` }),
743
- ]);
927
+ mockNetworkSiteTypes([
928
+ fakeNetworkSiteType({
929
+ id: CHILD_SITE_TYPE_ID,
930
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
931
+ }),
932
+ ]);
933
+ jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([
934
+ fakeSite({
935
+ materializedPath: `/${SITE_ID.toString()}/`,
936
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
937
+ }),
938
+ ]);
744
939
  jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
745
940
  fakeSite({
746
941
  id: PARENT_SITE_ID,
747
942
  _id: PARENT_SITE_ID.toString(),
943
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
748
944
  }),
749
945
  );
750
946
  jest
@@ -782,6 +978,67 @@ describe("NetworkSiteService.onBeforeUpdate (cycle rejection)", () => {
782
978
  expect(result.carryForward).toBeNull();
783
979
  expect(findBySpy).not.toHaveBeenCalled();
784
980
  });
981
+
982
+ it("rejects moving a site to another project", async () => {
983
+ jest
984
+ .spyOn(NetworkSiteService, "findBy")
985
+ .mockResolvedValue([fakeSite({ projectId: PROJECT_ID })]);
986
+
987
+ await expect(
988
+ (NetworkSiteService as any).onBeforeUpdate({
989
+ query: { _id: SITE_ID.toString() },
990
+ data: { projectId: OTHER_PROJECT_ID },
991
+ props: { isRoot: true },
992
+ } as unknown as UpdateBy<NetworkSite>),
993
+ ).rejects.toThrow("cannot be moved to another project");
994
+ });
995
+
996
+ it.each([
997
+ ["materializedPath", `/${SITE_ID.toString()}/`],
998
+ ["depth", 0],
999
+ ])(
1000
+ "rejects a direct write to server-managed %s",
1001
+ async (field: string, value: string | number) => {
1002
+ const findBySpy: jest.SpyInstance = jest.spyOn(
1003
+ NetworkSiteService,
1004
+ "findBy",
1005
+ );
1006
+
1007
+ await expect(
1008
+ (NetworkSiteService as any).onBeforeUpdate({
1009
+ query: { _id: SITE_ID.toString() },
1010
+ data: { [field]: value },
1011
+ props: { isRoot: true },
1012
+ } as unknown as UpdateBy<NetworkSite>),
1013
+ ).rejects.toThrow("cannot be updated directly");
1014
+
1015
+ expect(findBySpy).not.toHaveBeenCalled();
1016
+ },
1017
+ );
1018
+
1019
+ it("treats undefined relation properties on a model instance as omitted", async () => {
1020
+ const findBySpy: jest.SpyInstance = jest.spyOn(
1021
+ NetworkSiteService,
1022
+ "findBy",
1023
+ );
1024
+
1025
+ const result: OnUpdate<NetworkSite> = await (
1026
+ NetworkSiteService as any
1027
+ ).onBeforeUpdate({
1028
+ query: { _id: SITE_ID.toString() },
1029
+ data: {
1030
+ name: "renamed",
1031
+ parentSite: undefined,
1032
+ parentSiteId: undefined,
1033
+ networkSiteType: undefined,
1034
+ networkSiteTypeId: undefined,
1035
+ },
1036
+ props: { isRoot: true },
1037
+ } as unknown as UpdateBy<NetworkSite>);
1038
+
1039
+ expect(result.carryForward).toBeNull();
1040
+ expect(findBySpy).not.toHaveBeenCalled();
1041
+ });
785
1042
  });
786
1043
 
787
1044
  /*
@@ -870,6 +1127,23 @@ describe("NetworkSiteService.onBeforeUpdate (tenant scoping)", () => {
870
1127
  expect(result.carryForward.previousItems).toHaveLength(1);
871
1128
  expect(result.carryForward.newParentPath).toBeNull();
872
1129
  });
1130
+
1131
+ it("reads the same limit and skip window that the bulk update will write", async () => {
1132
+ const findBySpy: jest.SpyInstance = jest
1133
+ .spyOn(NetworkSiteService, "findBy")
1134
+ .mockResolvedValue([]);
1135
+
1136
+ await (NetworkSiteService as any).onBeforeUpdate({
1137
+ query: {},
1138
+ data: { parentSiteId: null },
1139
+ limit: 7,
1140
+ skip: 3,
1141
+ props: { tenantId: PROJECT_ID },
1142
+ } as unknown as UpdateBy<NetworkSite>);
1143
+
1144
+ expect(findBySpy.mock.calls[0]![0].limit).toBe(7);
1145
+ expect(findBySpy.mock.calls[0]![0].skip).toBe(3);
1146
+ });
873
1147
  });
874
1148
 
875
1149
  describe("NetworkSiteService.onBeforeCreate (cross-project parent guard)", () => {
@@ -918,10 +1192,17 @@ describe("NetworkSiteService.onBeforeCreate (cross-project parent guard)", () =>
918
1192
  });
919
1193
 
920
1194
  it("carries the parent path forward for a same-project parent", async () => {
1195
+ mockNetworkSiteTypes([
1196
+ fakeNetworkSiteType({
1197
+ id: CHILD_SITE_TYPE_ID,
1198
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1199
+ }),
1200
+ ]);
921
1201
  jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
922
1202
  fakeSite({
923
1203
  id: PARENT_SITE_ID,
924
1204
  _id: PARENT_SITE_ID.toString(),
1205
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
925
1206
  }),
926
1207
  );
927
1208
  jest
@@ -932,6 +1213,7 @@ describe("NetworkSiteService.onBeforeCreate (cross-project parent guard)", () =>
932
1213
  data: {
933
1214
  projectId: PROJECT_ID,
934
1215
  parentSiteId: PARENT_SITE_ID,
1216
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
935
1217
  },
936
1218
  props: { tenantId: PROJECT_ID },
937
1219
  });
@@ -942,88 +1224,852 @@ describe("NetworkSiteService.onBeforeCreate (cross-project parent guard)", () =>
942
1224
  });
943
1225
  });
944
1226
 
945
- describe("NetworkSiteService.onUpdateSuccess (subtree rebase)", () => {
1227
+ describe("NetworkSiteService site-type hierarchy enforcement on create", () => {
946
1228
  afterEach(() => {
947
1229
  jest.restoreAllMocks();
948
1230
  });
949
1231
 
950
- it("rebases the moved site and every descendant, then refreshes both chains", async () => {
951
- const childId: ObjectID = new ObjectID(
952
- "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
953
- );
954
- const oldParentId: ObjectID = PARENT_SITE_ID;
955
- const newParentId: ObjectID = new ObjectID(
956
- "ffffffff-ffff-4fff-8fff-ffffffffffff",
957
- );
958
-
959
- const oldPath: string = `/${oldParentId.toString()}/${SITE_ID.toString()}/`;
960
- const oldChildPath: string = `${oldPath}${childId.toString()}/`;
961
-
962
- const updateColumnsSpy: jest.SpyInstance = jest
963
- .spyOn(NetworkSiteService, "updateColumnsByIdWithoutHooks")
964
- .mockResolvedValue(undefined as never);
965
- jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([
966
- // The prefix query matches the moved site itself and its child.
967
- fakeSite({ materializedPath: oldPath }),
968
- fakeSite({
969
- id: childId,
970
- _id: childId.toString(),
971
- materializedPath: oldChildPath,
972
- }),
1232
+ it("allows a root type without a parent when the type arrives as a relation", async () => {
1233
+ const typeLookup: jest.SpyInstance = mockNetworkSiteTypes([
1234
+ fakeNetworkSiteType({ id: ROOT_SITE_TYPE_ID }),
973
1235
  ]);
974
- const rollupSpy: jest.SpyInstance = jest
975
- .spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
976
- .mockResolvedValue(undefined as never);
977
1236
 
978
- const onUpdate: OnUpdate<NetworkSite> = {
979
- updateBy: {
980
- query: { _id: SITE_ID.toString() },
981
- data: { parentSiteId: newParentId },
982
- props: { isRoot: true },
983
- } as unknown as UpdateBy<NetworkSite>,
984
- carryForward: {
985
- previousItems: [
986
- fakeSite({
987
- materializedPath: oldPath,
988
- parentSiteId: oldParentId,
989
- }),
990
- ],
991
- newParentId: newParentId,
992
- newParentPath: `/${newParentId.toString()}/`,
1237
+ const result: any = await (NetworkSiteService as any).onBeforeCreate({
1238
+ data: {
1239
+ projectId: PROJECT_ID,
1240
+ networkSiteType: { _id: ROOT_SITE_TYPE_ID.toString() },
993
1241
  },
994
- };
995
-
996
- await (NetworkSiteService as any).onUpdateSuccess(onUpdate, [SITE_ID]);
997
-
998
- const newPath: string = `/${newParentId.toString()}/${SITE_ID.toString()}/`;
999
-
1000
- // Moved site rewritten...
1001
- const selfUpdate: any = updateColumnsSpy.mock.calls.find((call: any) => {
1002
- return call[0].id.toString() === SITE_ID.toString();
1242
+ props: { tenantId: PROJECT_ID },
1003
1243
  });
1004
- expect(selfUpdate[0].data.materializedPath).toBe(newPath);
1005
- expect(selfUpdate[0].data.depth).toBe(1);
1006
1244
 
1007
- // ...and the descendant rebased under the new prefix.
1008
- const childUpdate: any = updateColumnsSpy.mock.calls.find((call: any) => {
1009
- return call[0].id.toString() === childId.toString();
1010
- });
1011
- expect(childUpdate[0].data.materializedPath).toBe(
1012
- `${newPath}${childId.toString()}/`,
1245
+ expect(result.carryForward.parentPath).toBeNull();
1246
+ expect(typeLookup).toHaveBeenCalledTimes(1);
1247
+ expect(typeLookup.mock.calls[0]![0].id.toString()).toBe(
1248
+ ROOT_SITE_TYPE_ID.toString(),
1013
1249
  );
1014
- expect(childUpdate[0].data.depth).toBe(2);
1250
+ });
1015
1251
 
1016
- // Rollups: the moved site's new chain + the old parent's chain.
1017
- const rollupIds: Array<string> = rollupSpy.mock.calls.map(
1018
- (call: Array<any>) => {
1019
- return call[0].toString();
1020
- },
1252
+ it("rejects a parent for a root type", async () => {
1253
+ mockNetworkSiteTypes([fakeNetworkSiteType({ id: ROOT_SITE_TYPE_ID })]);
1254
+ jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
1255
+ fakeSite({
1256
+ id: PARENT_SITE_ID,
1257
+ _id: PARENT_SITE_ID.toString(),
1258
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1259
+ }),
1021
1260
  );
1022
- expect(rollupIds).toContain(SITE_ID.toString());
1023
- expect(rollupIds).toContain(oldParentId.toString());
1024
- });
1025
1261
 
1026
- /*
1262
+ await expect(
1263
+ (NetworkSiteService as any).onBeforeCreate({
1264
+ data: {
1265
+ projectId: PROJECT_ID,
1266
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1267
+ parentSiteId: PARENT_SITE_ID,
1268
+ },
1269
+ props: { tenantId: PROJECT_ID },
1270
+ }),
1271
+ ).rejects.toThrow(
1272
+ "A site with a root network site type cannot have a parent site.",
1273
+ );
1274
+ });
1275
+
1276
+ it("requires a parent for a type configured below another type", async () => {
1277
+ mockNetworkSiteTypes([
1278
+ fakeNetworkSiteType({
1279
+ id: CHILD_SITE_TYPE_ID,
1280
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1281
+ }),
1282
+ ]);
1283
+
1284
+ await expect(
1285
+ (NetworkSiteService as any).onBeforeCreate({
1286
+ data: {
1287
+ projectId: PROJECT_ID,
1288
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
1289
+ },
1290
+ props: { tenantId: PROJECT_ID },
1291
+ }),
1292
+ ).rejects.toThrow("This network site type requires a parent site.");
1293
+ });
1294
+
1295
+ it("accepts a direct parent with exactly the configured type through relation fields", async () => {
1296
+ mockNetworkSiteTypes([
1297
+ fakeNetworkSiteType({
1298
+ id: CHILD_SITE_TYPE_ID,
1299
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1300
+ }),
1301
+ ]);
1302
+ jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
1303
+ fakeSite({
1304
+ id: PARENT_SITE_ID,
1305
+ _id: PARENT_SITE_ID.toString(),
1306
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1307
+ }),
1308
+ );
1309
+ jest
1310
+ .spyOn(NetworkSiteService, "getMaterializedPathForSite")
1311
+ .mockResolvedValue(`/${PARENT_SITE_ID.toString()}/`);
1312
+
1313
+ const result: any = await (NetworkSiteService as any).onBeforeCreate({
1314
+ data: {
1315
+ projectId: PROJECT_ID,
1316
+ networkSiteType: { id: CHILD_SITE_TYPE_ID },
1317
+ parentSite: { _id: PARENT_SITE_ID.toString() },
1318
+ },
1319
+ props: { tenantId: PROJECT_ID },
1320
+ });
1321
+
1322
+ expect(result.carryForward.parentPath).toBe(
1323
+ `/${PARENT_SITE_ID.toString()}/`,
1324
+ );
1325
+ });
1326
+
1327
+ it("rejects a parent whose type is not the configured direct parent", async () => {
1328
+ mockNetworkSiteTypes([
1329
+ fakeNetworkSiteType({
1330
+ id: CHILD_SITE_TYPE_ID,
1331
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1332
+ }),
1333
+ ]);
1334
+ jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
1335
+ fakeSite({
1336
+ id: PARENT_SITE_ID,
1337
+ _id: PARENT_SITE_ID.toString(),
1338
+ networkSiteTypeId: ALTERNATE_ROOT_SITE_TYPE_ID,
1339
+ }),
1340
+ );
1341
+
1342
+ await expect(
1343
+ (NetworkSiteService as any).onBeforeCreate({
1344
+ data: {
1345
+ projectId: PROJECT_ID,
1346
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
1347
+ parentSiteId: PARENT_SITE_ID,
1348
+ },
1349
+ props: { tenantId: PROJECT_ID },
1350
+ }),
1351
+ ).rejects.toThrow(
1352
+ "Parent site must use the configured parent network site type.",
1353
+ );
1354
+ });
1355
+
1356
+ it("rejects a type from another project", async () => {
1357
+ mockNetworkSiteTypes([
1358
+ fakeNetworkSiteType({
1359
+ id: ROOT_SITE_TYPE_ID,
1360
+ projectId: OTHER_PROJECT_ID,
1361
+ }),
1362
+ ]);
1363
+
1364
+ await expect(
1365
+ (NetworkSiteService as any).onBeforeCreate({
1366
+ data: {
1367
+ projectId: PROJECT_ID,
1368
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1369
+ },
1370
+ props: { tenantId: PROJECT_ID },
1371
+ }),
1372
+ ).rejects.toThrow("Network site type must belong to the same project.");
1373
+ });
1374
+
1375
+ it("uses a project relation payload for cross-project validation", async () => {
1376
+ mockNetworkSiteTypes([
1377
+ fakeNetworkSiteType({
1378
+ id: ROOT_SITE_TYPE_ID,
1379
+ projectId: OTHER_PROJECT_ID,
1380
+ }),
1381
+ ]);
1382
+
1383
+ await expect(
1384
+ (NetworkSiteService as any).onBeforeCreate({
1385
+ data: {
1386
+ project: { _id: PROJECT_ID.toString() },
1387
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1388
+ },
1389
+ props: { isRoot: true },
1390
+ }),
1391
+ ).rejects.toThrow("Network site type must belong to the same project.");
1392
+ });
1393
+
1394
+ it("rejects a missing type", async () => {
1395
+ mockNetworkSiteTypes([]);
1396
+
1397
+ await expect(
1398
+ (NetworkSiteService as any).onBeforeCreate({
1399
+ data: {
1400
+ projectId: PROJECT_ID,
1401
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1402
+ },
1403
+ props: { tenantId: PROJECT_ID },
1404
+ }),
1405
+ ).rejects.toThrow("Network site type not found.");
1406
+ });
1407
+
1408
+ it("rejects conflicting scalar and relation references before doing lookups", async () => {
1409
+ const siteLookup: jest.SpyInstance = jest.spyOn(
1410
+ NetworkSiteService,
1411
+ "findOneById",
1412
+ );
1413
+ const typeLookup: jest.SpyInstance = jest.spyOn(
1414
+ NetworkSiteTypeService,
1415
+ "findOneById",
1416
+ );
1417
+
1418
+ await expect(
1419
+ (NetworkSiteService as any).onBeforeCreate({
1420
+ data: {
1421
+ projectId: PROJECT_ID,
1422
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1423
+ networkSiteType: { _id: CHILD_SITE_TYPE_ID.toString() },
1424
+ },
1425
+ props: { tenantId: PROJECT_ID },
1426
+ }),
1427
+ ).rejects.toThrow(
1428
+ "Conflicting Network Site Type references were provided.",
1429
+ );
1430
+
1431
+ expect(siteLookup).not.toHaveBeenCalled();
1432
+ expect(typeLookup).not.toHaveBeenCalled();
1433
+ });
1434
+
1435
+ it("rejects a raw parent SQL expression instead of treating it as a clear", async () => {
1436
+ const siteLookup: jest.SpyInstance = jest.spyOn(
1437
+ NetworkSiteService,
1438
+ "findOneById",
1439
+ );
1440
+
1441
+ await expect(
1442
+ (NetworkSiteService as any).onBeforeCreate({
1443
+ data: {
1444
+ projectId: PROJECT_ID,
1445
+ parentSiteId: () => {
1446
+ return "some-parent-id";
1447
+ },
1448
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1449
+ },
1450
+ props: { tenantId: PROJECT_ID },
1451
+ }),
1452
+ ).rejects.toThrow(
1453
+ "parentSiteId cannot be set to a raw SQL expression because the network site hierarchy must be validated against an actual ID.",
1454
+ );
1455
+
1456
+ expect(siteLookup).not.toHaveBeenCalled();
1457
+ });
1458
+
1459
+ it("rejects a malformed non-null site type relation", async () => {
1460
+ const typeLookup: jest.SpyInstance = jest.spyOn(
1461
+ NetworkSiteTypeService,
1462
+ "findOneById",
1463
+ );
1464
+
1465
+ await expect(
1466
+ (NetworkSiteService as any).onBeforeCreate({
1467
+ data: {
1468
+ projectId: PROJECT_ID,
1469
+ networkSiteType: { name: "not an id" },
1470
+ },
1471
+ props: { tenantId: PROJECT_ID },
1472
+ }),
1473
+ ).rejects.toThrow(
1474
+ "networkSiteType must contain a valid Network Site Type ID.",
1475
+ );
1476
+
1477
+ expect(typeLookup).not.toHaveBeenCalled();
1478
+ });
1479
+
1480
+ it("does not permit an untyped child under a parent", async () => {
1481
+ jest
1482
+ .spyOn(NetworkSiteService, "findOneById")
1483
+ .mockResolvedValue(fakeSite({ id: PARENT_SITE_ID }));
1484
+
1485
+ await expect(
1486
+ (NetworkSiteService as any).onBeforeCreate({
1487
+ data: {
1488
+ projectId: PROJECT_ID,
1489
+ parentSiteId: PARENT_SITE_ID,
1490
+ },
1491
+ props: { tenantId: PROJECT_ID },
1492
+ }),
1493
+ ).rejects.toThrow(
1494
+ "A network site with a parent must have a network site type.",
1495
+ );
1496
+ });
1497
+ });
1498
+
1499
+ describe("NetworkSiteService site-type hierarchy enforcement on update", () => {
1500
+ afterEach(() => {
1501
+ jest.restoreAllMocks();
1502
+ });
1503
+
1504
+ function makeTypeUpdate(
1505
+ data: Record<string, unknown>,
1506
+ ): UpdateBy<NetworkSite> {
1507
+ return {
1508
+ query: { _id: SITE_ID.toString() },
1509
+ data: data,
1510
+ props: { tenantId: PROJECT_ID },
1511
+ } as unknown as UpdateBy<NetworkSite>;
1512
+ }
1513
+
1514
+ it("rejects attaching a root-typed site to a parent", async () => {
1515
+ mockNetworkSiteTypes([fakeNetworkSiteType({ id: ROOT_SITE_TYPE_ID })]);
1516
+ jest
1517
+ .spyOn(NetworkSiteService, "findBy")
1518
+ .mockResolvedValue([fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID })]);
1519
+ jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
1520
+ fakeSite({
1521
+ id: PARENT_SITE_ID,
1522
+ _id: PARENT_SITE_ID.toString(),
1523
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1524
+ }),
1525
+ );
1526
+ jest
1527
+ .spyOn(NetworkSiteService, "getMaterializedPathForSite")
1528
+ .mockResolvedValue(`/${PARENT_SITE_ID.toString()}/`);
1529
+
1530
+ await expect(
1531
+ (NetworkSiteService as any).onBeforeUpdate(
1532
+ makeTypeUpdate({ parentSiteId: PARENT_SITE_ID }),
1533
+ ),
1534
+ ).rejects.toThrow(
1535
+ "A site with a root network site type cannot have a parent site.",
1536
+ );
1537
+ });
1538
+
1539
+ it("rejects detaching a site whose type requires a parent", async () => {
1540
+ mockNetworkSiteTypes([
1541
+ fakeNetworkSiteType({
1542
+ id: CHILD_SITE_TYPE_ID,
1543
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1544
+ }),
1545
+ ]);
1546
+ jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([
1547
+ fakeSite({
1548
+ parentSiteId: PARENT_SITE_ID,
1549
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
1550
+ }),
1551
+ ]);
1552
+
1553
+ await expect(
1554
+ (NetworkSiteService as any).onBeforeUpdate(
1555
+ makeTypeUpdate({ parentSite: null }),
1556
+ ),
1557
+ ).rejects.toThrow("This network site type requires a parent site.");
1558
+ });
1559
+
1560
+ it("validates a type-only update against the site's existing parent", async () => {
1561
+ mockNetworkSiteTypes([
1562
+ fakeNetworkSiteType({
1563
+ id: CHILD_SITE_TYPE_ID,
1564
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1565
+ }),
1566
+ ]);
1567
+ const findBySpy: jest.SpyInstance = jest
1568
+ .spyOn(NetworkSiteService, "findBy")
1569
+ .mockResolvedValueOnce([
1570
+ fakeSite({
1571
+ parentSiteId: PARENT_SITE_ID,
1572
+ networkSiteTypeId: ALTERNATE_ROOT_SITE_TYPE_ID,
1573
+ }),
1574
+ ])
1575
+ .mockResolvedValueOnce([]);
1576
+ jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
1577
+ fakeSite({
1578
+ id: PARENT_SITE_ID,
1579
+ _id: PARENT_SITE_ID.toString(),
1580
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1581
+ }),
1582
+ );
1583
+
1584
+ const result: OnUpdate<NetworkSite> = await (
1585
+ NetworkSiteService as any
1586
+ ).onBeforeUpdate(makeTypeUpdate({ networkSiteTypeId: CHILD_SITE_TYPE_ID }));
1587
+
1588
+ expect(result.carryForward).toBeNull();
1589
+ expect(findBySpy).toHaveBeenCalledTimes(2);
1590
+ });
1591
+
1592
+ it("uses both proposed values when type and parent change together as relations", async () => {
1593
+ mockNetworkSiteTypes([
1594
+ fakeNetworkSiteType({
1595
+ id: CHILD_SITE_TYPE_ID,
1596
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1597
+ }),
1598
+ ]);
1599
+ jest
1600
+ .spyOn(NetworkSiteService, "findBy")
1601
+ .mockResolvedValueOnce([
1602
+ fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID }),
1603
+ ])
1604
+ .mockResolvedValueOnce([]);
1605
+ jest.spyOn(NetworkSiteService, "findOneById").mockResolvedValue(
1606
+ fakeSite({
1607
+ id: PARENT_SITE_ID,
1608
+ _id: PARENT_SITE_ID.toString(),
1609
+ networkSiteTypeId: ROOT_SITE_TYPE_ID,
1610
+ }),
1611
+ );
1612
+ jest
1613
+ .spyOn(NetworkSiteService, "getMaterializedPathForSite")
1614
+ .mockResolvedValue(`/${PARENT_SITE_ID.toString()}/`);
1615
+
1616
+ const result: any = await (NetworkSiteService as any).onBeforeUpdate(
1617
+ makeTypeUpdate({
1618
+ networkSiteType: { _id: CHILD_SITE_TYPE_ID.toString() },
1619
+ parentSite: { id: PARENT_SITE_ID },
1620
+ }),
1621
+ );
1622
+
1623
+ expect(result.carryForward.newParentId.toString()).toBe(
1624
+ PARENT_SITE_ID.toString(),
1625
+ );
1626
+ });
1627
+
1628
+ it("rejects a type change that would invalidate a direct child", async () => {
1629
+ mockNetworkSiteTypes([
1630
+ fakeNetworkSiteType({ id: ALTERNATE_ROOT_SITE_TYPE_ID }),
1631
+ fakeNetworkSiteType({
1632
+ id: CHILD_SITE_TYPE_ID,
1633
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1634
+ }),
1635
+ ]);
1636
+ const childId: ObjectID = new ObjectID(
1637
+ "99999999-9999-4999-8999-999999999999",
1638
+ );
1639
+ jest
1640
+ .spyOn(NetworkSiteService, "findBy")
1641
+ .mockResolvedValueOnce([
1642
+ fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID }),
1643
+ ])
1644
+ .mockResolvedValueOnce([
1645
+ fakeSite({
1646
+ id: childId,
1647
+ _id: childId.toString(),
1648
+ parentSiteId: SITE_ID,
1649
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
1650
+ }),
1651
+ ]);
1652
+
1653
+ await expect(
1654
+ (NetworkSiteService as any).onBeforeUpdate(
1655
+ makeTypeUpdate({
1656
+ networkSiteTypeId: ALTERNATE_ROOT_SITE_TYPE_ID,
1657
+ }),
1658
+ ),
1659
+ ).rejects.toThrow(
1660
+ "Parent site must use the configured parent network site type.",
1661
+ );
1662
+ });
1663
+
1664
+ it("allows a type change when every direct child expects the new type", async () => {
1665
+ mockNetworkSiteTypes([
1666
+ fakeNetworkSiteType({ id: ALTERNATE_ROOT_SITE_TYPE_ID }),
1667
+ fakeNetworkSiteType({
1668
+ id: GRANDCHILD_SITE_TYPE_ID,
1669
+ parentNetworkSiteTypeId: ALTERNATE_ROOT_SITE_TYPE_ID,
1670
+ }),
1671
+ ]);
1672
+ const childId: ObjectID = new ObjectID(
1673
+ "99999999-9999-4999-8999-999999999999",
1674
+ );
1675
+ const findBySpy: jest.SpyInstance = jest
1676
+ .spyOn(NetworkSiteService, "findBy")
1677
+ .mockResolvedValueOnce([
1678
+ fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID }),
1679
+ ])
1680
+ .mockResolvedValueOnce([
1681
+ fakeSite({
1682
+ id: childId,
1683
+ _id: childId.toString(),
1684
+ parentSiteId: SITE_ID,
1685
+ networkSiteTypeId: GRANDCHILD_SITE_TYPE_ID,
1686
+ }),
1687
+ ]);
1688
+
1689
+ await expect(
1690
+ (NetworkSiteService as any).onBeforeUpdate(
1691
+ makeTypeUpdate({
1692
+ networkSiteType: {
1693
+ _id: ALTERNATE_ROOT_SITE_TYPE_ID.toString(),
1694
+ },
1695
+ }),
1696
+ ),
1697
+ ).resolves.toMatchObject({ carryForward: null });
1698
+
1699
+ const childQuery: any = findBySpy.mock.calls[1]![0].query;
1700
+ expect(childQuery.projectId.toString()).toBe(PROJECT_ID.toString());
1701
+ expect(childQuery.parentSiteId.toString()).toBe(SITE_ID.toString());
1702
+ });
1703
+
1704
+ it("pages past a full batch so wide sites cannot hide invalid direct children", async () => {
1705
+ mockNetworkSiteTypes([
1706
+ fakeNetworkSiteType({ id: ALTERNATE_ROOT_SITE_TYPE_ID }),
1707
+ fakeNetworkSiteType({
1708
+ id: GRANDCHILD_SITE_TYPE_ID,
1709
+ parentNetworkSiteTypeId: ALTERNATE_ROOT_SITE_TYPE_ID,
1710
+ }),
1711
+ fakeNetworkSiteType({
1712
+ id: CHILD_SITE_TYPE_ID,
1713
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1714
+ }),
1715
+ ]);
1716
+ const child: NetworkSite = fakeSite({
1717
+ id: new ObjectID("99999999-9999-4999-8999-999999999999"),
1718
+ networkSiteTypeId: GRANDCHILD_SITE_TYPE_ID,
1719
+ });
1720
+ const childReadSkips: Array<number> = [];
1721
+ const invalidChildOnSecondPage: NetworkSite = fakeSite({
1722
+ id: new ObjectID("aaaaaaaa-1111-4111-8111-111111111111"),
1723
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
1724
+ });
1725
+ const findBySpy: jest.SpyInstance = jest
1726
+ .spyOn(NetworkSiteService, "findBy")
1727
+ .mockImplementation((input: any) => {
1728
+ if (input.query._id) {
1729
+ return Promise.resolve([
1730
+ fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID }),
1731
+ ]);
1732
+ }
1733
+
1734
+ childReadSkips.push(input.skip);
1735
+ if (input.skip === 0) {
1736
+ return Promise.resolve(
1737
+ Array.from({ length: input.limit }, () => {
1738
+ return child;
1739
+ }),
1740
+ );
1741
+ }
1742
+
1743
+ return Promise.resolve([invalidChildOnSecondPage]);
1744
+ });
1745
+
1746
+ await expect(
1747
+ (NetworkSiteService as any).onBeforeUpdate(
1748
+ makeTypeUpdate({
1749
+ networkSiteTypeId: ALTERNATE_ROOT_SITE_TYPE_ID,
1750
+ }),
1751
+ ),
1752
+ ).rejects.toThrow(
1753
+ "Parent site must use the configured parent network site type.",
1754
+ );
1755
+
1756
+ expect(findBySpy).toHaveBeenCalledTimes(3);
1757
+ expect(childReadSkips).toEqual([0, 1000]);
1758
+ });
1759
+
1760
+ it("rejects clearing a site's type while it still has direct children", async () => {
1761
+ mockNetworkSiteTypes([
1762
+ fakeNetworkSiteType({
1763
+ id: CHILD_SITE_TYPE_ID,
1764
+ parentNetworkSiteTypeId: ROOT_SITE_TYPE_ID,
1765
+ }),
1766
+ ]);
1767
+ const childId: ObjectID = new ObjectID(
1768
+ "99999999-9999-4999-8999-999999999999",
1769
+ );
1770
+ jest
1771
+ .spyOn(NetworkSiteService, "findBy")
1772
+ .mockResolvedValueOnce([
1773
+ fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID }),
1774
+ ])
1775
+ .mockResolvedValueOnce([
1776
+ fakeSite({
1777
+ id: childId,
1778
+ _id: childId.toString(),
1779
+ networkSiteTypeId: CHILD_SITE_TYPE_ID,
1780
+ }),
1781
+ ]);
1782
+
1783
+ await expect(
1784
+ (NetworkSiteService as any).onBeforeUpdate(
1785
+ makeTypeUpdate({ networkSiteTypeId: null }),
1786
+ ),
1787
+ ).rejects.toThrow(
1788
+ "Parent site must use the configured parent network site type.",
1789
+ );
1790
+ });
1791
+
1792
+ it("rejects a type-only update to a foreign-project type", async () => {
1793
+ mockNetworkSiteTypes([
1794
+ fakeNetworkSiteType({
1795
+ id: ALTERNATE_ROOT_SITE_TYPE_ID,
1796
+ projectId: OTHER_PROJECT_ID,
1797
+ }),
1798
+ ]);
1799
+ jest
1800
+ .spyOn(NetworkSiteService, "findBy")
1801
+ .mockResolvedValue([fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID })]);
1802
+
1803
+ await expect(
1804
+ (NetworkSiteService as any).onBeforeUpdate(
1805
+ makeTypeUpdate({
1806
+ networkSiteTypeId: ALTERNATE_ROOT_SITE_TYPE_ID,
1807
+ }),
1808
+ ),
1809
+ ).rejects.toThrow("Network site type must belong to the same project.");
1810
+ });
1811
+
1812
+ it("rejects conflicting parent spellings before hierarchy reads", async () => {
1813
+ const findBySpy: jest.SpyInstance = jest.spyOn(
1814
+ NetworkSiteService,
1815
+ "findBy",
1816
+ );
1817
+
1818
+ await expect(
1819
+ (NetworkSiteService as any).onBeforeUpdate(
1820
+ makeTypeUpdate({
1821
+ parentSiteId: PARENT_SITE_ID,
1822
+ parentSite: { _id: SITE_ID.toString() },
1823
+ }),
1824
+ ),
1825
+ ).rejects.toThrow("Conflicting Parent Site references were provided.");
1826
+
1827
+ expect(findBySpy).not.toHaveBeenCalled();
1828
+ });
1829
+
1830
+ it("rejects a raw site-type SQL expression before hierarchy reads", async () => {
1831
+ const findBySpy: jest.SpyInstance = jest.spyOn(
1832
+ NetworkSiteService,
1833
+ "findBy",
1834
+ );
1835
+
1836
+ await expect(
1837
+ (NetworkSiteService as any).onBeforeUpdate(
1838
+ makeTypeUpdate({
1839
+ networkSiteTypeId: () => {
1840
+ return "some-type-id";
1841
+ },
1842
+ }),
1843
+ ),
1844
+ ).rejects.toThrow(
1845
+ "networkSiteTypeId cannot be set to a raw SQL expression because the network site hierarchy must be validated against an actual ID.",
1846
+ );
1847
+
1848
+ expect(findBySpy).not.toHaveBeenCalled();
1849
+ });
1850
+
1851
+ it("rejects a malformed non-null parent relation before hierarchy reads", async () => {
1852
+ const findBySpy: jest.SpyInstance = jest.spyOn(
1853
+ NetworkSiteService,
1854
+ "findBy",
1855
+ );
1856
+
1857
+ await expect(
1858
+ (NetworkSiteService as any).onBeforeUpdate(
1859
+ makeTypeUpdate({ parentSite: { name: "not an id" } }),
1860
+ ),
1861
+ ).rejects.toThrow("parentSite must contain a valid Parent Site ID.");
1862
+
1863
+ expect(findBySpy).not.toHaveBeenCalled();
1864
+ });
1865
+
1866
+ it("does not re-query direct children for a no-op type write", async () => {
1867
+ mockNetworkSiteTypes([fakeNetworkSiteType({ id: ROOT_SITE_TYPE_ID })]);
1868
+ const findBySpy: jest.SpyInstance = jest
1869
+ .spyOn(NetworkSiteService, "findBy")
1870
+ .mockResolvedValue([fakeSite({ networkSiteTypeId: ROOT_SITE_TYPE_ID })]);
1871
+
1872
+ await (NetworkSiteService as any).onBeforeUpdate(
1873
+ makeTypeUpdate({ networkSiteTypeId: ROOT_SITE_TYPE_ID }),
1874
+ );
1875
+
1876
+ expect(findBySpy).toHaveBeenCalledTimes(1);
1877
+ });
1878
+ });
1879
+
1880
+ describe("NetworkSiteService.onUpdateSuccess (subtree rebase)", () => {
1881
+ afterEach(() => {
1882
+ jest.restoreAllMocks();
1883
+ });
1884
+
1885
+ it("rebases the moved site and every descendant, then refreshes both chains", async () => {
1886
+ const childId: ObjectID = new ObjectID(
1887
+ "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
1888
+ );
1889
+ const oldParentId: ObjectID = PARENT_SITE_ID;
1890
+ const newParentId: ObjectID = new ObjectID(
1891
+ "ffffffff-ffff-4fff-8fff-ffffffffffff",
1892
+ );
1893
+
1894
+ const oldPath: string = `/${oldParentId.toString()}/${SITE_ID.toString()}/`;
1895
+ const oldChildPath: string = `${oldPath}${childId.toString()}/`;
1896
+
1897
+ const updateColumnsSpy: jest.SpyInstance = jest
1898
+ .spyOn(NetworkSiteService, "updateColumnsByIdWithoutHooks")
1899
+ .mockResolvedValue(undefined as never);
1900
+ jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([
1901
+ // The prefix query matches the moved site itself and its child.
1902
+ fakeSite({ materializedPath: oldPath }),
1903
+ fakeSite({
1904
+ id: childId,
1905
+ _id: childId.toString(),
1906
+ materializedPath: oldChildPath,
1907
+ }),
1908
+ ]);
1909
+ const rollupSpy: jest.SpyInstance = jest
1910
+ .spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
1911
+ .mockResolvedValue(undefined as never);
1912
+
1913
+ const onUpdate: OnUpdate<NetworkSite> = {
1914
+ updateBy: {
1915
+ query: { _id: SITE_ID.toString() },
1916
+ data: { parentSiteId: newParentId },
1917
+ props: { isRoot: true },
1918
+ } as unknown as UpdateBy<NetworkSite>,
1919
+ carryForward: {
1920
+ previousItems: [
1921
+ fakeSite({
1922
+ materializedPath: oldPath,
1923
+ parentSiteId: oldParentId,
1924
+ }),
1925
+ ],
1926
+ newParentId: newParentId,
1927
+ newParentPath: `/${newParentId.toString()}/`,
1928
+ },
1929
+ };
1930
+
1931
+ await (NetworkSiteService as any).onUpdateSuccess(onUpdate, [SITE_ID]);
1932
+
1933
+ const newPath: string = `/${newParentId.toString()}/${SITE_ID.toString()}/`;
1934
+
1935
+ // Moved site rewritten...
1936
+ const selfUpdate: any = updateColumnsSpy.mock.calls.find((call: any) => {
1937
+ return call[0].id.toString() === SITE_ID.toString();
1938
+ });
1939
+ expect(selfUpdate[0].data.materializedPath).toBe(newPath);
1940
+ expect(selfUpdate[0].data.depth).toBe(1);
1941
+
1942
+ // ...and the descendant rebased under the new prefix.
1943
+ const childUpdate: any = updateColumnsSpy.mock.calls.find((call: any) => {
1944
+ return call[0].id.toString() === childId.toString();
1945
+ });
1946
+ expect(childUpdate[0].data.materializedPath).toBe(
1947
+ `${newPath}${childId.toString()}/`,
1948
+ );
1949
+ expect(childUpdate[0].data.depth).toBe(2);
1950
+
1951
+ // Rollups: the moved site's new chain + the old parent's chain.
1952
+ const rollupIds: Array<string> = rollupSpy.mock.calls.map(
1953
+ (call: Array<any>) => {
1954
+ return call[0].toString();
1955
+ },
1956
+ );
1957
+ expect(rollupIds).toContain(SITE_ID.toString());
1958
+ expect(rollupIds).toContain(oldParentId.toString());
1959
+ });
1960
+
1961
+ it("pages from the old prefix until a subtree larger than one batch is fully rebased", async () => {
1962
+ const newParentId: ObjectID = new ObjectID(
1963
+ "ffffffff-ffff-4fff-8fff-ffffffffffff",
1964
+ );
1965
+ const oldPath: string = `/${PARENT_SITE_ID.toString()}/${SITE_ID.toString()}/`;
1966
+ const firstBatch: Array<NetworkSite> = Array.from(
1967
+ { length: 1000 },
1968
+ (): NetworkSite => {
1969
+ const id: ObjectID = ObjectID.generate();
1970
+ return fakeSite({
1971
+ id,
1972
+ _id: id.toString(),
1973
+ materializedPath: `${oldPath}${id.toString()}/`,
1974
+ });
1975
+ },
1976
+ );
1977
+ const finalId: ObjectID = ObjectID.generate();
1978
+ const finalSite: NetworkSite = fakeSite({
1979
+ id: finalId,
1980
+ _id: finalId.toString(),
1981
+ materializedPath: `${oldPath}${finalId.toString()}/`,
1982
+ });
1983
+ const findBySpy: jest.SpyInstance = jest
1984
+ .spyOn(NetworkSiteService, "findBy")
1985
+ .mockResolvedValueOnce(firstBatch)
1986
+ .mockResolvedValueOnce([finalSite]);
1987
+ const updateColumnsSpy: jest.SpyInstance = jest
1988
+ .spyOn(NetworkSiteService, "updateColumnsByIdWithoutHooks")
1989
+ .mockResolvedValue(undefined as never);
1990
+ jest
1991
+ .spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
1992
+ .mockResolvedValue(undefined as never);
1993
+
1994
+ await (NetworkSiteService as any).onUpdateSuccess(
1995
+ {
1996
+ updateBy: {
1997
+ query: { _id: SITE_ID.toString() },
1998
+ data: { parentSiteId: newParentId },
1999
+ props: { isRoot: true },
2000
+ },
2001
+ carryForward: {
2002
+ previousItems: [
2003
+ fakeSite({
2004
+ materializedPath: oldPath,
2005
+ parentSiteId: PARENT_SITE_ID,
2006
+ }),
2007
+ ],
2008
+ newParentId,
2009
+ newParentPath: `/${newParentId.toString()}/`,
2010
+ },
2011
+ },
2012
+ [SITE_ID],
2013
+ );
2014
+
2015
+ expect(findBySpy).toHaveBeenCalledTimes(2);
2016
+ expect(findBySpy.mock.calls[0]![0]).toEqual(
2017
+ expect.objectContaining({ limit: 1000, skip: 0 }),
2018
+ );
2019
+ expect(findBySpy.mock.calls[1]![0]).toEqual(
2020
+ expect.objectContaining({ limit: 1000, skip: 0 }),
2021
+ );
2022
+ expect(updateColumnsSpy).toHaveBeenCalledTimes(1002);
2023
+ expect(updateColumnsSpy).toHaveBeenCalledWith(
2024
+ expect.objectContaining({ id: finalId }),
2025
+ );
2026
+ });
2027
+
2028
+ it("processes overlapping bulk move roots deepest-first", async () => {
2029
+ const descendantId: ObjectID = ObjectID.generate();
2030
+ const ancestorPath: string = `/${SITE_ID.toString()}/`;
2031
+ const descendantPath: string = `${ancestorPath}${descendantId.toString()}/`;
2032
+ const updateColumnsSpy: jest.SpyInstance = jest
2033
+ .spyOn(NetworkSiteService, "updateColumnsByIdWithoutHooks")
2034
+ .mockResolvedValue(undefined as never);
2035
+ jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([]);
2036
+ jest
2037
+ .spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
2038
+ .mockResolvedValue(undefined as never);
2039
+
2040
+ await (NetworkSiteService as any).onUpdateSuccess(
2041
+ {
2042
+ updateBy: {
2043
+ query: {},
2044
+ data: { parentSiteId: PARENT_SITE_ID },
2045
+ props: { isRoot: true },
2046
+ },
2047
+ carryForward: {
2048
+ previousItems: [
2049
+ fakeSite({ materializedPath: ancestorPath }),
2050
+ fakeSite({
2051
+ id: descendantId,
2052
+ _id: descendantId.toString(),
2053
+ parentSiteId: SITE_ID,
2054
+ materializedPath: descendantPath,
2055
+ }),
2056
+ ],
2057
+ newParentId: PARENT_SITE_ID,
2058
+ newParentPath: `/${PARENT_SITE_ID.toString()}/`,
2059
+ },
2060
+ },
2061
+ [SITE_ID, descendantId],
2062
+ );
2063
+
2064
+ expect(updateColumnsSpy.mock.calls[0]![0].id.toString()).toBe(
2065
+ descendantId.toString(),
2066
+ );
2067
+ expect(updateColumnsSpy.mock.calls[1]![0].id.toString()).toBe(
2068
+ SITE_ID.toString(),
2069
+ );
2070
+ });
2071
+
2072
+ /*
1027
2073
  * DatabaseService calls onUpdateSuccess even when the tenant-scoped UPDATE
1028
2074
  * matched zero rows. Nothing may be written for a row the UPDATE did not
1029
2075
  * touch - otherwise a cross-project id in the query rewrites the victim's
@@ -1220,59 +2266,324 @@ describe("NetworkSiteService.onMonitorStatusChanged", () => {
1220
2266
  "recomputeRollupForSiteAndAncestors",
1221
2267
  );
1222
2268
 
1223
- await NetworkSiteService.onMonitorStatusChanged({
1224
- projectId: PROJECT_ID,
1225
- monitorIds: [MONITOR_ID],
1226
- monitorStatusId: OFFLINE_STATUS_ID,
2269
+ await NetworkSiteService.onMonitorStatusChanged({
2270
+ projectId: PROJECT_ID,
2271
+ monitorIds: [MONITOR_ID],
2272
+ monitorStatusId: OFFLINE_STATUS_ID,
2273
+ });
2274
+
2275
+ expect(stampSpy).toHaveBeenCalledTimes(1);
2276
+ expect(rollupSpy).not.toHaveBeenCalled();
2277
+ });
2278
+
2279
+ it("does nothing when the monitors are not NetworkDevice monitors", async () => {
2280
+ jest.spyOn(MonitorService, "findBy").mockResolvedValue([]);
2281
+ /*
2282
+ * A device can be bound to a monitor either through a NetworkDevice
2283
+ * monitor's steps or directly by its own monitorId column, so the service
2284
+ * still asks NetworkDeviceService for devices carrying these monitorIds.
2285
+ * When that also comes back empty there is genuinely nothing to act on.
2286
+ */
2287
+ jest
2288
+ .spyOn(NetworkDeviceService, "findBy")
2289
+ .mockResolvedValue([] as unknown as Array<NetworkDevice>);
2290
+ const stampSpy: jest.SpyInstance = jest.spyOn(
2291
+ NetworkDeviceService,
2292
+ "updateColumnsByIdWithoutHooks",
2293
+ );
2294
+ const rollupSpy: jest.SpyInstance = jest.spyOn(
2295
+ NetworkSiteService,
2296
+ "recomputeRollupForSiteAndAncestors",
2297
+ );
2298
+
2299
+ await NetworkSiteService.onMonitorStatusChanged({
2300
+ projectId: PROJECT_ID,
2301
+ monitorIds: [MONITOR_ID],
2302
+ monitorStatusId: OFFLINE_STATUS_ID,
2303
+ });
2304
+
2305
+ // No device resolved, so nothing is stamped and no rollup is recomputed.
2306
+ expect(stampSpy).not.toHaveBeenCalled();
2307
+ expect(rollupSpy).not.toHaveBeenCalled();
2308
+ });
2309
+
2310
+ it("NEVER throws - a failing lookup is logged, not propagated", async () => {
2311
+ jest
2312
+ .spyOn(MonitorService, "findBy")
2313
+ .mockRejectedValue(new Error("database is down"));
2314
+
2315
+ await expect(
2316
+ NetworkSiteService.onMonitorStatusChanged({
2317
+ projectId: PROJECT_ID,
2318
+ monitorIds: [MONITOR_ID],
2319
+ monitorStatusId: OFFLINE_STATUS_ID,
2320
+ }),
2321
+ ).resolves.toBeUndefined();
2322
+ });
2323
+
2324
+ /*
2325
+ * Monitor-backed devices (monitoringMethod "Monitor") are never polled,
2326
+ * so their isReachable - the column the device list's summary tiles and
2327
+ * Status filter count and filter on in SQL - stays NULL forever unless
2328
+ * the server derives it from the bound monitor's status. These cases pin
2329
+ * that derivation: `isReachable = !MonitorStatus.isOfflineState`, read
2330
+ * from the status row ONCE per call and only when a monitor-backed
2331
+ * device is in the set, while a probe-polled device's isReachable stays
2332
+ * its own poll's to write.
2333
+ */
2334
+ describe("isReachable for monitor-backed devices", () => {
2335
+ const SECOND_DEVICE_ID: ObjectID = new ObjectID(
2336
+ "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
2337
+ );
2338
+ const THIRD_DEVICE_ID: ObjectID = new ObjectID(
2339
+ "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
2340
+ );
2341
+
2342
+ function monitorBackedDevice(id: ObjectID): NetworkDevice {
2343
+ return {
2344
+ id: id,
2345
+ siteId: SITE_ID,
2346
+ monitorId: MONITOR_ID,
2347
+ monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
2348
+ } as unknown as NetworkDevice;
2349
+ }
2350
+
2351
+ function probePolledDevice(
2352
+ id: ObjectID,
2353
+ monitoringMethod: string | undefined,
2354
+ ): NetworkDevice {
2355
+ return {
2356
+ id: id,
2357
+ siteId: SITE_ID,
2358
+ monitoringMethod: monitoringMethod,
2359
+ } as unknown as NetworkDevice;
2360
+ }
2361
+
2362
+ function fakeStatus(isOfflineState: boolean): MonitorStatus {
2363
+ return {
2364
+ id: OFFLINE_STATUS_ID,
2365
+ _id: OFFLINE_STATUS_ID.toString(),
2366
+ isOfflineState: isOfflineState,
2367
+ } as unknown as MonitorStatus;
2368
+ }
2369
+
2370
+ function stampedData(stampSpy: jest.SpyInstance): Array<any> {
2371
+ return stampSpy.mock.calls.map((call: Array<any>) => {
2372
+ return call[0].data;
2373
+ });
2374
+ }
2375
+
2376
+ /*
2377
+ * Wires the two device lookups for a "bound by monitorId" set: no
2378
+ * Network Device monitor references anything, every device comes back
2379
+ * from the monitorId query.
2380
+ */
2381
+ function mockBoundDevices(devices: Array<NetworkDevice>): {
2382
+ stamp: jest.SpyInstance;
2383
+ status: jest.SpyInstance;
2384
+ } {
2385
+ jest.spyOn(MonitorService, "findBy").mockResolvedValue([]);
2386
+ jest.spyOn(NetworkDeviceService, "findBy").mockResolvedValue(devices);
2387
+ jest
2388
+ .spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
2389
+ .mockResolvedValue(undefined as never);
2390
+
2391
+ const stamp: jest.SpyInstance = jest
2392
+ .spyOn(NetworkDeviceService, "updateColumnsByIdWithoutHooks")
2393
+ .mockResolvedValue(undefined as never);
2394
+ const status: jest.SpyInstance = jest.spyOn(
2395
+ MonitorStatusService,
2396
+ "findOneBy",
2397
+ );
2398
+
2399
+ return { stamp, status };
2400
+ }
2401
+
2402
+ it("reads the status row once for N monitor-backed rows and stamps isReachable false for an offline status", async () => {
2403
+ const { stamp, status } = mockBoundDevices([
2404
+ monitorBackedDevice(DEVICE_ID),
2405
+ monitorBackedDevice(SECOND_DEVICE_ID),
2406
+ monitorBackedDevice(THIRD_DEVICE_ID),
2407
+ ]);
2408
+ status.mockResolvedValue(fakeStatus(true));
2409
+
2410
+ await NetworkSiteService.onMonitorStatusChanged({
2411
+ projectId: PROJECT_ID,
2412
+ monitorIds: [MONITOR_ID],
2413
+ monitorStatusId: OFFLINE_STATUS_ID,
2414
+ });
2415
+
2416
+ expect(status).toHaveBeenCalledTimes(1);
2417
+ const statusArgs: any = status.mock.calls[0]![0];
2418
+ expect(statusArgs.query._id.toString()).toBe(
2419
+ OFFLINE_STATUS_ID.toString(),
2420
+ );
2421
+ expect(statusArgs.query.projectId.toString()).toBe(PROJECT_ID.toString());
2422
+ expect(statusArgs.select).toEqual({ _id: true, isOfflineState: true });
2423
+ expect(statusArgs.props).toEqual({ isRoot: true });
2424
+
2425
+ expect(stamp).toHaveBeenCalledTimes(3);
2426
+ for (const data of stampedData(stamp)) {
2427
+ expect(data.currentMonitorStatusId.toString()).toBe(
2428
+ OFFLINE_STATUS_ID.toString(),
2429
+ );
2430
+ expect(data.isReachable).toBe(false);
2431
+ }
2432
+ });
2433
+
2434
+ it("stamps isReachable true for an operational (non-offline) status", async () => {
2435
+ const { stamp, status } = mockBoundDevices([
2436
+ monitorBackedDevice(DEVICE_ID),
2437
+ ]);
2438
+ status.mockResolvedValue(fakeStatus(false));
2439
+
2440
+ await NetworkSiteService.onMonitorStatusChanged({
2441
+ projectId: PROJECT_ID,
2442
+ monitorIds: [MONITOR_ID],
2443
+ monitorStatusId: OPERATIONAL_STATUS_ID,
2444
+ });
2445
+
2446
+ expect(status).toHaveBeenCalledTimes(1);
2447
+ expect(stamp).toHaveBeenCalledTimes(1);
2448
+ expect(stampedData(stamp)[0].isReachable).toBe(true);
2449
+ expect(stampedData(stamp)[0].currentMonitorStatusId.toString()).toBe(
2450
+ OPERATIONAL_STATUS_ID.toString(),
2451
+ );
2452
+ });
2453
+
2454
+ /*
2455
+ * A "Degraded" status is neither operational nor offline. It is read
2456
+ * off the offline end, like DeviceReachabilityUtil does, so a degraded
2457
+ * device stays reachable rather than being painted down.
2458
+ */
2459
+ it("treats a status with no offline flag at all as reachable", async () => {
2460
+ const { stamp, status } = mockBoundDevices([
2461
+ monitorBackedDevice(DEVICE_ID),
2462
+ ]);
2463
+ status.mockResolvedValue({
2464
+ id: OFFLINE_STATUS_ID,
2465
+ _id: OFFLINE_STATUS_ID.toString(),
2466
+ } as unknown as MonitorStatus);
2467
+
2468
+ await NetworkSiteService.onMonitorStatusChanged({
2469
+ projectId: PROJECT_ID,
2470
+ monitorIds: [MONITOR_ID],
2471
+ monitorStatusId: OFFLINE_STATUS_ID,
2472
+ });
2473
+
2474
+ expect(stampedData(stamp)[0].isReachable).toBe(true);
2475
+ });
2476
+
2477
+ it("never reads the status row for an all-probe-polled set, and stamps the id only", async () => {
2478
+ const { stamp, status } = mockBoundDevices([
2479
+ probePolledDevice(DEVICE_ID, NetworkDeviceMonitoringMethod.Probe),
2480
+ // A row written before the column existed: NULL reads as Probe.
2481
+ probePolledDevice(SECOND_DEVICE_ID, undefined),
2482
+ ]);
2483
+
2484
+ await NetworkSiteService.onMonitorStatusChanged({
2485
+ projectId: PROJECT_ID,
2486
+ monitorIds: [MONITOR_ID],
2487
+ monitorStatusId: OFFLINE_STATUS_ID,
2488
+ });
2489
+
2490
+ expect(status).not.toHaveBeenCalled();
2491
+ expect(stamp).toHaveBeenCalledTimes(2);
2492
+ for (const data of stampedData(stamp)) {
2493
+ expect(data.currentMonitorStatusId.toString()).toBe(
2494
+ OFFLINE_STATUS_ID.toString(),
2495
+ );
2496
+ // The poll owns a probe-polled device's isReachable: the key is absent, not undefined.
2497
+ expect(data).not.toHaveProperty("isReachable");
2498
+ expect(Object.keys(data)).toEqual(["currentMonitorStatusId"]);
2499
+ }
1227
2500
  });
1228
2501
 
1229
- expect(stampSpy).toHaveBeenCalledTimes(1);
1230
- expect(rollupSpy).not.toHaveBeenCalled();
1231
- });
2502
+ it("in a mixed set only the monitor-backed rows get isReachable, from one status read", async () => {
2503
+ const { stamp, status } = mockBoundDevices([
2504
+ probePolledDevice(DEVICE_ID, NetworkDeviceMonitoringMethod.Probe),
2505
+ monitorBackedDevice(SECOND_DEVICE_ID),
2506
+ probePolledDevice(THIRD_DEVICE_ID, undefined),
2507
+ ]);
2508
+ status.mockResolvedValue(fakeStatus(true));
1232
2509
 
1233
- it("does nothing when the monitors are not NetworkDevice monitors", async () => {
1234
- jest.spyOn(MonitorService, "findBy").mockResolvedValue([]);
1235
- /*
1236
- * A device can be bound to a monitor either through a NetworkDevice
1237
- * monitor's steps or directly by its own monitorId column, so the service
1238
- * still asks NetworkDeviceService for devices carrying these monitorIds.
1239
- * When that also comes back empty there is genuinely nothing to act on.
1240
- */
1241
- jest
1242
- .spyOn(NetworkDeviceService, "findBy")
1243
- .mockResolvedValue([] as unknown as Array<NetworkDevice>);
1244
- const stampSpy: jest.SpyInstance = jest.spyOn(
1245
- NetworkDeviceService,
1246
- "updateColumnsByIdWithoutHooks",
1247
- );
1248
- const rollupSpy: jest.SpyInstance = jest.spyOn(
1249
- NetworkSiteService,
1250
- "recomputeRollupForSiteAndAncestors",
1251
- );
2510
+ await NetworkSiteService.onMonitorStatusChanged({
2511
+ projectId: PROJECT_ID,
2512
+ monitorIds: [MONITOR_ID],
2513
+ monitorStatusId: OFFLINE_STATUS_ID,
2514
+ });
1252
2515
 
1253
- await NetworkSiteService.onMonitorStatusChanged({
1254
- projectId: PROJECT_ID,
1255
- monitorIds: [MONITOR_ID],
1256
- monitorStatusId: OFFLINE_STATUS_ID,
2516
+ expect(status).toHaveBeenCalledTimes(1);
2517
+ expect(stamp).toHaveBeenCalledTimes(3);
2518
+
2519
+ const byDeviceId: Map<string, any> = new Map(
2520
+ stamp.mock.calls.map((call: Array<any>) => {
2521
+ return [call[0].id.toString(), call[0].data];
2522
+ }),
2523
+ );
2524
+
2525
+ expect(byDeviceId.get(SECOND_DEVICE_ID.toString()).isReachable).toBe(
2526
+ false,
2527
+ );
2528
+ expect(byDeviceId.get(DEVICE_ID.toString())).not.toHaveProperty(
2529
+ "isReachable",
2530
+ );
2531
+ expect(byDeviceId.get(THIRD_DEVICE_ID.toString())).not.toHaveProperty(
2532
+ "isReachable",
2533
+ );
1257
2534
  });
1258
2535
 
1259
- // No device resolved, so nothing is stamped and no rollup is recomputed.
1260
- expect(stampSpy).not.toHaveBeenCalled();
1261
- expect(rollupSpy).not.toHaveBeenCalled();
1262
- });
2536
+ it("stamps the id but leaves isReachable alone, with a warning, when the status row is not found", async () => {
2537
+ const { stamp, status } = mockBoundDevices([
2538
+ monitorBackedDevice(DEVICE_ID),
2539
+ ]);
2540
+ status.mockResolvedValue(null);
2541
+ const warn: jest.SpyInstance = jest
2542
+ .spyOn(logger, "warn")
2543
+ .mockImplementation(() => {
2544
+ return undefined;
2545
+ });
1263
2546
 
1264
- it("NEVER throws - a failing lookup is logged, not propagated", async () => {
1265
- jest
1266
- .spyOn(MonitorService, "findBy")
1267
- .mockRejectedValue(new Error("database is down"));
2547
+ await NetworkSiteService.onMonitorStatusChanged({
2548
+ projectId: PROJECT_ID,
2549
+ monitorIds: [MONITOR_ID],
2550
+ monitorStatusId: OFFLINE_STATUS_ID,
2551
+ });
1268
2552
 
1269
- await expect(
1270
- NetworkSiteService.onMonitorStatusChanged({
2553
+ expect(stamp).toHaveBeenCalledTimes(1);
2554
+ expect(stampedData(stamp)[0].currentMonitorStatusId.toString()).toBe(
2555
+ OFFLINE_STATUS_ID.toString(),
2556
+ );
2557
+ expect(stampedData(stamp)[0]).not.toHaveProperty("isReachable");
2558
+ expect(warn).toHaveBeenCalledTimes(1);
2559
+ expect(String(warn.mock.calls[0]![0])).toContain(
2560
+ OFFLINE_STATUS_ID.toString(),
2561
+ );
2562
+ });
2563
+
2564
+ it("selects monitoringMethod on BOTH device lookups", async () => {
2565
+ // A Network Device monitor referencing a device drives the first lookup...
2566
+ jest
2567
+ .spyOn(MonitorService, "findBy")
2568
+ .mockResolvedValue([fakeNetworkDeviceMonitor([DEVICE_ID.toString()])]);
2569
+ // ...and the monitorId query is always the second.
2570
+ const findDevices: jest.SpyInstance = jest
2571
+ .spyOn(NetworkDeviceService, "findBy")
2572
+ .mockResolvedValue([]);
2573
+
2574
+ await NetworkSiteService.onMonitorStatusChanged({
1271
2575
  projectId: PROJECT_ID,
1272
2576
  monitorIds: [MONITOR_ID],
1273
2577
  monitorStatusId: OFFLINE_STATUS_ID,
1274
- }),
1275
- ).resolves.toBeUndefined();
2578
+ });
2579
+
2580
+ expect(findDevices).toHaveBeenCalledTimes(2);
2581
+ for (const call of findDevices.mock.calls) {
2582
+ expect((call[0] as any).select.monitoringMethod).toBe(true);
2583
+ expect((call[0] as any).select._id).toBe(true);
2584
+ expect((call[0] as any).select.siteId).toBe(true);
2585
+ }
2586
+ });
1276
2587
  });
1277
2588
  });
1278
2589
 
@@ -1450,10 +2761,10 @@ describe("NetworkSiteService.getMaterializedPathForSite", () => {
1450
2761
  });
1451
2762
 
1452
2763
  /*
1453
- * parentSiteId is onDelete: "SET NULL", so deleting a mid-tree site detaches
1454
- * its children but strands their materializedPath through the dead row -
1455
- * leaving the children endpoint (parentSiteId) and the rollup engine
1456
- * (materializedPath) disagreeing, and double-counting the subtree's outages.
2764
+ * A typed child cannot be promoted or attached to its grandparent without
2765
+ * violating its configured direct-parent type. The before hook therefore
2766
+ * blocks partial subtree deletes. The success-hook tests retain the old
2767
+ * orphan repair as a defensive fallback for hook-bypassing/internal deletes.
1457
2768
  */
1458
2769
  describe("NetworkSiteService delete hooks (orphan repair)", () => {
1459
2770
  afterEach(() => {
@@ -1501,6 +2812,148 @@ describe("NetworkSiteService delete hooks (orphan repair)", () => {
1501
2812
  expect(result.carryForward.sitesToDelete).toEqual([]);
1502
2813
  });
1503
2814
 
2815
+ it("uses the same tenant scope for a root delete preflight", async () => {
2816
+ const findBySpy: jest.SpyInstance = jest
2817
+ .spyOn(NetworkSiteService, "findBy")
2818
+ .mockResolvedValue([]);
2819
+
2820
+ await (NetworkSiteService as any).onBeforeDelete({
2821
+ query: { _id: DISTRICT_ID.toString() },
2822
+ props: { tenantId: PROJECT_ID, isRoot: true },
2823
+ } as unknown as DeleteBy<NetworkSite>);
2824
+
2825
+ expect(findBySpy.mock.calls[0]![0].query.projectId.toString()).toBe(
2826
+ PROJECT_ID.toString(),
2827
+ );
2828
+ });
2829
+
2830
+ it("does not invent a tenant scope for a root multi-tenant delete preflight", async () => {
2831
+ const findBySpy: jest.SpyInstance = jest
2832
+ .spyOn(NetworkSiteService, "findBy")
2833
+ .mockResolvedValue([]);
2834
+
2835
+ await (NetworkSiteService as any).onBeforeDelete({
2836
+ query: { _id: DISTRICT_ID.toString() },
2837
+ props: {
2838
+ tenantId: PROJECT_ID,
2839
+ isRoot: true,
2840
+ isMultiTenantRequest: true,
2841
+ },
2842
+ } as unknown as DeleteBy<NetworkSite>);
2843
+
2844
+ expect(findBySpy.mock.calls[0]![0].query.projectId).toBeUndefined();
2845
+ });
2846
+
2847
+ it("rejects deleting a site while a direct child would survive", async () => {
2848
+ const findBySpy: jest.SpyInstance = jest
2849
+ .spyOn(NetworkSiteService, "findBy")
2850
+ .mockResolvedValueOnce([deletedDistrict()])
2851
+ .mockResolvedValueOnce([
2852
+ fakeSite({
2853
+ id: STORE_ID,
2854
+ _id: STORE_ID.toString(),
2855
+ parentSiteId: DISTRICT_ID,
2856
+ }),
2857
+ ]);
2858
+
2859
+ await expect(
2860
+ (NetworkSiteService as any).onBeforeDelete({
2861
+ query: { _id: DISTRICT_ID.toString() },
2862
+ limit: 1,
2863
+ skip: 0,
2864
+ props: { tenantId: PROJECT_ID },
2865
+ } as unknown as DeleteBy<NetworkSite>),
2866
+ ).rejects.toThrow(
2867
+ "A network site with child sites cannot be deleted. Move or delete its child sites first.",
2868
+ );
2869
+
2870
+ const childQuery: any = findBySpy.mock.calls[1]![0].query;
2871
+ expect(childQuery.projectId).toBeInstanceOf(FindOperator);
2872
+ expect(
2873
+ Object.values(
2874
+ childQuery.projectId.objectLiteralParameters as Record<
2875
+ string,
2876
+ Array<string>
2877
+ >,
2878
+ ).flat(),
2879
+ ).toContain(PROJECT_ID.toString());
2880
+ });
2881
+
2882
+ it("allows a bulk delete when every direct child is in the delete set", async () => {
2883
+ const store: NetworkSite = fakeSite({
2884
+ id: STORE_ID,
2885
+ _id: STORE_ID.toString(),
2886
+ parentSiteId: DISTRICT_ID,
2887
+ materializedPath: STORE_PATH,
2888
+ });
2889
+ jest
2890
+ .spyOn(NetworkSiteService, "findBy")
2891
+ .mockResolvedValueOnce([deletedDistrict(), store])
2892
+ .mockResolvedValueOnce([store]);
2893
+
2894
+ const result: OnDelete<NetworkSite> = await (
2895
+ NetworkSiteService as any
2896
+ ).onBeforeDelete({
2897
+ query: {},
2898
+ limit: 2,
2899
+ skip: 0,
2900
+ props: { tenantId: PROJECT_ID },
2901
+ } as unknown as DeleteBy<NetworkSite>);
2902
+
2903
+ expect(result.carryForward.sitesToDelete).toEqual([
2904
+ deletedDistrict(),
2905
+ store,
2906
+ ]);
2907
+ });
2908
+
2909
+ it("rejects a bulk parent-and-child delete when a grandchild would survive", async () => {
2910
+ const store: NetworkSite = fakeSite({
2911
+ id: STORE_ID,
2912
+ _id: STORE_ID.toString(),
2913
+ parentSiteId: DISTRICT_ID,
2914
+ materializedPath: STORE_PATH,
2915
+ });
2916
+ jest
2917
+ .spyOn(NetworkSiteService, "findBy")
2918
+ .mockResolvedValueOnce([deletedDistrict(), store])
2919
+ .mockResolvedValueOnce([
2920
+ store,
2921
+ fakeSite({
2922
+ id: UNIT_ID,
2923
+ _id: UNIT_ID.toString(),
2924
+ parentSiteId: STORE_ID,
2925
+ materializedPath: UNIT_PATH,
2926
+ }),
2927
+ ]);
2928
+
2929
+ await expect(
2930
+ (NetworkSiteService as any).onBeforeDelete({
2931
+ query: {},
2932
+ limit: 2,
2933
+ skip: 0,
2934
+ props: { tenantId: PROJECT_ID },
2935
+ } as unknown as DeleteBy<NetworkSite>),
2936
+ ).rejects.toThrow(
2937
+ "A network site with child sites cannot be deleted. Move or delete its child sites first.",
2938
+ );
2939
+ });
2940
+
2941
+ it("uses the requested window when determining the exact bulk delete set", async () => {
2942
+ const findBySpy: jest.SpyInstance = jest
2943
+ .spyOn(NetworkSiteService, "findBy")
2944
+ .mockResolvedValue([]);
2945
+
2946
+ await (NetworkSiteService as any).onBeforeDelete({
2947
+ query: {},
2948
+ limit: 7,
2949
+ skip: 3,
2950
+ props: { tenantId: PROJECT_ID },
2951
+ } as unknown as DeleteBy<NetworkSite>);
2952
+
2953
+ expect(findBySpy.mock.calls[0]![0].limit).toBe(7);
2954
+ expect(findBySpy.mock.calls[0]![0].skip).toBe(3);
2955
+ });
2956
+
1504
2957
  it("reparents the direct child and rebases the whole subtree", async () => {
1505
2958
  jest
1506
2959
  .spyOn(NetworkSiteService, "getMaterializedPathForSite")
@@ -1560,6 +3013,57 @@ describe("NetworkSiteService delete hooks (orphan repair)", () => {
1560
3013
  expect(rollupSpy.mock.calls[0]![0].toString()).toBe(REGION_ID.toString());
1561
3014
  });
1562
3015
 
3016
+ it("pages legacy orphan repair from offset zero until every descendant is rewritten", async () => {
3017
+ const repeatedId: ObjectID = ObjectID.generate();
3018
+ const repeatedDescendant: NetworkSite = fakeSite({
3019
+ id: repeatedId,
3020
+ _id: repeatedId.toString(),
3021
+ materializedPath: `${DISTRICT_PATH}${repeatedId.toString()}/`,
3022
+ });
3023
+ const finalId: ObjectID = ObjectID.generate();
3024
+ const finalDescendant: NetworkSite = fakeSite({
3025
+ id: finalId,
3026
+ _id: finalId.toString(),
3027
+ materializedPath: `${DISTRICT_PATH}${finalId.toString()}/`,
3028
+ });
3029
+
3030
+ jest
3031
+ .spyOn(NetworkSiteService, "getMaterializedPathForSite")
3032
+ .mockResolvedValue(`/${REGION_ID.toString()}/`);
3033
+ const findBySpy: jest.SpyInstance = jest
3034
+ .spyOn(NetworkSiteService, "findBy")
3035
+ .mockResolvedValueOnce(
3036
+ new Array<NetworkSite>(1000).fill(repeatedDescendant),
3037
+ )
3038
+ .mockResolvedValueOnce([finalDescendant]);
3039
+ const updateColumnsSpy: jest.SpyInstance = jest
3040
+ .spyOn(NetworkSiteService, "updateColumnsByIdWithoutHooks")
3041
+ .mockResolvedValue(undefined as never);
3042
+ jest
3043
+ .spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
3044
+ .mockResolvedValue(undefined as never);
3045
+
3046
+ await (NetworkSiteService as any).onDeleteSuccess(
3047
+ {
3048
+ deleteBy: { query: {}, props: { tenantId: PROJECT_ID } },
3049
+ carryForward: { sitesToDelete: [deletedDistrict()] },
3050
+ },
3051
+ [DISTRICT_ID],
3052
+ );
3053
+
3054
+ expect(findBySpy).toHaveBeenCalledTimes(2);
3055
+ expect(findBySpy.mock.calls[0]![0]).toEqual(
3056
+ expect.objectContaining({ limit: 1000, skip: 0 }),
3057
+ );
3058
+ expect(findBySpy.mock.calls[1]![0]).toEqual(
3059
+ expect.objectContaining({ limit: 1000, skip: 0 }),
3060
+ );
3061
+ expect(updateColumnsSpy).toHaveBeenCalledTimes(1001);
3062
+ expect(updateColumnsSpy).toHaveBeenCalledWith(
3063
+ expect.objectContaining({ id: finalId }),
3064
+ );
3065
+ });
3066
+
1563
3067
  it("promotes children to roots when the deleted site was a root", async () => {
1564
3068
  const rootPath: string = `/${DISTRICT_ID.toString()}/`;
1565
3069
  jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([
@@ -1647,3 +3151,236 @@ describe("NetworkSiteService delete hooks (orphan repair)", () => {
1647
3151
  ).resolves.toBeDefined();
1648
3152
  });
1649
3153
  });
3154
+
3155
+ describe("NetworkSiteService hierarchy mutation lock", () => {
3156
+ const runThroughLock: (data: {
3157
+ operation: () => Promise<unknown>;
3158
+ }) => Promise<unknown> = async (data: {
3159
+ operation: () => Promise<unknown>;
3160
+ }): Promise<unknown> => {
3161
+ return await data.operation();
3162
+ };
3163
+
3164
+ beforeEach(() => {
3165
+ jest.restoreAllMocks();
3166
+ });
3167
+
3168
+ afterEach(() => {
3169
+ jest.restoreAllMocks();
3170
+ });
3171
+
3172
+ it("uses the shared project lock around create", async () => {
3173
+ const site: NetworkSite = fakeSite({});
3174
+ const runExclusiveSpy: jest.SpyInstance = jest
3175
+ .spyOn(NetworkSiteHierarchyLock, "runExclusive")
3176
+ .mockImplementation(runThroughLock as never);
3177
+ const superCreateSpy: jest.SpyInstance = jest
3178
+ .spyOn(DatabaseService.prototype, "create")
3179
+ .mockResolvedValue(site);
3180
+
3181
+ await expect(
3182
+ NetworkSiteService.create({
3183
+ data: site,
3184
+ props: { isRoot: true },
3185
+ }),
3186
+ ).resolves.toBe(site);
3187
+
3188
+ expect(runExclusiveSpy).toHaveBeenCalledWith(
3189
+ expect.objectContaining({ projectIds: [PROJECT_ID] }),
3190
+ );
3191
+ expect(runExclusiveSpy.mock.invocationCallOrder[0]!).toBeLessThan(
3192
+ superCreateSpy.mock.invocationCallOrder[0]!,
3193
+ );
3194
+ });
3195
+
3196
+ it.each([
3197
+ ["parentSiteId", PARENT_SITE_ID],
3198
+ ["networkSiteTypeId", CHILD_SITE_TYPE_ID],
3199
+ ["projectId", PROJECT_ID],
3200
+ ["depth", 0],
3201
+ ])(
3202
+ "locks an update that changes %s",
3203
+ async (field: string, value: ObjectID | number) => {
3204
+ const runExclusiveSpy: jest.SpyInstance = jest
3205
+ .spyOn(NetworkSiteHierarchyLock, "runExclusive")
3206
+ .mockImplementation(runThroughLock as never);
3207
+ const findBySpy: jest.SpyInstance = jest
3208
+ .spyOn(NetworkSiteService, "findBy")
3209
+ .mockResolvedValue([fakeSite({})]);
3210
+ const superUpdateSpy: jest.SpyInstance = jest
3211
+ .spyOn(DatabaseService.prototype, "updateOneBy")
3212
+ .mockResolvedValue(1);
3213
+
3214
+ await expect(
3215
+ NetworkSiteService.updateOneBy({
3216
+ query: { _id: SITE_ID.toString() },
3217
+ data: { [field]: value },
3218
+ props: { isRoot: true },
3219
+ } as any),
3220
+ ).resolves.toBe(1);
3221
+
3222
+ expect(findBySpy).toHaveBeenCalledWith(
3223
+ expect.objectContaining({ select: { projectId: true } }),
3224
+ );
3225
+ expect(runExclusiveSpy).toHaveBeenCalledWith(
3226
+ expect.objectContaining({ projectIds: [PROJECT_ID] }),
3227
+ );
3228
+ expect(superUpdateSpy).toHaveBeenCalledTimes(1);
3229
+ },
3230
+ );
3231
+
3232
+ it("locks deletes with the same project key used by type mutations", async () => {
3233
+ const runExclusiveSpy: jest.SpyInstance = jest
3234
+ .spyOn(NetworkSiteHierarchyLock, "runExclusive")
3235
+ .mockImplementation(runThroughLock as never);
3236
+ jest.spyOn(NetworkSiteService, "findBy").mockResolvedValue([fakeSite({})]);
3237
+ jest.spyOn(DatabaseService.prototype, "deleteOneBy").mockResolvedValue(1);
3238
+
3239
+ await expect(
3240
+ NetworkSiteService.deleteOneBy({
3241
+ query: { _id: SITE_ID.toString() },
3242
+ props: { isRoot: true },
3243
+ }),
3244
+ ).resolves.toBe(1);
3245
+
3246
+ expect(runExclusiveSpy).toHaveBeenCalledWith(
3247
+ expect.objectContaining({ projectIds: [PROJECT_ID] }),
3248
+ );
3249
+ });
3250
+
3251
+ it("bypasses the lock for rollup-only and ignoreHooks updates", async () => {
3252
+ const runExclusiveSpy: jest.SpyInstance = jest.spyOn(
3253
+ NetworkSiteHierarchyLock,
3254
+ "runExclusive",
3255
+ );
3256
+ const superUpdateSpy: jest.SpyInstance = jest
3257
+ .spyOn(DatabaseService.prototype, "updateOneBy")
3258
+ .mockResolvedValue(1);
3259
+
3260
+ await NetworkSiteService.updateOneBy({
3261
+ query: { _id: SITE_ID.toString() },
3262
+ data: { lastRollupAt: new Date() },
3263
+ props: { isRoot: true },
3264
+ });
3265
+ await NetworkSiteService.updateOneBy({
3266
+ query: { _id: SITE_ID.toString() },
3267
+ data: { parentSiteId: PARENT_SITE_ID },
3268
+ props: { isRoot: true, ignoreHooks: true },
3269
+ });
3270
+
3271
+ expect(runExclusiveSpy).not.toHaveBeenCalled();
3272
+ expect(superUpdateSpy).toHaveBeenCalledTimes(2);
3273
+ });
3274
+
3275
+ it("rejects an unscoped root hierarchy bulk mutation before writing", async () => {
3276
+ const superUpdateSpy: jest.SpyInstance = jest.spyOn(
3277
+ DatabaseService.prototype,
3278
+ "updateBy",
3279
+ );
3280
+
3281
+ await expect(
3282
+ NetworkSiteService.updateBy({
3283
+ query: { name: "Any matching site" },
3284
+ data: { parentSiteId: null },
3285
+ limit: 10,
3286
+ skip: 0,
3287
+ props: { isRoot: true },
3288
+ }),
3289
+ ).rejects.toThrow(NETWORK_SITE_HIERARCHY_ROOT_SCOPE_ERROR_MESSAGE);
3290
+
3291
+ expect(superUpdateSpy).not.toHaveBeenCalled();
3292
+ });
3293
+
3294
+ it("rejects an open-ended root update even when root supplies tenantId", async () => {
3295
+ const superUpdateSpy: jest.SpyInstance = jest.spyOn(
3296
+ DatabaseService.prototype,
3297
+ "updateBy",
3298
+ );
3299
+
3300
+ await expect(
3301
+ NetworkSiteService.updateBy({
3302
+ query: { name: "Any matching site" },
3303
+ data: { parentSiteId: null },
3304
+ limit: 10,
3305
+ skip: 0,
3306
+ props: { isRoot: true, tenantId: PROJECT_ID },
3307
+ }),
3308
+ ).rejects.toThrow(NETWORK_SITE_HIERARCHY_ROOT_SCOPE_ERROR_MESSAGE);
3309
+
3310
+ expect(superUpdateSpy).not.toHaveBeenCalled();
3311
+ });
3312
+
3313
+ it("rejects an open-ended root write to a materialized hierarchy field", async () => {
3314
+ const superUpdateSpy: jest.SpyInstance = jest.spyOn(
3315
+ DatabaseService.prototype,
3316
+ "updateBy",
3317
+ );
3318
+
3319
+ await expect(
3320
+ NetworkSiteService.updateBy({
3321
+ query: { name: "Any matching site" },
3322
+ data: { depth: 0 },
3323
+ limit: 10,
3324
+ skip: 0,
3325
+ props: { isRoot: true, tenantId: PROJECT_ID },
3326
+ }),
3327
+ ).rejects.toThrow(NETWORK_SITE_HIERARCHY_ROOT_SCOPE_ERROR_MESSAGE);
3328
+
3329
+ expect(superUpdateSpy).not.toHaveBeenCalled();
3330
+ });
3331
+
3332
+ it("turns the retention cron's open root query into a leaf-only ID batch", async () => {
3333
+ const parentId: ObjectID = PARENT_SITE_ID;
3334
+ const leafId: ObjectID = SITE_ID;
3335
+ const retentionQuery: any = { deletedAt: { olderThanThirtyDays: true } };
3336
+
3337
+ jest
3338
+ .spyOn(NetworkSiteService, "findBy")
3339
+ .mockImplementation(async (findBy: any) => {
3340
+ if (findBy.query?.parentSiteId) {
3341
+ return [
3342
+ fakeSite({
3343
+ id: leafId,
3344
+ _id: leafId.toString(),
3345
+ parentSiteId: parentId,
3346
+ }),
3347
+ ];
3348
+ }
3349
+
3350
+ if (findBy.skip > 0) {
3351
+ return [];
3352
+ }
3353
+
3354
+ return [
3355
+ fakeSite({ id: parentId, _id: parentId.toString() }),
3356
+ fakeSite({ id: leafId, _id: leafId.toString() }),
3357
+ ];
3358
+ });
3359
+ const runExclusiveSpy: jest.SpyInstance = jest
3360
+ .spyOn(NetworkSiteHierarchyLock, "runExclusive")
3361
+ .mockImplementation(runThroughLock as never);
3362
+ const superHardDeleteSpy: jest.SpyInstance = jest
3363
+ .spyOn(DatabaseService.prototype, "hardDeleteBy")
3364
+ .mockResolvedValue(1);
3365
+
3366
+ await expect(
3367
+ NetworkSiteService.hardDeleteBy({
3368
+ query: retentionQuery,
3369
+ limit: 2,
3370
+ skip: 0,
3371
+ props: { isRoot: true },
3372
+ } as DeleteBy<NetworkSite>),
3373
+ ).resolves.toBe(1);
3374
+
3375
+ expect(runExclusiveSpy).toHaveBeenCalledWith(
3376
+ expect.objectContaining({ projectIds: [PROJECT_ID] }),
3377
+ );
3378
+ const closedDelete: any = superHardDeleteSpy.mock.calls[0]![0];
3379
+ expect(closedDelete.query.deletedAt).toBe(retentionQuery.deletedAt);
3380
+ expect(
3381
+ Object.values(closedDelete.query._id.objectLiteralParameters)[0],
3382
+ ).toEqual([leafId.toString()]);
3383
+ expect(closedDelete.limit).toBe(1);
3384
+ expect(closedDelete.skip).toBe(0);
3385
+ });
3386
+ });