@oneuptime/common 12.0.2 → 12.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/Models/DatabaseModels/CephCluster.ts +14 -0
  2. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  3. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  4. package/Models/DatabaseModels/DockerHost.ts +14 -0
  5. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  6. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  7. package/Models/DatabaseModels/Index.ts +2 -0
  8. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  9. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  10. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  11. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  12. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  13. package/Models/DatabaseModels/Project.ts +16 -2
  14. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  15. package/Models/DatabaseModels/Service.ts +30 -0
  16. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  17. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  18. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  19. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  20. package/Models/DatabaseModels/User.ts +30 -0
  21. package/Server/API/AIAgentDataAPI.ts +31 -0
  22. package/Server/API/BaseAPI.ts +13 -2
  23. package/Server/API/DashboardAPI.ts +458 -12
  24. package/Server/API/GitHubAPI.ts +119 -283
  25. package/Server/API/UserAPI.ts +263 -1
  26. package/Server/EnvironmentConfig.ts +38 -4
  27. package/Server/Infrastructure/GlobalCache.ts +152 -0
  28. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  36. package/Server/Services/AlertStateTimelineService.ts +20 -0
  37. package/Server/Services/AnalyticsDatabaseService.ts +72 -1
  38. package/Server/Services/CephClusterService.ts +26 -86
  39. package/Server/Services/CloudResourceService.ts +23 -83
  40. package/Server/Services/CodeRepositoryService.ts +105 -2
  41. package/Server/Services/DatabaseService.ts +296 -2
  42. package/Server/Services/DockerHostService.ts +19 -79
  43. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  44. package/Server/Services/HostService.ts +46 -99
  45. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  46. package/Server/Services/Index.ts +2 -0
  47. package/Server/Services/IoTFleetService.ts +19 -79
  48. package/Server/Services/KubernetesClusterService.ts +17 -78
  49. package/Server/Services/LabelService.ts +57 -10
  50. package/Server/Services/MetricTypeService.ts +84 -0
  51. package/Server/Services/MonitorService.ts +89 -14
  52. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  53. package/Server/Services/MonitorTemplateService.ts +65 -0
  54. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  55. package/Server/Services/PodmanHostService.ts +19 -79
  56. package/Server/Services/ProxmoxClusterService.ts +23 -83
  57. package/Server/Services/RumApplicationService.ts +19 -80
  58. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  59. package/Server/Services/ServerlessFunctionService.ts +24 -85
  60. package/Server/Services/ServiceService.ts +299 -73
  61. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  62. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  63. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  64. package/Server/Services/TelemetryEntityService.ts +1 -0
  65. package/Server/Services/UserService.ts +184 -0
  66. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  67. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  68. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  69. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  72. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  73. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  74. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  75. package/Server/Utils/PasswordHash.ts +306 -0
  76. package/Server/Utils/SingleFlight.ts +83 -0
  77. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  78. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  79. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  80. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  81. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  82. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  83. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  84. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  85. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  86. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  87. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  88. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  89. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  90. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  91. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  92. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  93. package/Tests/Server/API/Helpers.ts +6 -1
  94. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  95. package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
  96. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  97. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  98. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  99. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  100. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
  101. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  102. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  103. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
  104. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  105. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  106. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  107. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  108. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  109. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  110. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  111. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  112. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  113. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  114. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  115. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  116. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  117. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  118. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  119. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  120. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  121. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  122. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  123. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  124. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  125. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  128. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  129. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  130. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  131. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  132. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  133. package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
  134. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  135. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  136. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  137. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  138. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  139. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  140. package/Tests/Types/Decimal.test.ts +37 -0
  141. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  142. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  143. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  144. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  145. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  146. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  147. package/Tests/Types/Port.test.ts +38 -0
  148. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  149. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  150. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  151. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  152. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  153. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  154. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  155. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  156. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  157. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  158. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  159. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  160. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  161. package/Types/Database/ColumnLength.ts +9 -1
  162. package/Types/Database/DatabaseProperty.ts +26 -0
  163. package/Types/Database/NumericColumnValue.ts +128 -0
  164. package/Types/Database/TableColumn.ts +11 -0
  165. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  166. package/Types/Decimal.ts +16 -4
  167. package/Types/HashedString.ts +139 -4
  168. package/Types/Permission.ts +48 -0
  169. package/Types/Port.ts +16 -4
  170. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  171. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  172. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  173. package/UI/Components/Navbar/NavBar.tsx +12 -2
  174. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  175. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  176. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  177. package/Utils/Dashboard/Components/Index.ts +7 -0
  178. package/Utils/TeamMembersByProject.ts +237 -0
  179. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  180. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  182. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  184. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  186. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  188. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  190. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  192. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  193. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  194. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  195. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  196. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  197. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  198. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  199. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  200. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  202. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/Project.js +16 -2
  204. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  206. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  208. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  210. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  211. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  212. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  214. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  216. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/User.js +32 -0
  218. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/BaseAPI.js +11 -1
  222. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +343 -10
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/GitHubAPI.js +94 -167
  226. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  227. package/build/dist/Server/API/UserAPI.js +187 -2
  228. package/build/dist/Server/API/UserAPI.js.map +1 -1
  229. package/build/dist/Server/EnvironmentConfig.js +34 -4
  230. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/GlobalCache.js +120 -0
  232. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  234. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  249. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  250. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/CephClusterService.js +26 -76
  254. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  255. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  256. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  257. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  258. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  259. package/build/dist/Server/Services/DatabaseService.js +246 -2
  260. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  261. package/build/dist/Server/Services/DockerHostService.js +19 -69
  262. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  263. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  264. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  265. package/build/dist/Server/Services/HostService.js +58 -102
  266. package/build/dist/Server/Services/HostService.js.map +1 -1
  267. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  268. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  269. package/build/dist/Server/Services/Index.js +2 -0
  270. package/build/dist/Server/Services/Index.js.map +1 -1
  271. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  272. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  273. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  274. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  275. package/build/dist/Server/Services/LabelService.js +42 -7
  276. package/build/dist/Server/Services/LabelService.js.map +1 -1
  277. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  278. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  279. package/build/dist/Server/Services/MonitorService.js +81 -9
  280. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  281. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  282. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  283. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  284. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  285. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  286. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  287. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  288. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  289. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  290. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  291. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  292. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  293. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  294. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  295. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  296. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  297. package/build/dist/Server/Services/ServiceService.js +236 -61
  298. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  299. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  300. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  301. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  302. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  303. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  305. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  306. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  307. package/build/dist/Server/Services/UserService.js +165 -0
  308. package/build/dist/Server/Services/UserService.js.map +1 -1
  309. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  310. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  311. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  312. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  313. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  314. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  315. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  316. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  317. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  318. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  320. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  322. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  326. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  327. package/build/dist/Server/Utils/PasswordHash.js +237 -0
  328. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  329. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  330. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  331. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  332. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  333. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  334. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  335. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  336. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  337. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  338. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  340. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  342. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  344. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  346. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  348. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  349. package/build/dist/Types/Database/ColumnLength.js +9 -1
  350. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  351. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  352. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  353. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  354. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  355. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  356. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  357. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  358. package/build/dist/Types/Decimal.js +14 -3
  359. package/build/dist/Types/Decimal.js.map +1 -1
  360. package/build/dist/Types/HashedString.js +95 -5
  361. package/build/dist/Types/HashedString.js.map +1 -1
  362. package/build/dist/Types/Permission.js +42 -0
  363. package/build/dist/Types/Permission.js.map +1 -1
  364. package/build/dist/Types/Port.js +15 -5
  365. package/build/dist/Types/Port.js.map +1 -1
  366. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  367. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  368. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  369. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  370. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  371. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  372. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  373. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  374. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  375. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  376. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  377. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  378. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  379. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  380. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  381. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  382. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  383. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  384. package/package.json +1 -1
@@ -1,25 +1,40 @@
1
1
  import MonitorStepsProjectValidator from "../../../../Server/Utils/Monitor/MonitorStepsProjectValidator";
2
2
  import AlertSeverityService from "../../../../Server/Services/AlertSeverityService";
3
3
  import IncidentSeverityService from "../../../../Server/Services/IncidentSeverityService";
4
+ import LabelService from "../../../../Server/Services/LabelService";
4
5
  import MonitorStatusService from "../../../../Server/Services/MonitorStatusService";
6
+ import OnCallDutyPolicyService from "../../../../Server/Services/OnCallDutyPolicyService";
7
+ import ServiceService from "../../../../Server/Services/ServiceService";
5
8
  import AlertSeverity from "../../../../Models/DatabaseModels/AlertSeverity";
6
9
  import IncidentSeverity from "../../../../Models/DatabaseModels/IncidentSeverity";
10
+ import Label from "../../../../Models/DatabaseModels/Label";
7
11
  import MonitorStatus from "../../../../Models/DatabaseModels/MonitorStatus";
12
+ import OnCallDutyPolicy from "../../../../Models/DatabaseModels/OnCallDutyPolicy";
8
13
  import BadDataException from "../../../../Types/Exception/BadDataException";
14
+ import { JSONObject, ObjectType } from "../../../../Types/JSON";
9
15
  import ObjectID from "../../../../Types/ObjectID";
10
- import { JSONObject } from "../../../../Types/JSON";
11
16
  import { afterEach, describe, expect, it, jest } from "@jest/globals";
12
17
 
13
18
  /*
14
- * Contract under test: monitorSteps embeds ids of project-scoped records (the
15
- * default monitor status, the status each criteria switches to, and the
16
- * severities used to open incidents and alerts). Saving one that belongs to a
17
- * *different* project is what tied two projects together in production and
18
- * left the referenced project undeletable, so it must be rejected on write.
19
+ * Contract under test: monitorSteps embeds ids of other records the default
20
+ * monitor status, the status each criteria switches to, the severities used to
21
+ * open incidents and alerts, and the on-call policies, labels, teams and users
22
+ * stamped on what those create. Two things can be wrong with one of them.
19
23
  *
20
- * The check has to stay narrow: monitorSteps is full of unrelated uuids (step
21
- * ids, criteria ids, incident/alert template ids) that match no record at all,
22
- * and those must keep saving fine.
24
+ * An id belonging to a *different* project ties two projects together and
25
+ * leaves the referenced project undeletable. That is refused whether the field
26
+ * is used at run time or not.
27
+ *
28
+ * An id that exists NOWHERE is issue #3039: the monitor saved fine and then
29
+ * failed on every check, inside the probe worker, with
30
+ * insert or update on table "MonitorStatusTimeline" violates foreign key
31
+ * constraint
32
+ * and nothing surfaced to whoever supplied the id. That is refused for the ids
33
+ * the monitor actually reads — and only when the write is INTRODUCING them, so
34
+ * a monitor stored broken before this guard existed can still be saved.
35
+ *
36
+ * monitorSteps is also full of uuids that are NOT references (step ids,
37
+ * criteria ids, incident/alert template ids). Those must keep saving fine.
23
38
  */
24
39
 
25
40
  const PROJECT_ID: ObjectID = new ObjectID(
@@ -31,14 +46,21 @@ const OTHER_PROJECT_ID: ObjectID = new ObjectID(
31
46
 
32
47
  const OWN_STATUS_ID: string = "59b5ab80-63f6-4ffd-b3df-31c6ad127695";
33
48
  const FOREIGN_STATUS_ID: string = "cfc2f04f-79cb-4344-8c54-dafe5e3a290c";
34
- const FOREIGN_ALERT_SEVERITY_ID: string =
35
- "a2eb67d4-bd2e-4186-9187-dad799c9316c";
49
+ const UNKNOWN_STATUS_ID: string = "b8a7d5d1-2f5a-45cf-8f2c-2f2b83d1d9aa";
50
+ const OWN_INCIDENT_SEVERITY_ID: string = "1de3b8f4-0f2d-4a6f-9a2e-7cf3a1b2c3d4";
51
+ const UNKNOWN_INCIDENT_SEVERITY_ID: string =
52
+ "2ef4c905-1a3e-4b70-8b3f-8d04b2c3d4e5";
53
+ const UNKNOWN_ALERT_SEVERITY_ID: string =
54
+ "3f05da16-2b4f-4c81-9c40-9e15c3d4e5f6";
55
+ const UNKNOWN_ON_CALL_POLICY_ID: string =
56
+ "4016eb27-3c50-4d92-8d51-af26d4e5f607";
57
+ const FOREIGN_LABEL_ID: string = "5127fc38-4d61-4ea3-9e62-b037e5f60718";
58
+ const UNKNOWN_TELEMETRY_SERVICE_ID: string =
59
+ "62380d49-5e72-4fb4-8f73-c148f6071829";
36
60
 
37
- type FoundRecords = {
38
- monitorStatuses?: Array<MonitorStatus>;
39
- incidentSeverities?: Array<IncidentSeverity>;
40
- alertSeverities?: Array<AlertSeverity>;
41
- };
61
+ const STEP_ID: string = "4b752ccd-92e8-4295-9d33-b670e1721fbb";
62
+ const CRITERIA_ID: string = "5c863dde-a3f9-43a6-8e44-c781f2832acc";
63
+ const INCIDENT_TEMPLATE_ID: string = "6d974eef-b40a-44b7-9f55-d892031943dd";
42
64
 
43
65
  const monitorStatus: (id: string, projectId: ObjectID) => MonitorStatus = (
44
66
  id: string,
@@ -51,17 +73,37 @@ const monitorStatus: (id: string, projectId: ObjectID) => MonitorStatus = (
51
73
  return status;
52
74
  };
53
75
 
54
- const alertSeverity: (id: string, projectId: ObjectID) => AlertSeverity = (
76
+ const incidentSeverity: (
55
77
  id: string,
56
78
  projectId: ObjectID,
57
- ): AlertSeverity => {
58
- const severity: AlertSeverity = new AlertSeverity();
79
+ ) => IncidentSeverity = (id: string, projectId: ObjectID): IncidentSeverity => {
80
+ const severity: IncidentSeverity = new IncidentSeverity();
59
81
  severity._id = id;
60
- severity.name = "High";
82
+ severity.name = "Critical";
61
83
  severity.projectId = projectId;
62
84
  return severity;
63
85
  };
64
86
 
87
+ const label: (id: string, projectId: ObjectID) => Label = (
88
+ id: string,
89
+ projectId: ObjectID,
90
+ ): Label => {
91
+ const item: Label = new Label();
92
+ item._id = id;
93
+ item.name = "Production";
94
+ item.projectId = projectId;
95
+ return item;
96
+ };
97
+
98
+ type FoundRecords = {
99
+ monitorStatuses?: Array<MonitorStatus>;
100
+ incidentSeverities?: Array<IncidentSeverity>;
101
+ alertSeverities?: Array<AlertSeverity>;
102
+ onCallDutyPolicies?: Array<OnCallDutyPolicy>;
103
+ labels?: Array<Label>;
104
+ telemetryServices?: Array<JSONObject>;
105
+ };
106
+
65
107
  const stubLookups: (found: FoundRecords) => void = (
66
108
  found: FoundRecords,
67
109
  ): void => {
@@ -74,18 +116,67 @@ const stubLookups: (found: FoundRecords) => void = (
74
116
  jest
75
117
  .spyOn(AlertSeverityService, "findBy")
76
118
  .mockResolvedValue(found.alertSeverities || []);
119
+ jest
120
+ .spyOn(OnCallDutyPolicyService, "findBy")
121
+ .mockResolvedValue(found.onCallDutyPolicies || []);
122
+ jest.spyOn(LabelService, "findBy").mockResolvedValue(found.labels || []);
123
+ jest
124
+ .spyOn(ServiceService, "findBy")
125
+ .mockResolvedValue((found.telemetryServices || []) as never);
126
+ };
127
+
128
+ const objectIdJSON: (id: string) => JSONObject = (id: string): JSONObject => {
129
+ return { _type: ObjectType.ObjectID, value: id };
77
130
  };
78
131
 
79
- const stepsReferencing: (ids: Array<string>) => JSONObject = (
80
- ids: Array<string>,
81
- ): JSONObject => {
132
+ /*
133
+ * A monitorSteps document with one step and one criteria, shaped exactly the
134
+ * way MonitorSteps.toJSON writes it.
135
+ */
136
+ const steps: (data: {
137
+ defaultMonitorStatusId?: string | undefined;
138
+ criteria?: JSONObject | undefined;
139
+ step?: JSONObject | undefined;
140
+ }) => JSONObject = (data: {
141
+ defaultMonitorStatusId?: string | undefined;
142
+ criteria?: JSONObject | undefined;
143
+ step?: JSONObject | undefined;
144
+ }): JSONObject => {
82
145
  return {
83
- _type: "MonitorSteps",
146
+ _type: ObjectType.MonitorSteps,
84
147
  value: {
85
- defaultMonitorStatusId: ids[0],
86
- monitorStepsInstanceArray: ids.slice(1).map((id: string) => {
87
- return { _type: "MonitorStep", value: { id: id } };
88
- }),
148
+ defaultMonitorStatusId: data.defaultMonitorStatusId,
149
+ monitorStepsInstanceArray: [
150
+ {
151
+ _type: ObjectType.MonitorStep,
152
+ value: {
153
+ id: STEP_ID,
154
+ ...(data.step || {}),
155
+ monitorCriteria: {
156
+ _type: ObjectType.MonitorCriteria,
157
+ value: {
158
+ monitorCriteriaInstanceArray: data.criteria
159
+ ? [
160
+ {
161
+ _type: ObjectType.MonitorCriteriaInstance,
162
+ value: {
163
+ id: CRITERIA_ID,
164
+ name: "Monitor is offline",
165
+ description: "",
166
+ filterCondition: "all",
167
+ filters: [],
168
+ incidents: [],
169
+ alerts: [],
170
+ ...data.criteria,
171
+ },
172
+ },
173
+ ]
174
+ : [],
175
+ },
176
+ },
177
+ },
178
+ },
179
+ ],
89
180
  },
90
181
  };
91
182
  };
@@ -95,87 +186,542 @@ describe("MonitorStepsProjectValidator", () => {
95
186
  jest.restoreAllMocks();
96
187
  });
97
188
 
98
- it("rejects a monitor status that belongs to another project", async () => {
99
- stubLookups({
100
- monitorStatuses: [monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID)],
189
+ describe("references that do not exist (issue #3039)", () => {
190
+ it("rejects a default monitor status that exists nowhere", () => {
191
+ stubLookups({});
192
+
193
+ return expect(
194
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
195
+ monitorSteps: steps({
196
+ defaultMonitorStatusId: UNKNOWN_STATUS_ID,
197
+ }),
198
+ projectId: PROJECT_ID,
199
+ }),
200
+ ).rejects.toThrow(BadDataException);
101
201
  });
102
202
 
103
- await expect(
104
- MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
105
- monitorSteps: stepsReferencing([FOREIGN_STATUS_ID]),
106
- projectId: PROJECT_ID,
107
- }),
108
- ).rejects.toThrow(BadDataException);
109
- });
203
+ it("rejects the criteria status that produced the MonitorStatusTimeline foreign key error", () => {
204
+ stubLookups({
205
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
206
+ });
110
207
 
111
- it("names every offending record in the message", async () => {
112
- stubLookups({
113
- monitorStatuses: [monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID)],
114
- alertSeverities: [
115
- alertSeverity(FOREIGN_ALERT_SEVERITY_ID, OTHER_PROJECT_ID),
116
- ],
208
+ return expect(
209
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
210
+ monitorSteps: steps({
211
+ defaultMonitorStatusId: OWN_STATUS_ID,
212
+ criteria: {
213
+ monitorStatusId: UNKNOWN_STATUS_ID,
214
+ changeMonitorStatus: true,
215
+ },
216
+ }),
217
+ projectId: PROJECT_ID,
218
+ }),
219
+ ).rejects.toThrow(
220
+ `Monitor Status (criteria "Monitor is offline" monitor status) "${UNKNOWN_STATUS_ID}"`,
221
+ );
117
222
  });
118
223
 
119
- await expect(
120
- MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
121
- monitorSteps: stepsReferencing([
122
- FOREIGN_STATUS_ID,
123
- FOREIGN_ALERT_SEVERITY_ID,
124
- ]),
125
- projectId: PROJECT_ID,
126
- }),
127
- ).rejects.toThrow(
128
- /Monitor Status "Operational".*Alert Severity "High"|Alert Severity "High".*Monitor Status "Operational"/,
129
- );
224
+ it("rejects an incident severity that exists nowhere", () => {
225
+ stubLookups({
226
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
227
+ });
228
+
229
+ return expect(
230
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
231
+ monitorSteps: steps({
232
+ defaultMonitorStatusId: OWN_STATUS_ID,
233
+ criteria: {
234
+ createIncidents: true,
235
+ incidents: [
236
+ {
237
+ id: INCIDENT_TEMPLATE_ID,
238
+ title: "Down",
239
+ description: "",
240
+ incidentSeverityId: objectIdJSON(
241
+ UNKNOWN_INCIDENT_SEVERITY_ID,
242
+ ),
243
+ },
244
+ ],
245
+ },
246
+ }),
247
+ projectId: PROJECT_ID,
248
+ }),
249
+ ).rejects.toThrow(
250
+ `Incident Severity (criteria "Monitor is offline" incident severity) "${UNKNOWN_INCIDENT_SEVERITY_ID}"`,
251
+ );
252
+ });
253
+
254
+ it("rejects an alert severity that exists nowhere", () => {
255
+ stubLookups({
256
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
257
+ });
258
+
259
+ return expect(
260
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
261
+ monitorSteps: steps({
262
+ defaultMonitorStatusId: OWN_STATUS_ID,
263
+ criteria: {
264
+ createAlerts: true,
265
+ alerts: [
266
+ {
267
+ id: INCIDENT_TEMPLATE_ID,
268
+ title: "Down",
269
+ description: "",
270
+ alertSeverityId: objectIdJSON(UNKNOWN_ALERT_SEVERITY_ID),
271
+ },
272
+ ],
273
+ },
274
+ }),
275
+ projectId: PROJECT_ID,
276
+ }),
277
+ ).rejects.toThrow(
278
+ `Alert Severity (criteria "Monitor is offline" alert severity) "${UNKNOWN_ALERT_SEVERITY_ID}"`,
279
+ );
280
+ });
281
+
282
+ it("rejects an on-call policy that exists nowhere", () => {
283
+ stubLookups({
284
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
285
+ incidentSeverities: [
286
+ incidentSeverity(OWN_INCIDENT_SEVERITY_ID, PROJECT_ID),
287
+ ],
288
+ });
289
+
290
+ return expect(
291
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
292
+ monitorSteps: steps({
293
+ defaultMonitorStatusId: OWN_STATUS_ID,
294
+ criteria: {
295
+ createIncidents: true,
296
+ incidents: [
297
+ {
298
+ id: INCIDENT_TEMPLATE_ID,
299
+ title: "Down",
300
+ description: "",
301
+ incidentSeverityId: objectIdJSON(OWN_INCIDENT_SEVERITY_ID),
302
+ onCallPolicyIds: [objectIdJSON(UNKNOWN_ON_CALL_POLICY_ID)],
303
+ },
304
+ ],
305
+ },
306
+ }),
307
+ projectId: PROJECT_ID,
308
+ }),
309
+ ).rejects.toThrow(
310
+ `On-Call Duty Policy (criteria "Monitor is offline" incident on-call policy) "${UNKNOWN_ON_CALL_POLICY_ID}"`,
311
+ );
312
+ });
313
+
314
+ it("does not require an id the monitor never reads", () => {
315
+ /*
316
+ * A criteria keeps its monitorStatusId when `changeMonitorStatus` is off,
317
+ * and keeps whole incident templates when `createIncidents` is off, so
318
+ * toggling the switch back on restores what you had. None of that can
319
+ * produce the foreign key violation, and refusing a save over a field
320
+ * that does nothing would be a worse failure than the one being fixed.
321
+ */
322
+ stubLookups({
323
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
324
+ });
325
+
326
+ return expect(
327
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
328
+ monitorSteps: steps({
329
+ defaultMonitorStatusId: OWN_STATUS_ID,
330
+ criteria: {
331
+ monitorStatusId: UNKNOWN_STATUS_ID,
332
+ changeMonitorStatus: false,
333
+ createIncidents: false,
334
+ incidents: [
335
+ {
336
+ id: INCIDENT_TEMPLATE_ID,
337
+ title: "Down",
338
+ description: "",
339
+ incidentSeverityId: objectIdJSON(
340
+ UNKNOWN_INCIDENT_SEVERITY_ID,
341
+ ),
342
+ },
343
+ ],
344
+ },
345
+ }),
346
+ projectId: PROJECT_ID,
347
+ }),
348
+ ).resolves.toBeUndefined();
349
+ });
350
+
351
+ it("does not require a telemetry service the step is scoped to", () => {
352
+ /*
353
+ * A deleted telemetry service leaves the monitor querying nothing, not
354
+ * violating a foreign key, and deleting one is ordinary enough that
355
+ * requiring it would refuse every later edit of a monitor that once
356
+ * watched it.
357
+ */
358
+ stubLookups({
359
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
360
+ });
361
+
362
+ return expect(
363
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
364
+ monitorSteps: steps({
365
+ defaultMonitorStatusId: OWN_STATUS_ID,
366
+ step: {
367
+ metricMonitor: {
368
+ telemetryServiceIds: [
369
+ objectIdJSON(UNKNOWN_TELEMETRY_SERVICE_ID),
370
+ ],
371
+ },
372
+ },
373
+ }),
374
+ projectId: PROJECT_ID,
375
+ }),
376
+ ).resolves.toBeUndefined();
377
+ });
378
+
379
+ it("ignores uuids that are not references at all", () => {
380
+ /*
381
+ * Step ids, criteria ids and incident template ids are uuids too and
382
+ * match no record. The old regex-scraping guard could not tell them apart
383
+ * from a real reference, which is precisely why it could never check
384
+ * existence.
385
+ */
386
+ stubLookups({
387
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
388
+ });
389
+
390
+ return expect(
391
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
392
+ monitorSteps: steps({
393
+ defaultMonitorStatusId: OWN_STATUS_ID,
394
+ criteria: {
395
+ createIncidents: true,
396
+ incidents: [
397
+ { id: INCIDENT_TEMPLATE_ID, title: "Down", description: "" },
398
+ ],
399
+ },
400
+ }),
401
+ projectId: PROJECT_ID,
402
+ }),
403
+ ).resolves.toBeUndefined();
404
+ });
130
405
  });
131
406
 
132
- it("accepts records that belong to the monitor's own project", async () => {
133
- stubLookups({
134
- monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
407
+ describe("ids the record already holds", () => {
408
+ it("lets an update through when the dangling id was already stored", () => {
409
+ /*
410
+ * These ids have no foreign key behind them, so one really can end up
411
+ * dangling through no fault of the person now saving — deleting a monitor
412
+ * status does not rewrite the criteria that name it. The dashboard
413
+ * re-submits the whole blob on every save, so a strict check would refuse
414
+ * every edit of such a monitor, including renaming it.
415
+ */
416
+ stubLookups({
417
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
418
+ });
419
+
420
+ const stored: JSONObject = steps({
421
+ defaultMonitorStatusId: OWN_STATUS_ID,
422
+ criteria: {
423
+ monitorStatusId: UNKNOWN_STATUS_ID,
424
+ changeMonitorStatus: true,
425
+ },
426
+ });
427
+
428
+ return expect(
429
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
430
+ monitorSteps: stored,
431
+ projectId: PROJECT_ID,
432
+ alreadyStoredMonitorSteps: stored,
433
+ }),
434
+ ).resolves.toBeUndefined();
135
435
  });
136
436
 
137
- await expect(
138
- MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
139
- monitorSteps: stepsReferencing([OWN_STATUS_ID]),
437
+ it("grandfathers regardless of the case the id is re-sent in", () => {
438
+ /*
439
+ * ObjectID keeps whatever case it was handed, so the same id can be
440
+ * stored as Postgres rendered it (lower) and re-sent by a caller in
441
+ * upper. Treating that as a new id would refuse the very save this
442
+ * exemption exists to allow.
443
+ */
444
+ stubLookups({
445
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
446
+ });
447
+
448
+ return expect(
449
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
450
+ monitorSteps: steps({
451
+ defaultMonitorStatusId: OWN_STATUS_ID,
452
+ criteria: {
453
+ monitorStatusId: UNKNOWN_STATUS_ID.toUpperCase(),
454
+ changeMonitorStatus: true,
455
+ },
456
+ }),
457
+ projectId: PROJECT_ID,
458
+ alreadyStoredMonitorSteps: steps({
459
+ defaultMonitorStatusId: OWN_STATUS_ID,
460
+ criteria: {
461
+ monitorStatusId: UNKNOWN_STATUS_ID,
462
+ changeMonitorStatus: true,
463
+ },
464
+ }),
465
+ }),
466
+ ).resolves.toBeUndefined();
467
+ });
468
+
469
+ it("still refuses a dangling id the update is introducing", () => {
470
+ stubLookups({
471
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
472
+ });
473
+
474
+ return expect(
475
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
476
+ monitorSteps: steps({
477
+ defaultMonitorStatusId: OWN_STATUS_ID,
478
+ criteria: {
479
+ monitorStatusId: UNKNOWN_STATUS_ID,
480
+ changeMonitorStatus: true,
481
+ },
482
+ }),
483
+ projectId: PROJECT_ID,
484
+ alreadyStoredMonitorSteps: steps({
485
+ defaultMonitorStatusId: OWN_STATUS_ID,
486
+ criteria: {
487
+ monitorStatusId: OWN_STATUS_ID,
488
+ changeMonitorStatus: true,
489
+ },
490
+ }),
491
+ }),
492
+ ).rejects.toThrow("do not exist");
493
+ });
494
+
495
+ it("lets an update through when the cross-project id was already stored", () => {
496
+ /*
497
+ * The 1785240000000 repair migration deliberately left behind the
498
+ * cross-project ids it could not map to a local equivalent, and there is
499
+ * no migration for the labels / teams / on-call policies this guard now
500
+ * also covers. Refusing them here would leave those monitors permanently
501
+ * unsaveable — including a rename — with no way out.
502
+ *
503
+ * Letting one ride is safe now in a way it was not before: the writes
504
+ * that would turn it into a real foreign key are guarded at their own
505
+ * choke points, so it can no longer make the other project undeletable.
506
+ */
507
+ stubLookups({
508
+ monitorStatuses: [
509
+ monitorStatus(OWN_STATUS_ID, PROJECT_ID),
510
+ monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID),
511
+ ],
512
+ });
513
+
514
+ const stored: JSONObject = steps({
515
+ defaultMonitorStatusId: OWN_STATUS_ID,
516
+ criteria: {
517
+ monitorStatusId: FOREIGN_STATUS_ID,
518
+ changeMonitorStatus: true,
519
+ },
520
+ });
521
+
522
+ return expect(
523
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
524
+ monitorSteps: stored,
525
+ projectId: PROJECT_ID,
526
+ alreadyStoredMonitorSteps: stored,
527
+ }),
528
+ ).resolves.toBeUndefined();
529
+ });
530
+
531
+ it("still refuses a cross-project id the update is introducing", () => {
532
+ stubLookups({
533
+ monitorStatuses: [
534
+ monitorStatus(OWN_STATUS_ID, PROJECT_ID),
535
+ monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID),
536
+ ],
537
+ });
538
+
539
+ return expect(
540
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
541
+ monitorSteps: steps({
542
+ defaultMonitorStatusId: OWN_STATUS_ID,
543
+ criteria: {
544
+ monitorStatusId: FOREIGN_STATUS_ID,
545
+ changeMonitorStatus: true,
546
+ },
547
+ }),
548
+ projectId: PROJECT_ID,
549
+ alreadyStoredMonitorSteps: steps({
550
+ defaultMonitorStatusId: OWN_STATUS_ID,
551
+ }),
552
+ }),
553
+ ).rejects.toThrow("belong to a different project");
554
+ });
555
+
556
+ it("issues no query at all when the update introduces nothing new", () => {
557
+ /*
558
+ * Template sync pushes one blob onto every linked monitor. If each of
559
+ * them re-validated the ids it already had, one click would fan out into
560
+ * a lookup per model per monitor.
561
+ */
562
+ stubLookups({
563
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
564
+ });
565
+
566
+ const stored: JSONObject = steps({
567
+ defaultMonitorStatusId: OWN_STATUS_ID,
568
+ criteria: {
569
+ monitorStatusId: OWN_STATUS_ID,
570
+ changeMonitorStatus: true,
571
+ },
572
+ });
573
+
574
+ return MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
575
+ monitorSteps: stored,
140
576
  projectId: PROJECT_ID,
141
- }),
142
- ).resolves.toBeUndefined();
577
+ alreadyStoredMonitorSteps: stored,
578
+ }).then(() => {
579
+ expect(MonitorStatusService.findBy).not.toHaveBeenCalled();
580
+ });
581
+ });
143
582
  });
144
583
 
145
- it("ignores uuids that are not project-scoped records", async () => {
146
- // Step ids and criteria ids are uuids too, and match nothing.
147
- stubLookups({});
148
-
149
- await expect(
150
- MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
151
- monitorSteps: stepsReferencing([
152
- OWN_STATUS_ID,
153
- "679a113f-669e-4b0f-b3db-31eb2a794ed5",
154
- "ff11aa22-bb33-cc44-dd55-000000000ee1",
155
- ]),
156
- projectId: PROJECT_ID,
157
- }),
158
- ).resolves.toBeUndefined();
584
+ describe("references that belong to another project", () => {
585
+ it("rejects a monitor status from another project", () => {
586
+ stubLookups({
587
+ monitorStatuses: [monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID)],
588
+ });
589
+
590
+ return expect(
591
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
592
+ monitorSteps: steps({
593
+ defaultMonitorStatusId: FOREIGN_STATUS_ID,
594
+ }),
595
+ projectId: PROJECT_ID,
596
+ }),
597
+ ).rejects.toThrow(
598
+ 'Monitor Status (default monitor status) "Operational"',
599
+ );
600
+ });
601
+
602
+ it("rejects a label from another project even though labels are never required to exist", () => {
603
+ stubLookups({
604
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
605
+ incidentSeverities: [
606
+ incidentSeverity(OWN_INCIDENT_SEVERITY_ID, PROJECT_ID),
607
+ ],
608
+ labels: [label(FOREIGN_LABEL_ID, OTHER_PROJECT_ID)],
609
+ });
610
+
611
+ return expect(
612
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
613
+ monitorSteps: steps({
614
+ defaultMonitorStatusId: OWN_STATUS_ID,
615
+ criteria: {
616
+ createIncidents: false,
617
+ incidents: [
618
+ {
619
+ id: INCIDENT_TEMPLATE_ID,
620
+ title: "Down",
621
+ description: "",
622
+ labelIds: [objectIdJSON(FOREIGN_LABEL_ID)],
623
+ },
624
+ ],
625
+ },
626
+ }),
627
+ projectId: PROJECT_ID,
628
+ }),
629
+ ).rejects.toThrow("belong to a different project");
630
+ });
631
+
632
+ it("names every offending record in the message", () => {
633
+ stubLookups({
634
+ monitorStatuses: [monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID)],
635
+ });
636
+
637
+ return expect(
638
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
639
+ monitorSteps: steps({
640
+ defaultMonitorStatusId: FOREIGN_STATUS_ID,
641
+ criteria: {
642
+ monitorStatusId: UNKNOWN_STATUS_ID,
643
+ changeMonitorStatus: true,
644
+ },
645
+ }),
646
+ projectId: PROJECT_ID,
647
+ }),
648
+ ).rejects.toThrow(
649
+ /belong to a different project: Monitor Status \(default monitor status\) "Operational"[\s\S]*do not exist: Monitor Status/,
650
+ );
651
+ });
159
652
  });
160
653
 
161
- it("does no lookups when there are no monitorSteps or no project", async () => {
162
- /*
163
- * stubLookups installs the spy, so the lookup is asserted on the service
164
- * method itself. Annotating the spy handle is not an option here: this
165
- * file takes jest from @jest/globals, so spyOn returns jest-mock's type
166
- * while the jest.SpyInstance namespace resolves to @types/jest's.
167
- */
168
- stubLookups({});
654
+ describe("when there is nothing to check", () => {
655
+ it("accepts records that belong to the monitor's own project", () => {
656
+ stubLookups({
657
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
658
+ });
169
659
 
170
- await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
171
- monitorSteps: undefined,
172
- projectId: PROJECT_ID,
660
+ return expect(
661
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
662
+ monitorSteps: steps({
663
+ defaultMonitorStatusId: OWN_STATUS_ID,
664
+ criteria: {
665
+ monitorStatusId: OWN_STATUS_ID,
666
+ changeMonitorStatus: true,
667
+ },
668
+ }),
669
+ projectId: PROJECT_ID,
670
+ }),
671
+ ).resolves.toBeUndefined();
173
672
  });
174
- await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
175
- monitorSteps: stepsReferencing([OWN_STATUS_ID]),
176
- projectId: undefined,
673
+
674
+ it("does no lookups when there are no monitorSteps or no project", async () => {
675
+ /*
676
+ * stubLookups installs the spy, so the lookup is asserted on the service
677
+ * method itself. Annotating the spy handle is not an option here: this
678
+ * file takes jest from @jest/globals, so spyOn returns jest-mock's type
679
+ * while the jest.SpyInstance namespace resolves to @types/jest's.
680
+ */
681
+ stubLookups({});
682
+
683
+ await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
684
+ monitorSteps: undefined,
685
+ projectId: PROJECT_ID,
686
+ });
687
+ await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
688
+ monitorSteps: steps({ defaultMonitorStatusId: OWN_STATUS_ID }),
689
+ projectId: undefined,
690
+ });
691
+
692
+ expect(MonitorStatusService.findBy).not.toHaveBeenCalled();
693
+ });
694
+
695
+ it("does no lookups when monitorSteps holds no references at all", () => {
696
+ stubLookups({});
697
+
698
+ return MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
699
+ monitorSteps: steps({}),
700
+ projectId: PROJECT_ID,
701
+ }).then(() => {
702
+ expect(MonitorStatusService.findBy).not.toHaveBeenCalled();
703
+ });
177
704
  });
178
705
 
179
- expect(MonitorStatusService.findBy).not.toHaveBeenCalled();
706
+ it("only queries the models that are actually referenced", () => {
707
+ /*
708
+ * Every referenced model costs a round trip on the monitor write path, so
709
+ * a monitor that names no label must not pay for a Label lookup.
710
+ */
711
+ stubLookups({
712
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
713
+ });
714
+
715
+ return MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
716
+ monitorSteps: steps({ defaultMonitorStatusId: OWN_STATUS_ID }),
717
+ projectId: PROJECT_ID,
718
+ }).then(() => {
719
+ expect(MonitorStatusService.findBy).toHaveBeenCalledTimes(1);
720
+ expect(LabelService.findBy).not.toHaveBeenCalled();
721
+ expect(OnCallDutyPolicyService.findBy).not.toHaveBeenCalled();
722
+ expect(IncidentSeverityService.findBy).not.toHaveBeenCalled();
723
+ expect(AlertSeverityService.findBy).not.toHaveBeenCalled();
724
+ });
725
+ });
180
726
  });
181
727
  });