@oneuptime/common 12.0.1 → 12.0.3

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 (385) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  2. package/Models/DatabaseModels/Host.ts +12 -4
  3. package/Models/DatabaseModels/Index.ts +2 -0
  4. package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
  5. package/Models/DatabaseModels/KubernetesResource.ts +8 -7
  6. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  7. package/Models/DatabaseModels/PodmanResource.ts +6 -6
  8. package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
  9. package/Models/DatabaseModels/StatusPage.ts +95 -0
  10. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  11. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  12. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  13. package/Server/API/BaseAPI.ts +13 -2
  14. package/Server/API/DashboardAPI.ts +39 -0
  15. package/Server/API/TeamMemberAPI.ts +85 -0
  16. package/Server/EnvironmentConfig.ts +31 -1
  17. package/Server/Infrastructure/GlobalCache.ts +115 -0
  18. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  25. package/Server/Middleware/ProjectAuthorization.ts +66 -4
  26. package/Server/Services/AlertStateTimelineService.ts +20 -0
  27. package/Server/Services/BillingService.ts +600 -51
  28. package/Server/Services/CephClusterService.ts +27 -45
  29. package/Server/Services/CephResourceService.ts +43 -4
  30. package/Server/Services/CloudResourceService.ts +24 -42
  31. package/Server/Services/DatabaseService.ts +198 -6
  32. package/Server/Services/DockerHostService.ts +20 -38
  33. package/Server/Services/DockerResourceService.ts +62 -8
  34. package/Server/Services/DockerSwarmClusterService.ts +28 -46
  35. package/Server/Services/DockerSwarmResourceService.ts +53 -4
  36. package/Server/Services/HostService.ts +47 -57
  37. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  38. package/Server/Services/Index.ts +2 -0
  39. package/Server/Services/IoTDeviceService.ts +1 -17
  40. package/Server/Services/IoTFleetService.ts +20 -38
  41. package/Server/Services/KubernetesClusterService.ts +18 -36
  42. package/Server/Services/KubernetesContainerService.ts +50 -9
  43. package/Server/Services/KubernetesResourceService.ts +53 -7
  44. package/Server/Services/LabelService.ts +153 -1
  45. package/Server/Services/MetricTypeService.ts +84 -0
  46. package/Server/Services/MonitorService.ts +146 -14
  47. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  48. package/Server/Services/MonitorTemplateService.ts +65 -0
  49. package/Server/Services/PodmanHostService.ts +20 -38
  50. package/Server/Services/PodmanResourceService.ts +62 -8
  51. package/Server/Services/ProjectService.ts +165 -35
  52. package/Server/Services/ProxmoxClusterService.ts +24 -42
  53. package/Server/Services/ProxmoxResourceService.ts +43 -4
  54. package/Server/Services/RumApplicationService.ts +20 -38
  55. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  56. package/Server/Services/ServerlessFunctionService.ts +25 -43
  57. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
  58. package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
  59. package/Server/Services/ServiceService.ts +299 -73
  60. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  61. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  62. package/Server/Services/StatusPageService.ts +95 -36
  63. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
  64. package/Server/Services/StatusPageSubscriberService.ts +3 -0
  65. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  66. package/Server/Services/TelemetryEntityService.ts +1 -0
  67. package/Server/Types/Database/JSONColumnQuery.ts +621 -0
  68. package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
  69. package/Server/Types/Database/QueryHelper.ts +17 -32
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
  72. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  73. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  74. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  75. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -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/UsersTableGroupsByPerson.test.tsx +448 -0
  82. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  83. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  84. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  85. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  86. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
  87. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  88. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
  89. package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
  90. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  91. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
  92. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
  93. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
  94. package/Tests/Server/Services/BillingService.test.ts +78 -3
  95. package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
  96. package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
  97. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  98. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  99. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
  100. package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
  101. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +658 -0
  102. package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
  103. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
  104. package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
  105. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  106. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  107. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  108. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
  109. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
  110. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +636 -0
  111. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
  112. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
  113. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  114. package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -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/StatusPageSubscriberReport.test.ts +29 -13
  119. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
  120. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
  121. package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
  122. package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
  123. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  124. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  125. package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  128. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  129. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  130. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  131. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  132. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  133. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  134. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  135. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  136. package/Tests/Types/Decimal.test.ts +37 -0
  137. package/Tests/Types/Events/Recurring.test.ts +157 -0
  138. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  139. package/Tests/Types/Port.test.ts +38 -0
  140. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  141. package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
  142. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
  143. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  144. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  145. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
  146. package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
  147. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  148. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  149. package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
  150. package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
  151. package/Types/CustomField/CustomFieldDefinition.ts +24 -0
  152. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  153. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  154. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  155. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  156. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  157. package/Types/Database/DatabaseProperty.ts +26 -0
  158. package/Types/Database/NumericColumnValue.ts +128 -0
  159. package/Types/Date.ts +112 -0
  160. package/Types/Decimal.ts +16 -4
  161. package/Types/Events/Recurring.ts +99 -1
  162. package/Types/Permission.ts +48 -0
  163. package/Types/Port.ts +16 -4
  164. package/Types/StatusPage/StatusPageReport.ts +11 -0
  165. package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
  166. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  167. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  168. package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
  169. package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
  170. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
  171. package/UI/Utils/TeamMembersByUser.ts +302 -0
  172. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  173. package/Utils/Dashboard/Components/Index.ts +7 -0
  174. package/Utils/StatusPage/ReportPeriod.ts +236 -0
  175. package/Utils/Telemetry/HostIpAddresses.ts +98 -0
  176. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  177. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  178. package/build/dist/Models/DatabaseModels/Host.js +12 -4
  179. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  180. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  181. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  182. package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
  183. package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
  184. package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
  185. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  187. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
  189. package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
  191. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
  193. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  195. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  196. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  197. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  199. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  200. package/build/dist/Server/API/BaseAPI.js +11 -1
  201. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  202. package/build/dist/Server/API/DashboardAPI.js +39 -0
  203. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  204. package/build/dist/Server/API/TeamMemberAPI.js +57 -2
  205. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  206. package/build/dist/Server/EnvironmentConfig.js +27 -1
  207. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  208. package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
  209. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  210. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  211. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  212. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
  213. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
  214. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
  215. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
  216. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
  217. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
  218. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
  219. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
  220. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  221. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  224. package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
  225. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  226. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  227. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  228. package/build/dist/Server/Services/BillingService.js +466 -36
  229. package/build/dist/Server/Services/BillingService.js.map +1 -1
  230. package/build/dist/Server/Services/CephClusterService.js +26 -39
  231. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  232. package/build/dist/Server/Services/CephResourceService.js +28 -3
  233. package/build/dist/Server/Services/CephResourceService.js.map +1 -1
  234. package/build/dist/Server/Services/CloudResourceService.js +23 -36
  235. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  236. package/build/dist/Server/Services/DatabaseService.js +155 -2
  237. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  238. package/build/dist/Server/Services/DockerHostService.js +19 -32
  239. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  240. package/build/dist/Server/Services/DockerResourceService.js +34 -4
  241. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  242. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -40
  243. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  244. package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
  245. package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
  246. package/build/dist/Server/Services/HostService.js +58 -64
  247. package/build/dist/Server/Services/HostService.js.map +1 -1
  248. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  249. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  250. package/build/dist/Server/Services/Index.js +2 -0
  251. package/build/dist/Server/Services/Index.js.map +1 -1
  252. package/build/dist/Server/Services/IoTDeviceService.js +1 -8
  253. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  254. package/build/dist/Server/Services/IoTFleetService.js +19 -32
  255. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  256. package/build/dist/Server/Services/KubernetesClusterService.js +17 -30
  257. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  258. package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
  259. package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
  260. package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
  261. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  262. package/build/dist/Server/Services/LabelService.js +127 -0
  263. package/build/dist/Server/Services/LabelService.js.map +1 -1
  264. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  265. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  266. package/build/dist/Server/Services/MonitorService.js +128 -9
  267. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  268. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  269. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  270. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  271. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  272. package/build/dist/Server/Services/PodmanHostService.js +19 -32
  273. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  274. package/build/dist/Server/Services/PodmanResourceService.js +34 -4
  275. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  276. package/build/dist/Server/Services/ProjectService.js +135 -21
  277. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  278. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -36
  279. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  280. package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
  281. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  282. package/build/dist/Server/Services/RumApplicationService.js +19 -32
  283. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  284. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  285. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  286. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -37
  287. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  288. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
  289. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
  290. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
  291. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  292. package/build/dist/Server/Services/ServiceService.js +236 -61
  293. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  294. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  295. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  296. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  297. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  298. package/build/dist/Server/Services/StatusPageService.js +69 -46
  299. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  300. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
  301. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  302. package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
  303. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  305. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  306. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  307. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  308. package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
  309. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
  310. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
  311. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
  312. package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
  313. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  314. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  315. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  316. package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
  317. package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
  318. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  320. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  322. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  326. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  327. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  328. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  329. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  330. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  331. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  332. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  333. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  334. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  335. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  336. package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
  337. package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
  338. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  340. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  342. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  344. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  346. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  348. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  349. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  350. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  351. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  352. package/build/dist/Types/Date.js +68 -0
  353. package/build/dist/Types/Date.js.map +1 -1
  354. package/build/dist/Types/Decimal.js +14 -3
  355. package/build/dist/Types/Decimal.js.map +1 -1
  356. package/build/dist/Types/Events/Recurring.js +63 -0
  357. package/build/dist/Types/Events/Recurring.js.map +1 -1
  358. package/build/dist/Types/Permission.js +42 -0
  359. package/build/dist/Types/Permission.js.map +1 -1
  360. package/build/dist/Types/Port.js +15 -5
  361. package/build/dist/Types/Port.js.map +1 -1
  362. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
  363. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
  364. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  365. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  366. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  367. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  368. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  369. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
  370. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  371. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
  372. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
  373. package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
  374. package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
  375. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  376. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  377. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  378. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  379. package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
  380. package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
  381. package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
  382. package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
  383. package/jest.config.json +2 -0
  384. package/package.json +1 -1
  385. package/tsconfig.json +12 -1
@@ -0,0 +1,267 @@
1
+ import {
2
+ afterAll,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ jest,
8
+ } from "@jest/globals";
9
+ import APIKeyAccessPermission from "../../../../Server/Utils/APIKey/AccessPermission";
10
+ import ApiKeyPermissionService from "../../../../Server/Services/ApiKeyPermissionService";
11
+ import APIKeyPermission from "../../../../Models/DatabaseModels/ApiKeyPermission";
12
+ import Label from "../../../../Models/DatabaseModels/Label";
13
+ import ObjectID from "../../../../Types/ObjectID";
14
+ import Permission, {
15
+ UserGlobalAccessPermission,
16
+ UserPermission,
17
+ UserTenantAccessPermission,
18
+ } from "../../../../Types/Permission";
19
+ import { getJestSpyOn } from "../../../Spy";
20
+
21
+ /*
22
+ * This class turns an API key into the permission set a request runs under, so
23
+ * every method here is a privilege-granting decision. The security invariant
24
+ * that must hold across all of them: a *default* (ordinary) API key is never
25
+ * silently handed ProjectOwner, while a *master* key deliberately is. A
26
+ * regression that leaks ProjectOwner into the default path would give every API
27
+ * key full control of the project; one that dropped it from the master path
28
+ * would break trusted internal automation. Both directions are pinned below.
29
+ *
30
+ * getApiTenantAccessPermission also reshapes stored permission rows into the
31
+ * runtime UserPermission shape (labels -> labelIds, block flag preserved). That
32
+ * mapping is where a dropped label would quietly widen a scoped permission, so
33
+ * it is tested against a spied service rather than a live database.
34
+ */
35
+
36
+ const projectId: ObjectID = ObjectID.generate();
37
+ const apiKeyId: ObjectID = ObjectID.generate();
38
+
39
+ const permissionValues: (
40
+ permissions: Array<UserPermission>,
41
+ ) => Array<Permission> = (
42
+ permissions: Array<UserPermission>,
43
+ ): Array<Permission> => {
44
+ return permissions.map((p: UserPermission) => {
45
+ return p.permission;
46
+ });
47
+ };
48
+
49
+ describe("APIKeyAccessPermission.getDefaultApiGlobalPermission", () => {
50
+ it("grants Public, User and CurrentUser for the project", async () => {
51
+ const perm: UserGlobalAccessPermission =
52
+ await APIKeyAccessPermission.getDefaultApiGlobalPermission(projectId);
53
+
54
+ expect(perm._type).toBe("UserGlobalAccessPermission");
55
+ expect(
56
+ perm.projectIds.map((p: ObjectID) => {
57
+ return p.toString();
58
+ }),
59
+ ).toEqual([projectId.toString()]);
60
+ expect(perm.globalPermissions).toEqual([
61
+ Permission.Public,
62
+ Permission.User,
63
+ Permission.CurrentUser,
64
+ ]);
65
+ });
66
+
67
+ it("does NOT grant ProjectOwner to an ordinary key", async () => {
68
+ // The core boundary: a default key must never be an owner.
69
+ const perm: UserGlobalAccessPermission =
70
+ await APIKeyAccessPermission.getDefaultApiGlobalPermission(projectId);
71
+ expect(perm.globalPermissions).not.toContain(Permission.ProjectOwner);
72
+ });
73
+ });
74
+
75
+ describe("APIKeyAccessPermission.getMasterKeyApiGlobalPermission", () => {
76
+ it("grants everything the default key has, plus ProjectOwner", async () => {
77
+ const master: UserGlobalAccessPermission =
78
+ await APIKeyAccessPermission.getMasterKeyApiGlobalPermission(projectId);
79
+ const def: UserGlobalAccessPermission =
80
+ await APIKeyAccessPermission.getDefaultApiGlobalPermission(projectId);
81
+
82
+ // Superset: no default grant is lost when elevating to master.
83
+ for (const p of def.globalPermissions) {
84
+ expect(master.globalPermissions).toContain(p);
85
+ }
86
+ // The one deliberate elevation.
87
+ expect(master.globalPermissions).toContain(Permission.ProjectOwner);
88
+ });
89
+
90
+ it("scopes to exactly the one project", async () => {
91
+ const master: UserGlobalAccessPermission =
92
+ await APIKeyAccessPermission.getMasterKeyApiGlobalPermission(projectId);
93
+ expect(
94
+ master.projectIds.map((p: ObjectID) => {
95
+ return p.toString();
96
+ }),
97
+ ).toEqual([projectId.toString()]);
98
+ });
99
+ });
100
+
101
+ describe("APIKeyAccessPermission.getMasterApiTenantAccessPermission", () => {
102
+ it("elevates the default tenant permission with ProjectOwner", async () => {
103
+ const perm: UserTenantAccessPermission =
104
+ await APIKeyAccessPermission.getMasterApiTenantAccessPermission(
105
+ projectId,
106
+ );
107
+
108
+ expect(perm._type).toBe("UserTenantAccessPermission");
109
+ expect(perm.projectId.toString()).toBe(projectId.toString());
110
+ expect(permissionValues(perm.permissions)).toContain(
111
+ Permission.ProjectOwner,
112
+ );
113
+ // The default tenant baseline is retained alongside the elevation.
114
+ expect(permissionValues(perm.permissions)).toContain(
115
+ Permission.CurrentUser,
116
+ );
117
+ });
118
+ });
119
+
120
+ describe("APIKeyAccessPermission.getApiTenantAccessPermission", () => {
121
+ const spyFindBy: jest.SpyInstance = getJestSpyOn(
122
+ ApiKeyPermissionService,
123
+ "findBy",
124
+ );
125
+
126
+ beforeEach(() => {
127
+ jest.clearAllMocks();
128
+ });
129
+
130
+ afterAll(() => {
131
+ jest.restoreAllMocks();
132
+ });
133
+
134
+ const makeRow: (data: {
135
+ permission: Permission;
136
+ labelIds?: Array<ObjectID>;
137
+ isBlockPermission?: boolean;
138
+ }) => APIKeyPermission = (data: {
139
+ permission: Permission;
140
+ labelIds?: Array<ObjectID>;
141
+ isBlockPermission?: boolean;
142
+ }): APIKeyPermission => {
143
+ const row: APIKeyPermission = new APIKeyPermission();
144
+ row.permission = data.permission;
145
+ if (data.labelIds) {
146
+ row.labels = data.labelIds.map((id: ObjectID) => {
147
+ const label: Label = new Label();
148
+ label.id = id;
149
+ return label;
150
+ });
151
+ }
152
+ row.isBlockPermission = data.isBlockPermission ?? false;
153
+ return row;
154
+ };
155
+
156
+ it("queries the permissions for exactly this api key", async () => {
157
+ spyFindBy.mockResolvedValue([] as never);
158
+
159
+ await APIKeyAccessPermission.getApiTenantAccessPermission(
160
+ projectId,
161
+ apiKeyId,
162
+ );
163
+
164
+ expect(spyFindBy).toHaveBeenCalledTimes(1);
165
+ const arg: { query: { apiKeyId: ObjectID } } = spyFindBy.mock
166
+ .calls[0]![0] as { query: { apiKeyId: ObjectID } };
167
+ expect(arg.query.apiKeyId.toString()).toBe(apiKeyId.toString());
168
+ });
169
+
170
+ it("returns just the default baseline when the key has no permissions", async () => {
171
+ spyFindBy.mockResolvedValue([] as never);
172
+
173
+ const perm: UserTenantAccessPermission =
174
+ await APIKeyAccessPermission.getApiTenantAccessPermission(
175
+ projectId,
176
+ apiKeyId,
177
+ );
178
+
179
+ // No stored rows -> no ProjectOwner. An empty key is not an owner.
180
+ expect(permissionValues(perm.permissions)).not.toContain(
181
+ Permission.ProjectOwner,
182
+ );
183
+ expect(permissionValues(perm.permissions)).toContain(
184
+ Permission.CurrentUser,
185
+ );
186
+ });
187
+
188
+ it("maps each stored permission row onto the tenant permission", async () => {
189
+ const labelId: ObjectID = ObjectID.generate();
190
+ spyFindBy.mockResolvedValue([
191
+ makeRow({
192
+ permission: Permission.CreateProjectIncident,
193
+ labelIds: [labelId],
194
+ isBlockPermission: false,
195
+ }),
196
+ ] as never);
197
+
198
+ const perm: UserTenantAccessPermission =
199
+ await APIKeyAccessPermission.getApiTenantAccessPermission(
200
+ projectId,
201
+ apiKeyId,
202
+ );
203
+
204
+ const mapped: UserPermission | undefined = perm.permissions.find(
205
+ (p: UserPermission) => {
206
+ return p.permission === Permission.CreateProjectIncident;
207
+ },
208
+ );
209
+ expect(mapped).toBeDefined();
210
+ /*
211
+ * The label scope must survive the mapping, or the permission silently
212
+ * widens from "these labels" to "all".
213
+ */
214
+ expect(
215
+ mapped!.labelIds.map((l: ObjectID) => {
216
+ return l.toString();
217
+ }),
218
+ ).toEqual([labelId.toString()]);
219
+ });
220
+
221
+ it("preserves the block flag so a deny row stays a deny row", async () => {
222
+ spyFindBy.mockResolvedValue([
223
+ makeRow({
224
+ permission: Permission.CreateProjectIncident,
225
+ labelIds: [],
226
+ isBlockPermission: true,
227
+ }),
228
+ ] as never);
229
+
230
+ const perm: UserTenantAccessPermission =
231
+ await APIKeyAccessPermission.getApiTenantAccessPermission(
232
+ projectId,
233
+ apiKeyId,
234
+ );
235
+
236
+ const mapped: UserPermission | undefined = perm.permissions.find(
237
+ (p: UserPermission) => {
238
+ return p.permission === Permission.CreateProjectIncident;
239
+ },
240
+ );
241
+ // Flipping a block permission to an allow would invert an admin's intent.
242
+ expect(mapped!.isBlockPermission).toBe(true);
243
+ });
244
+
245
+ it("treats a row with no labels as an empty (unscoped) label set", async () => {
246
+ const row: APIKeyPermission = new APIKeyPermission();
247
+ row.permission = Permission.CreateProjectIncident;
248
+ // labels intentionally left undefined.
249
+ row.isBlockPermission = false;
250
+ spyFindBy.mockResolvedValue([row] as never);
251
+
252
+ const perm: UserTenantAccessPermission =
253
+ await APIKeyAccessPermission.getApiTenantAccessPermission(
254
+ projectId,
255
+ apiKeyId,
256
+ );
257
+
258
+ const mapped: UserPermission | undefined = perm.permissions.find(
259
+ (p: UserPermission) => {
260
+ return p.permission === Permission.CreateProjectIncident;
261
+ },
262
+ );
263
+ expect(mapped).toBeDefined();
264
+ // No crash on undefined labels, and it becomes [] rather than undefined.
265
+ expect(mapped!.labelIds).toEqual([]);
266
+ });
267
+ });
@@ -1,25 +1,36 @@
1
1
  import ProjectScopedReferenceValidator from "../../../../Server/Utils/Database/ProjectScopedReferenceValidator";
2
+ import DatabaseService from "../../../../Server/Services/DatabaseService";
2
3
  import IncidentSeverityService from "../../../../Server/Services/IncidentSeverityService";
3
4
  import IncidentStateService from "../../../../Server/Services/IncidentStateService";
4
5
  import MonitorStatusService from "../../../../Server/Services/MonitorStatusService";
6
+ import UserService from "../../../../Server/Services/UserService";
7
+ import DatabaseBaseModel from "../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
5
8
  import IncidentSeverity from "../../../../Models/DatabaseModels/IncidentSeverity";
6
9
  import IncidentState from "../../../../Models/DatabaseModels/IncidentState";
7
10
  import MonitorStatus from "../../../../Models/DatabaseModels/MonitorStatus";
11
+ import User from "../../../../Models/DatabaseModels/User";
8
12
  import BadDataException from "../../../../Types/Exception/BadDataException";
13
+ import Name from "../../../../Types/Name";
9
14
  import ObjectID from "../../../../Types/ObjectID";
10
15
  import { afterEach, describe, expect, it, jest } from "@jest/globals";
11
16
 
12
17
  /*
13
18
  * Contract under test: an incident (or alert, or scheduled maintenance event)
14
19
  * stores the id of its state, its severity and the monitor status it switches
15
- * monitors to. Saving one that belongs to a *different* project is what leaves
16
- * the referenced project undeletable — deleting that project cascades into the
17
- * record, the row here still points at it, and the ON DELETE NO ACTION foreign
18
- * key refuses the delete with 23503.
20
+ * monitors to. Two things can be wrong with one of those ids, and the guard
21
+ * refuses both.
19
22
  *
20
- * The check has to stay narrow in the same way MonitorStepsProjectValidator
21
- * is: an id that matches no row at all must keep saving, otherwise a record
22
- * that already points at something deleted can never be saved back into shape.
23
+ * An id belonging to a *different* project is what leaves the referenced
24
+ * project undeletable deleting that project cascades into the record, the row
25
+ * here still points at it, and the ON DELETE NO ACTION foreign key refuses the
26
+ * delete with 23503.
27
+ *
28
+ * An id that exists NOWHERE is issue #3039: the REST API took any uuid for
29
+ * these, and the write then failed as a raw foreign key violation deep in
30
+ * Postgres — or, for ids stored in a JSON blob with no foreign key behind them,
31
+ * saved fine and blew up much later inside the probe worker. Rejecting it here
32
+ * names the id instead. Callers whose reference is allowed to dangle opt out
33
+ * per reference with `mustExist: false`.
23
34
  */
24
35
 
25
36
  const PROJECT_ID: ObjectID = new ObjectID(
@@ -34,6 +45,7 @@ const FOREIGN_SEVERITY_ID: string = "cfc2f04f-79cb-4344-8c54-dafe5e3a290c";
34
45
  const FOREIGN_STATE_ID: string = "a2eb67d4-bd2e-4186-9187-dad799c9316c";
35
46
  const FOREIGN_STATUS_ID: string = "1a1cf3f2-0e35-4a1e-98a1-3f0f8b5f7f9e";
36
47
  const UNKNOWN_ID: string = "9c0ba0b3-2f8e-4c02-a8d5-6a4d2f5b9c11";
48
+ const OWN_USER_ID: string = "7c2f6b40-6a1b-4f18-9d0e-2c5ba2d3f0a7";
37
49
 
38
50
  const incidentSeverity: (
39
51
  id: string,
@@ -172,17 +184,200 @@ describe("ProjectScopedReferenceValidator", () => {
172
184
  ).resolves.toBeUndefined();
173
185
  });
174
186
 
175
- it("leaves an id that matches no record alone", async () => {
187
+ it("rejects an id that matches no record at all", async () => {
188
+ /*
189
+ * Issue #3039. This used to be allowed through on purpose, and the write
190
+ * then either came back as an opaque foreign key violation from Postgres or
191
+ * — in a JSON column with no foreign key — saved and failed later, at run
192
+ * time, with nothing reported to whoever supplied the id.
193
+ */
194
+ mockLookups({ incidentSeverities: [] });
195
+
196
+ await expect(
197
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
198
+ projectId: PROJECT_ID,
199
+ subject: "incident",
200
+ references: [
201
+ {
202
+ modelName: "Incident Severity",
203
+ id: UNKNOWN_ID,
204
+ service: IncidentSeverityService,
205
+ },
206
+ ],
207
+ }),
208
+ ).rejects.toThrow(BadDataException);
209
+ });
210
+
211
+ it("accepts an UPPERCASE uuid for a record Postgres reads back lower-cased", async () => {
212
+ /*
213
+ * ObjectID keeps whatever case it was handed — its validation regex is
214
+ * case-insensitive — and Postgres compares `uuid` by parsed value, so an
215
+ * uppercase id in the payload selects the row fine but reads back
216
+ * lower-cased. Comparing the two verbatim reported a record that plainly
217
+ * exists as missing, and the API answered "does not exist" for an id the
218
+ * caller had just read out of a GET response.
219
+ */
220
+ mockLookups({
221
+ incidentSeverities: [incidentSeverity(OWN_SEVERITY_ID, PROJECT_ID)],
222
+ });
223
+
224
+ await expect(
225
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
226
+ projectId: PROJECT_ID,
227
+ references: [
228
+ {
229
+ modelName: "Incident Severity",
230
+ id: OWN_SEVERITY_ID.toUpperCase(),
231
+ service: IncidentSeverityService,
232
+ },
233
+ ],
234
+ }),
235
+ ).resolves.toBeUndefined();
236
+ });
237
+
238
+ it("echoes the id as the caller wrote it when reporting it missing", async () => {
239
+ mockLookups({ incidentSeverities: [] });
240
+
241
+ await expect(
242
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
243
+ projectId: PROJECT_ID,
244
+ references: [
245
+ {
246
+ modelName: "Incident Severity",
247
+ id: UNKNOWN_ID.toUpperCase(),
248
+ service: IncidentSeverityService,
249
+ },
250
+ ],
251
+ }),
252
+ ).rejects.toThrow(`Incident Severity "${UNKNOWN_ID.toUpperCase()}"`);
253
+ });
254
+
255
+ it("names the missing id in the rejection so the caller can find it", async () => {
256
+ mockLookups({ incidentSeverities: [] });
257
+
258
+ await expect(
259
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
260
+ projectId: PROJECT_ID,
261
+ subject: "incident",
262
+ references: [
263
+ {
264
+ modelName: "Incident Severity",
265
+ id: UNKNOWN_ID,
266
+ service: IncidentSeverityService,
267
+ },
268
+ ],
269
+ }),
270
+ ).rejects.toThrow(
271
+ `This incident references records that do not exist: Incident Severity "${UNKNOWN_ID}". Please pick values that exist in this project and try again.`,
272
+ );
273
+ });
274
+
275
+ it("leaves a missing id alone when the caller opts out with mustExist false", async () => {
276
+ /*
277
+ * The opt-out exists for references with no foreign key behind them, where
278
+ * a stored id really can dangle (deleting a monitor status does not rewrite
279
+ * the monitor criteria that name it). Refusing those would stop a user
280
+ * saving their way out of a record that was already broken.
281
+ */
282
+ mockLookups({ incidentSeverities: [] });
283
+
284
+ await expect(
285
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
286
+ projectId: PROJECT_ID,
287
+ references: [
288
+ {
289
+ modelName: "Incident Severity",
290
+ id: UNKNOWN_ID,
291
+ service: IncidentSeverityService,
292
+ mustExist: false,
293
+ },
294
+ ],
295
+ }),
296
+ ).resolves.toBeUndefined();
297
+ });
298
+
299
+ it("keeps the strictest requirement when the same id arrives twice", async () => {
176
300
  /*
177
- * A record that already points at something deleted must stay saveable
178
- * refusing unknown ids would trap the user. The not-null and foreign key
179
- * checks report that case on their own.
301
+ * Two monitor criteria can name the same status, one operative and one not.
302
+ * They collapse to a single lookup, and the one that requires the record to
303
+ * exist must not be dropped by the one that does not.
180
304
  */
181
305
  mockLookups({ incidentSeverities: [] });
182
306
 
183
307
  await expect(
184
308
  ProjectScopedReferenceValidator.validateReferencesBelongToProject({
185
309
  projectId: PROJECT_ID,
310
+ references: [
311
+ {
312
+ modelName: "Incident Severity",
313
+ id: UNKNOWN_ID,
314
+ service: IncidentSeverityService,
315
+ mustExist: false,
316
+ },
317
+ {
318
+ modelName: "Incident Severity",
319
+ id: UNKNOWN_ID,
320
+ service: IncidentSeverityService,
321
+ mustExist: true,
322
+ },
323
+ ],
324
+ }),
325
+ ).rejects.toThrow("do not exist");
326
+ });
327
+
328
+ it("reports a foreign reference and a missing one in the same message", async () => {
329
+ /*
330
+ * A payload with several bad ids should not have to be fixed one
331
+ * round-trip at a time.
332
+ */
333
+ mockLookups({
334
+ incidentSeverities: [
335
+ incidentSeverity(FOREIGN_SEVERITY_ID, OTHER_PROJECT_ID),
336
+ ],
337
+ });
338
+
339
+ let message: string = "";
340
+
341
+ try {
342
+ await ProjectScopedReferenceValidator.validateReferencesBelongToProject({
343
+ projectId: PROJECT_ID,
344
+ subject: "incident",
345
+ references: [
346
+ {
347
+ modelName: "Incident Severity",
348
+ id: FOREIGN_SEVERITY_ID,
349
+ service: IncidentSeverityService,
350
+ },
351
+ {
352
+ modelName: "Incident Severity",
353
+ id: UNKNOWN_ID,
354
+ service: IncidentSeverityService,
355
+ },
356
+ ],
357
+ });
358
+ } catch (error) {
359
+ message = (error as BadDataException).message;
360
+ }
361
+
362
+ expect(message).toContain(
363
+ 'belong to a different project: Incident Severity "Critical"',
364
+ );
365
+ expect(message).toContain(
366
+ `do not exist: Incident Severity "${UNKNOWN_ID}"`,
367
+ );
368
+ });
369
+
370
+ it("does not require a record to exist when its project is unknown", async () => {
371
+ /*
372
+ * Same reason the cross-project half is skipped: root and internal writes
373
+ * do not always carry a project, and the guard must stay out of the way
374
+ * rather than guess.
375
+ */
376
+ const { severityFindBy } = mockLookups({ incidentSeverities: [] });
377
+
378
+ await expect(
379
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
380
+ projectId: undefined,
186
381
  references: [
187
382
  {
188
383
  modelName: "Incident Severity",
@@ -192,6 +387,52 @@ describe("ProjectScopedReferenceValidator", () => {
192
387
  ],
193
388
  }),
194
389
  ).resolves.toBeUndefined();
390
+
391
+ expect(severityFindBy).not.toHaveBeenCalled();
392
+ });
393
+
394
+ it("checks existence against a model that has no project of its own", async () => {
395
+ /*
396
+ * Monitor criteria can name owner *users*, and User has no tenant column.
397
+ * The cross-project comparison has to be skipped for those — comparing an
398
+ * absent projectId against the write's project would report every user as
399
+ * belonging to a different project — while the existence check still runs.
400
+ */
401
+ const user: User = new User();
402
+ user._id = OWN_USER_ID;
403
+ user.name = new Name("Alice");
404
+
405
+ jest.spyOn(UserService, "findBy").mockResolvedValue([user] as never);
406
+
407
+ await expect(
408
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
409
+ projectId: PROJECT_ID,
410
+ references: [
411
+ {
412
+ modelName: "User",
413
+ id: OWN_USER_ID,
414
+ service:
415
+ UserService as unknown as DatabaseService<DatabaseBaseModel>,
416
+ },
417
+ ],
418
+ }),
419
+ ).resolves.toBeUndefined();
420
+
421
+ jest.spyOn(UserService, "findBy").mockResolvedValue([] as never);
422
+
423
+ await expect(
424
+ ProjectScopedReferenceValidator.validateReferencesBelongToProject({
425
+ projectId: PROJECT_ID,
426
+ references: [
427
+ {
428
+ modelName: "User",
429
+ id: UNKNOWN_ID,
430
+ service:
431
+ UserService as unknown as DatabaseService<DatabaseBaseModel>,
432
+ },
433
+ ],
434
+ }),
435
+ ).rejects.toThrow(`User "${UNKNOWN_ID}"`);
195
436
  });
196
437
 
197
438
  it("reports every foreign reference in a single message", async () => {
@@ -272,9 +513,15 @@ describe("ProjectScopedReferenceValidator", () => {
272
513
  it("looks each model up once, with every id it was asked about", async () => {
273
514
  /*
274
515
  * These run on the incident create path, so three references must not mean
275
- * three round trips per model.
516
+ * three round trips per model. Both ids are returned so the existence check
517
+ * has nothing to complain about and the call reaches its end.
276
518
  */
277
- const { severityFindBy } = mockLookups({ incidentSeverities: [] });
519
+ const { severityFindBy } = mockLookups({
520
+ incidentSeverities: [
521
+ incidentSeverity(OWN_SEVERITY_ID, PROJECT_ID),
522
+ incidentSeverity(FOREIGN_SEVERITY_ID, PROJECT_ID),
523
+ ],
524
+ });
278
525
 
279
526
  await ProjectScopedReferenceValidator.validateReferencesBelongToProject({
280
527
  projectId: PROJECT_ID,