@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
@@ -0,0 +1,306 @@
1
+ import AllModelTypes from "../../../Models/DatabaseModels/Index";
2
+ import LogDropFilter from "../../../Models/DatabaseModels/LogDropFilter";
3
+ import TraceDropFilter from "../../../Models/DatabaseModels/TraceDropFilter";
4
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
5
+ import { isNumericTableColumnType } from "../../../Types/Database/NumericColumnValue";
6
+ import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
7
+ import { describe, expect, it } from "@jest/globals";
8
+ import { getMetadataArgsStorage } from "typeorm";
9
+ import { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
10
+ import { ValueTransformer } from "typeorm/decorator/options/ValueTransformer";
11
+
12
+ /*
13
+ * Contract under test — two schema-wide invariants about column
14
+ * transformers, not any one model's behaviour. Both are traps that are
15
+ * invisible at the call site: the model reads as a perfectly ordinary
16
+ * column, and the failure surfaces as a Postgres error a long way away.
17
+ *
18
+ * 1. A transformer must not fold `undefined` into `null`.
19
+ *
20
+ * TypeORM runs the transformer BEFORE deciding whether the caller
21
+ * supplied the column:
22
+ *
23
+ * InsertQueryBuilder.createColumnValueExpression
24
+ * -> driver.preparePersistentValue (runs transformer.to)
25
+ * -> if (value === undefined) expression += "DEFAULT"
26
+ *
27
+ * So on a `NOT NULL DEFAULT x` column, answering `null` makes the DEFAULT
28
+ * unreachable and every insert that omits the column dies on the not-null
29
+ * constraint. That is what shipped for the drop-filter drop counters, and
30
+ * creating any log drop filter in the dashboard returned HTTP 500:
31
+ *
32
+ * null value in column "droppedCount" of relation "LogDropFilter"
33
+ * violates not-null constraint
34
+ *
35
+ * github.com/OneUptime/oneuptime/issues/3026
36
+ *
37
+ * 2. A transformer on a number column must accept a raw number.
38
+ *
39
+ * `BaseModel.fromJSON` normalizes strings bound for number columns (see
40
+ * github.com/OneUptime/oneuptime/issues/3027), so a column declared
41
+ * `TableColumnType.Number` now reliably holds a number by the time the
42
+ * transformer sees it — even when its in-memory type is a wrapped object
43
+ * like `Port` or `Decimal`. A transformer that only knew how to handle
44
+ * its own class and a string answered `null` for a number, which wrote
45
+ * NULL over a value the user had just typed.
46
+ */
47
+
48
+ interface TransformerColumn {
49
+ entity: string;
50
+ property: string;
51
+ transformer: ValueTransformer;
52
+ defaultValue: unknown;
53
+ }
54
+
55
+ /*
56
+ * TypeORM accepts either a single transformer or an array applied in order.
57
+ * `to` runs last-to-first over an array, so the *last* entry is the one that
58
+ * sees the raw entity value.
59
+ */
60
+ function firstTransformerToRun(
61
+ transformer: ValueTransformer | Array<ValueTransformer>,
62
+ ): ValueTransformer {
63
+ if (Array.isArray(transformer)) {
64
+ return transformer[transformer.length - 1] as ValueTransformer;
65
+ }
66
+
67
+ return transformer;
68
+ }
69
+
70
+ function notNullColumnsWithADefaultAndATransformer(): Array<TransformerColumn> {
71
+ const columns: Array<TransformerColumn> = [];
72
+
73
+ for (const column of getMetadataArgsStorage()
74
+ .columns as Array<ColumnMetadataArgs>) {
75
+ const options: ColumnMetadataArgs["options"] = column.options || {};
76
+
77
+ if (!options.transformer) {
78
+ continue;
79
+ }
80
+
81
+ if (options.nullable !== false) {
82
+ continue;
83
+ }
84
+
85
+ if (options.default === undefined || options.default === null) {
86
+ continue;
87
+ }
88
+
89
+ columns.push({
90
+ entity:
91
+ (column.target as { name?: string })?.name || String(column.target),
92
+ property: column.propertyName,
93
+ transformer: firstTransformerToRun(options.transformer),
94
+ defaultValue: options.default,
95
+ });
96
+ }
97
+
98
+ return columns;
99
+ }
100
+
101
+ describe("NOT NULL columns that carry both a DEFAULT and a transformer", () => {
102
+ const columns: Array<TransformerColumn> =
103
+ notNullColumnsWithADefaultAndATransformer();
104
+
105
+ /*
106
+ * If this ever finds nothing, the filter above has drifted (a renamed
107
+ * option, a TypeORM upgrade) and the real assertion below is vacuously
108
+ * passing — which is the same as deleting it.
109
+ */
110
+ it("finds the drop-filter counters, so the scan itself is known to work", () => {
111
+ const scanned: Array<string> = columns.map((column: TransformerColumn) => {
112
+ return `${column.entity}.${column.property}`;
113
+ });
114
+
115
+ expect(scanned).toContain("LogDropFilter.droppedCount");
116
+ expect(scanned).toContain("TraceDropFilter.droppedCount");
117
+ });
118
+
119
+ it("never lets a transformer swallow undefined into null", () => {
120
+ const offenders: Array<string> = [];
121
+
122
+ for (const column of columns) {
123
+ if (!column.transformer.to) {
124
+ continue;
125
+ }
126
+
127
+ if (column.transformer.to(undefined) !== undefined) {
128
+ offenders.push(
129
+ `${column.entity}.${column.property} (NOT NULL DEFAULT ${String(
130
+ column.defaultValue,
131
+ )}) maps undefined to ${String(column.transformer.to(undefined))}`,
132
+ );
133
+ }
134
+ }
135
+
136
+ /*
137
+ * Named rather than counted: the message has to say which column will
138
+ * 500 on every insert, because the failure surfaces as a Postgres
139
+ * constraint error a long way from the model file.
140
+ */
141
+ expect(offenders).toEqual([]);
142
+ });
143
+ });
144
+
145
+ /*
146
+ * Every column declared as a number type that also carries a transformer.
147
+ *
148
+ * The declared type is not a perfect proxy for the in-memory one — an SMTP
149
+ * port is declared `Number` but typed `Port`, a usage figure is declared
150
+ * `Number` but typed `Decimal` — and those are exactly the columns where a
151
+ * transformer might never have been handed a bare number before.
152
+ */
153
+ function numericColumnsWithATransformer(): Array<TransformerColumn> {
154
+ const columns: Array<TransformerColumn> = [];
155
+
156
+ for (const modelType of AllModelTypes) {
157
+ const model: BaseModel = new modelType();
158
+ const entity: string = modelType.name;
159
+
160
+ for (const columnName of model.getTableColumns().columns) {
161
+ const metadata: TableColumnMetadata =
162
+ model.getTableColumnMetadata(columnName);
163
+
164
+ if (!metadata || !isNumericTableColumnType(metadata.type)) {
165
+ continue;
166
+ }
167
+
168
+ const column: ColumnMetadataArgs | undefined = (
169
+ getMetadataArgsStorage().columns as Array<ColumnMetadataArgs>
170
+ ).find((candidate: ColumnMetadataArgs) => {
171
+ return (
172
+ (candidate.target as { name?: string })?.name === entity &&
173
+ candidate.propertyName === columnName
174
+ );
175
+ });
176
+
177
+ if (!column?.options?.transformer) {
178
+ continue;
179
+ }
180
+
181
+ columns.push({
182
+ entity: entity,
183
+ property: columnName,
184
+ transformer: firstTransformerToRun(column.options.transformer),
185
+ defaultValue: column.options.default,
186
+ });
187
+ }
188
+ }
189
+
190
+ return columns;
191
+ }
192
+
193
+ describe("number columns that carry a transformer", () => {
194
+ const columns: Array<TransformerColumn> = numericColumnsWithATransformer();
195
+
196
+ /*
197
+ * The wrapped-type columns are the whole reason this scan exists. If it
198
+ * stops finding them, the assertion below is passing vacuously.
199
+ */
200
+ it("finds the wrapped-type number columns, so the scan is known to work", () => {
201
+ const scanned: Array<string> = columns.map((column: TransformerColumn) => {
202
+ return `${column.entity}.${column.property}`;
203
+ });
204
+
205
+ expect(scanned).toContain("GlobalConfig.smtpPort");
206
+ expect(scanned).toContain("ProjectSmtpConfig.port");
207
+ expect(scanned).toContain("TelemetryUsageBilling.dataIngestedInGB");
208
+ expect(scanned).toContain("LogDropFilter.droppedCount");
209
+ });
210
+
211
+ /*
212
+ * `BaseModel.fromJSON` now hands these transformers a real number where it
213
+ * used to hand them the string the form produced. A transformer that
214
+ * answers null for a number writes NULL over a value the user supplied —
215
+ * silently on a nullable column, and as a 500 on a NOT NULL one.
216
+ */
217
+ it("accepts a raw number rather than answering null", () => {
218
+ const offenders: Array<string> = [];
219
+
220
+ for (const column of columns) {
221
+ if (!column.transformer.to) {
222
+ continue;
223
+ }
224
+
225
+ /*
226
+ * 1 is in range for every number column in the schema; 0 is the value
227
+ * a truthiness check silently drops.
228
+ */
229
+ for (const sample of [1, 0]) {
230
+ let stored: unknown;
231
+
232
+ try {
233
+ stored = column.transformer.to(sample);
234
+ } catch (error) {
235
+ offenders.push(
236
+ `${column.entity}.${column.property} threw on ${sample}: ${
237
+ (error as Error).message
238
+ }`,
239
+ );
240
+ continue;
241
+ }
242
+
243
+ if (stored === null || stored === undefined) {
244
+ offenders.push(
245
+ `${column.entity}.${column.property} maps the number ${sample} to ${String(
246
+ stored,
247
+ )}`,
248
+ );
249
+ }
250
+ }
251
+ }
252
+
253
+ expect(offenders).toEqual([]);
254
+ });
255
+ });
256
+
257
+ describe("drop filter counter columns", () => {
258
+ /*
259
+ * Spelled out for the two columns the bug actually shipped on, so the
260
+ * regression is legible without reading the scan above.
261
+ */
262
+ const CASES: Array<{ name: string }> = [
263
+ { name: LogDropFilter.name },
264
+ { name: TraceDropFilter.name },
265
+ ];
266
+
267
+ describe.each(CASES)("$name.droppedCount", ({ name }: { name: string }) => {
268
+ function droppedCountColumn(): ColumnMetadataArgs {
269
+ const column: ColumnMetadataArgs | undefined = (
270
+ getMetadataArgsStorage().columns as Array<ColumnMetadataArgs>
271
+ ).find((candidate: ColumnMetadataArgs) => {
272
+ return (
273
+ (candidate.target as { name?: string })?.name === name &&
274
+ candidate.propertyName === "droppedCount"
275
+ );
276
+ });
277
+
278
+ if (!column) {
279
+ throw new Error(`${name}.droppedCount is not a registered column`);
280
+ }
281
+
282
+ return column;
283
+ }
284
+
285
+ it("is NOT NULL with a zero default, so the UI never renders a blank counter", () => {
286
+ const options: ColumnMetadataArgs["options"] = droppedCountColumn()
287
+ .options as ColumnMetadataArgs["options"];
288
+
289
+ expect(options.nullable).toBe(false);
290
+ expect(options.default).toBe(0);
291
+ });
292
+
293
+ /*
294
+ * The exact regression: an insert that leaves the counter alone — which
295
+ * is every create, since only the ingest path writes it — has to reach
296
+ * Postgres as DEFAULT, not as NULL.
297
+ */
298
+ it("lets a create fall through to that default instead of writing NULL", () => {
299
+ const transformer: ValueTransformer = firstTransformerToRun(
300
+ droppedCountColumn().options.transformer as ValueTransformer,
301
+ );
302
+
303
+ expect(transformer.to!(undefined)).toBeUndefined();
304
+ });
305
+ });
306
+ });
@@ -0,0 +1,333 @@
1
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
2
+ import LogDropFilter from "../../../Models/DatabaseModels/LogDropFilter";
3
+ import TraceDropFilter from "../../../Models/DatabaseModels/TraceDropFilter";
4
+ import Monitor from "../../../Models/DatabaseModels/Monitor";
5
+ import GlobalConfig from "../../../Models/DatabaseModels/GlobalConfig";
6
+ import LogDropFilterAction from "../../../Types/Log/LogDropFilterAction";
7
+ import { coerceNumericColumnsInJSON } from "../../../Types/Database/NumericColumnValue";
8
+ import { JSONObject } from "../../../Types/JSON";
9
+ import ObjectID from "../../../Types/ObjectID";
10
+ import Port from "../../../Types/Port";
11
+ import { describe, expect, it } from "@jest/globals";
12
+
13
+ /*
14
+ * Contract under test — what a number column holds after a JSON round trip.
15
+ *
16
+ * `BaseModel.fromJSON` is the single door every model comes through, and it
17
+ * runs on BOTH sides of the wire: ModelForm builds the model it is about to
18
+ * POST with it, and BaseAPI rebuilds that model from the request body with
19
+ * it. That makes it the one place that can fix the following, once.
20
+ *
21
+ * HTML has no numeric input event — `<input type="number">` gives you
22
+ * `e.target.value`, a string — so the dashboard posted "10" for every number
23
+ * field in the product. Postgres coerces '10' to 10 on the way in, so this
24
+ * was invisible everywhere except where something reads the value before it
25
+ * is stored. The drop-filter save hook does exactly that, and a user who
26
+ * typed a perfectly good sample percentage got:
27
+ *
28
+ * HTTP 400 — Sample percentage is required when the action is "Sample".
29
+ *
30
+ * github.com/OneUptime/oneuptime/issues/3027
31
+ */
32
+
33
+ describe("BaseModel.fromJSON on number columns", () => {
34
+ /*
35
+ * The reported payload, as the dashboard actually sends it: every scalar
36
+ * that came from a text-ish input is a string.
37
+ */
38
+ it("turns the dashboard's sample-filter payload into real numbers", () => {
39
+ const posted: JSONObject = {
40
+ name: "Sample healthcheck logs",
41
+ filterQuery: "body LIKE 'healthcheck'",
42
+ action: LogDropFilterAction.Sample,
43
+ samplePercentage: "10",
44
+ sortOrder: "1",
45
+ isEnabled: true,
46
+ };
47
+
48
+ const model: LogDropFilter = BaseModel.fromJSON(
49
+ posted,
50
+ LogDropFilter,
51
+ ) as LogDropFilter;
52
+
53
+ expect(model.samplePercentage).toBe(10);
54
+ expect(typeof model.samplePercentage).toBe("number");
55
+ expect(model.sortOrder).toBe(1);
56
+ expect(typeof model.sortOrder).toBe("number");
57
+ });
58
+
59
+ it("does the same for trace drop filters", () => {
60
+ const model: TraceDropFilter = BaseModel.fromJSON(
61
+ {
62
+ action: "sample",
63
+ samplePercentage: "25",
64
+ filterQuery: "name = 'GET /health'",
65
+ },
66
+ TraceDropFilter,
67
+ ) as TraceDropFilter;
68
+
69
+ expect(model.samplePercentage).toBe(25);
70
+ });
71
+
72
+ it("accepts a percentage that was already a number", () => {
73
+ const model: LogDropFilter = BaseModel.fromJSON(
74
+ { samplePercentage: 10 },
75
+ LogDropFilter,
76
+ ) as LogDropFilter;
77
+
78
+ expect(model.samplePercentage).toBe(10);
79
+ });
80
+
81
+ it("keeps the whole usable percentage range intact", () => {
82
+ for (const percentage of [1, 5, 10, 50, 99]) {
83
+ const model: LogDropFilter = BaseModel.fromJSON(
84
+ { samplePercentage: String(percentage) },
85
+ LogDropFilter,
86
+ ) as LogDropFilter;
87
+
88
+ expect(model.samplePercentage).toBe(percentage);
89
+ }
90
+ });
91
+
92
+ it("leaves an unset percentage unset rather than defaulting it to zero", () => {
93
+ const model: LogDropFilter = BaseModel.fromJSON(
94
+ { action: LogDropFilterAction.Drop, filterQuery: "severityText='Debug'" },
95
+ LogDropFilter,
96
+ ) as LogDropFilter;
97
+
98
+ expect(model.samplePercentage).toBeUndefined();
99
+ });
100
+
101
+ it("leaves a null percentage null", () => {
102
+ const model: LogDropFilter = BaseModel.fromJSON(
103
+ { samplePercentage: null },
104
+ LogDropFilter,
105
+ ) as LogDropFilter;
106
+
107
+ expect(model.samplePercentage).toBeNull();
108
+ });
109
+
110
+ /*
111
+ * `Number("")` is 0, and 0 is a meaningful sample percentage the validator
112
+ * rejects on purpose. Inventing it from a cleared field would turn a
113
+ * user's blank into a rejection about a value they never typed.
114
+ */
115
+ it("does not turn a cleared field into zero", () => {
116
+ const model: LogDropFilter = BaseModel.fromJSON(
117
+ { samplePercentage: "" },
118
+ LogDropFilter,
119
+ ) as LogDropFilter;
120
+
121
+ expect(model.samplePercentage).not.toBe(0);
122
+ });
123
+
124
+ it("leaves unparsable text alone for validation to reject", () => {
125
+ const model: LogDropFilter = BaseModel.fromJSON(
126
+ { samplePercentage: "abc" },
127
+ LogDropFilter,
128
+ ) as LogDropFilter;
129
+
130
+ expect(model.samplePercentage).toBe("abc" as unknown as number);
131
+ });
132
+
133
+ /*
134
+ * This is deliberately not drop-filter specific: the string arrived from a
135
+ * shared form component, so the fix has to hold for every number column or
136
+ * the next hook that reads one before storage repeats issue 3027.
137
+ */
138
+ it("applies to number columns on unrelated models too", () => {
139
+ const monitor: Monitor = BaseModel.fromJSON(
140
+ { name: "API", minimumProbeAgreement: "3" },
141
+ Monitor,
142
+ ) as Monitor;
143
+
144
+ expect(monitor.minimumProbeAgreement).toBe(3);
145
+ });
146
+
147
+ it("does not touch columns that merely look numeric", () => {
148
+ const model: LogDropFilter = BaseModel.fromJSON(
149
+ {
150
+ name: "12345",
151
+ action: "sample",
152
+ filterQuery: "1234",
153
+ samplePercentage: "10",
154
+ },
155
+ LogDropFilter,
156
+ ) as LogDropFilter;
157
+
158
+ expect(model.name).toBe("12345");
159
+ expect(model.filterQuery).toBe("1234");
160
+ expect(model.action).toBe("sample");
161
+ // ...while the number column beside them is still coerced.
162
+ expect(model.samplePercentage).toBe(10);
163
+ });
164
+
165
+ it("still deserializes the columns beside it normally", () => {
166
+ const projectId: string = "22222222-2222-4222-8222-222222222222";
167
+
168
+ const model: LogDropFilter = BaseModel.fromJSON(
169
+ {
170
+ projectId: new ObjectID(projectId).toJSON(),
171
+ samplePercentage: "10",
172
+ },
173
+ LogDropFilter,
174
+ ) as LogDropFilter;
175
+
176
+ expect(model.projectId).toBeInstanceOf(ObjectID);
177
+ expect(model.projectId?.toString()).toBe(projectId);
178
+ expect(model.samplePercentage).toBe(10);
179
+ });
180
+
181
+ /*
182
+ * The browser leg of the same trip: ModelForm hands fromJSON the raw form
183
+ * values and then serializes the result as the request body. If coercion
184
+ * did not survive toJSON, the server would still receive a string.
185
+ */
186
+ it("survives the toJSON that turns the model into a request body", () => {
187
+ const model: LogDropFilter = BaseModel.fromJSON(
188
+ {
189
+ name: "Sample healthcheck logs",
190
+ filterQuery: "body LIKE 'healthcheck'",
191
+ action: LogDropFilterAction.Sample,
192
+ samplePercentage: "10",
193
+ },
194
+ LogDropFilter,
195
+ ) as LogDropFilter;
196
+
197
+ const body: JSONObject = BaseModel.toJSON(model, LogDropFilter);
198
+
199
+ expect(body["samplePercentage"]).toBe(10);
200
+
201
+ const rebuilt: LogDropFilter = BaseModel.fromJSON(
202
+ body,
203
+ LogDropFilter,
204
+ ) as LogDropFilter;
205
+
206
+ expect(rebuilt.samplePercentage).toBe(10);
207
+ });
208
+
209
+ /*
210
+ * `droppedCount` is bigint and ingest-owned. The API serializes it as a
211
+ * number, but a client (or a driver) handing back the string form must not
212
+ * leave a string on a column the dashboard does arithmetic on.
213
+ */
214
+ it("normalizes a bigint counter that arrives as a string", () => {
215
+ const model: LogDropFilter = BaseModel.fromJSON(
216
+ { droppedCount: "4294967296" },
217
+ LogDropFilter,
218
+ ) as LogDropFilter;
219
+
220
+ expect(model.droppedCount).toBe(4294967296);
221
+ });
222
+
223
+ /*
224
+ * Not every `TableColumnType.Number` column holds a bare number in
225
+ * memory: an SMTP port is declared Number but typed `Port`, and it is
226
+ * written through Port's own transformer. Coercing "587" to 587 there is
227
+ * only safe because that transformer accepts a raw number — so assert the
228
+ * pair, not just the coercion.
229
+ */
230
+ it("keeps a Port column storable after coercing its string form", () => {
231
+ const config: GlobalConfig = BaseModel.fromJSON(
232
+ { smtpPort: "587" },
233
+ GlobalConfig,
234
+ ) as GlobalConfig;
235
+
236
+ expect(config.smtpPort).toBe(587 as unknown as Port);
237
+ expect(Port.getDatabaseTransformer().to(config.smtpPort)).toBe(587);
238
+ });
239
+ });
240
+
241
+ /*
242
+ * The update half. `BaseAPI.updateItem` never builds a model — it goes from
243
+ * the request body straight to a partial entity — so it needs the same
244
+ * normalization applied to a loose patch, or a PATCH and a POST disagree
245
+ * about what "10" is and the save-time hooks see different types depending
246
+ * on which verb the caller used.
247
+ */
248
+ describe("coerceNumericColumnsInJSON on an update patch", () => {
249
+ it("coerces a number column the patch names", () => {
250
+ const patch: JSONObject = coerceNumericColumnsInJSON(
251
+ { samplePercentage: "25" },
252
+ new LogDropFilter(),
253
+ );
254
+
255
+ expect(patch["samplePercentage"]).toBe(25);
256
+ });
257
+
258
+ it("leaves the columns it does not own alone", () => {
259
+ const patch: JSONObject = coerceNumericColumnsInJSON(
260
+ {
261
+ name: "12345",
262
+ action: "sample",
263
+ filterQuery: "severityText = 'Debug'",
264
+ samplePercentage: "25",
265
+ isEnabled: false,
266
+ },
267
+ new LogDropFilter(),
268
+ );
269
+
270
+ expect(patch["name"]).toBe("12345");
271
+ expect(patch["action"]).toBe("sample");
272
+ expect(patch["filterQuery"]).toBe("severityText = 'Debug'");
273
+ expect(patch["isEnabled"]).toBe(false);
274
+ expect(patch["samplePercentage"]).toBe(25);
275
+ });
276
+
277
+ it("ignores keys that are not columns at all", () => {
278
+ const patch: JSONObject = coerceNumericColumnsInJSON(
279
+ { notAColumn: "10" },
280
+ new LogDropFilter(),
281
+ );
282
+
283
+ expect(patch["notAColumn"]).toBe("10");
284
+ });
285
+
286
+ /*
287
+ * A PartialEntity may carry a `() => string` raw SQL expression instead of
288
+ * a literal. Coercion must not stringify or otherwise disturb one.
289
+ */
290
+ it("passes a raw SQL expression through untouched", () => {
291
+ const expression: () => string = (): string => {
292
+ return '"samplePercentage" + 1';
293
+ };
294
+
295
+ const patch: JSONObject = coerceNumericColumnsInJSON(
296
+ { samplePercentage: expression as unknown as JSONObject },
297
+ new LogDropFilter(),
298
+ );
299
+
300
+ expect(patch["samplePercentage"]).toBe(expression);
301
+ });
302
+
303
+ it("does not invent a value for a cleared field", () => {
304
+ const patch: JSONObject = coerceNumericColumnsInJSON(
305
+ { samplePercentage: "" },
306
+ new LogDropFilter(),
307
+ );
308
+
309
+ expect(patch["samplePercentage"]).not.toBe(0);
310
+ });
311
+
312
+ it("keeps an explicit null so a nullable column can be cleared", () => {
313
+ const patch: JSONObject = coerceNumericColumnsInJSON(
314
+ { samplePercentage: null },
315
+ new LogDropFilter(),
316
+ );
317
+
318
+ expect(patch["samplePercentage"]).toBeNull();
319
+ });
320
+
321
+ /*
322
+ * The API layer hands this a freshly-constructed model of the entity
323
+ * being updated, so it has to work off an instance rather than the class.
324
+ */
325
+ it("works off any model instance, not just drop filters", () => {
326
+ const patch: JSONObject = coerceNumericColumnsInJSON(
327
+ { minimumProbeAgreement: "3" },
328
+ new Monitor(),
329
+ );
330
+
331
+ expect(patch["minimumProbeAgreement"]).toBe(3);
332
+ });
333
+ });