@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,342 @@
1
+ import DatabaseService from "../../../Server/Services/DatabaseService";
2
+ import Service from "../../../Models/DatabaseModels/Service";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+ import BadDataException from "../../../Types/Exception/BadDataException";
5
+ import { getJestSpyOn } from "../../Spy";
6
+ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
7
+
8
+ /*
9
+ * Pins `updateColumnsByIdIfUnlockedWithoutHooks` at the SQL level.
10
+ *
11
+ * This primitive is the structural backstop under every ingest-path throttle,
12
+ * and the thing that makes it a backstop is one clause: FOR UPDATE SKIP
13
+ * LOCKED. A single-statement UPDATE holds its row lock briefly, but "briefly"
14
+ * stops mattering once the arrival rate on ONE row exceeds the service rate —
15
+ * Postgres queues lock waiters strictly, so every waiter pins a backend for
16
+ * the sum of everyone ahead of it. That is what produced 1,017 active
17
+ * connections with 892 parked on row locks and a 3.7-hour tail, on a database
18
+ * that was never short of capacity.
19
+ *
20
+ * SKIP LOCKED caps the number of backends blocked on any single row at one,
21
+ * and it does so unconditionally — it keeps holding when Redis is down, when a
22
+ * fence has a bug, when someone flushes the cache. None of the throttles
23
+ * upstream can make that guarantee, so nothing else in the codebase can
24
+ * substitute for it.
25
+ *
26
+ * Every assertion here is therefore about the generated statement, not about
27
+ * behaviour observable from a call site. Mocking this method out (as the
28
+ * service-level suites do) mocks the guarantee out with it, so it has to be
29
+ * checked here or nowhere. If an assertion below starts failing, the fix is
30
+ * almost certainly in the code, not in the assertion.
31
+ *
32
+ * No Postgres, no Redis — the repository is faked and the emitted SQL and
33
+ * bound parameters are captured.
34
+ */
35
+
36
+ const SERVICE_ID: ObjectID = new ObjectID(
37
+ "33333333-3333-4333-8333-333333333333",
38
+ );
39
+
40
+ interface CapturedQuery {
41
+ sql: string;
42
+ params: Array<unknown>;
43
+ }
44
+
45
+ describe("DatabaseService.updateColumnsByIdIfUnlockedWithoutHooks", () => {
46
+ let service: DatabaseService<Service>;
47
+ let captured: Array<CapturedQuery>;
48
+ let rowsToReturn: Array<unknown>;
49
+
50
+ beforeEach(() => {
51
+ jest.restoreAllMocks();
52
+ captured = [];
53
+ rowsToReturn = [{ _id: SERVICE_ID.toString() }];
54
+
55
+ service = new DatabaseService<Service>(Service);
56
+
57
+ const columns: Record<string, string> = {
58
+ lastSeenAt: "lastSeenAt",
59
+ serviceVersion: "serviceVersion",
60
+ cloudRegion: "cloudRegion",
61
+ _id: "_id",
62
+ updatedAt: "updatedAt",
63
+ };
64
+
65
+ getJestSpyOn(service, "getRepository").mockReturnValue({
66
+ metadata: {
67
+ tableName: "Service",
68
+ findColumnWithPropertyName: (
69
+ propertyName: string,
70
+ ): { databaseName: string } | undefined => {
71
+ return columns[propertyName]
72
+ ? { databaseName: columns[propertyName]! }
73
+ : undefined;
74
+ },
75
+ updateDateColumn: { databaseName: "updatedAt" },
76
+ primaryColumns: [{ databaseName: "_id" }],
77
+ },
78
+ manager: {
79
+ connection: {
80
+ driver: {
81
+ preparePersistentValue: (value: unknown): unknown => {
82
+ return value;
83
+ },
84
+ },
85
+ },
86
+ query: async (
87
+ sql: string,
88
+ params: Array<unknown>,
89
+ ): Promise<Array<unknown>> => {
90
+ captured.push({ sql, params });
91
+ return rowsToReturn;
92
+ },
93
+ },
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ } as any);
96
+ });
97
+
98
+ describe("the non-blocking guarantee", () => {
99
+ /*
100
+ * The whole point. Without SKIP LOCKED this is an ordinary blocking
101
+ * UPDATE and the convoy is back.
102
+ */
103
+ it("locks the target row with FOR UPDATE SKIP LOCKED", async () => {
104
+ const now: Date = new Date();
105
+
106
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
107
+ id: SERVICE_ID,
108
+ data: { lastSeenAt: now },
109
+ });
110
+
111
+ expect(captured).toHaveLength(1);
112
+ expect(captured[0]!.sql).toContain("FOR UPDATE SKIP LOCKED");
113
+ });
114
+
115
+ /*
116
+ * The lock must be taken by a sub-SELECT on the same table and the UPDATE
117
+ * driven from its result. Reaching the row any other way (a bare WHERE
118
+ * clause) makes the outer UPDATE take its own blocking lock and the SKIP
119
+ * LOCKED is decorative.
120
+ */
121
+ it("drives the UPDATE from the locked sub-SELECT, not a bare predicate", async () => {
122
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
123
+ id: SERVICE_ID,
124
+ data: { lastSeenAt: new Date() },
125
+ });
126
+
127
+ expect(captured[0]!.sql).toContain(
128
+ `WHERE "_id" IN (SELECT "_id" FROM "Service" WHERE "_id" = $2 FOR UPDATE SKIP LOCKED)`,
129
+ );
130
+ });
131
+
132
+ it("issues exactly one statement — no BEGIN, no COMMIT, no extra reads", async () => {
133
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
134
+ id: SERVICE_ID,
135
+ data: { lastSeenAt: new Date(), serviceVersion: "1.2.3" },
136
+ });
137
+
138
+ expect(captured).toHaveLength(1);
139
+ expect(captured[0]!.sql.toUpperCase()).not.toContain("BEGIN");
140
+ expect(captured[0]!.sql.toUpperCase()).not.toContain("COMMIT");
141
+ });
142
+
143
+ /*
144
+ * `version = version + 1` would drag this write into a fight with the
145
+ * optimistic lock on a row a human may be editing, and is exactly what
146
+ * routing through the ORM's save() path reintroduces.
147
+ */
148
+ it("never bumps the optimistic-lock version column", async () => {
149
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
150
+ id: SERVICE_ID,
151
+ data: { lastSeenAt: new Date() },
152
+ });
153
+
154
+ expect(captured[0]!.sql).not.toContain("version");
155
+ });
156
+ });
157
+
158
+ describe("reporting whether the write landed", () => {
159
+ /*
160
+ * A skipped write and an applied write are indistinguishable without
161
+ * RETURNING: a bare UPDATE surfaces no rows through TypeORM's query(). If
162
+ * this regresses, every caller silently believes contended writes
163
+ * succeeded — which is how a "reliable" heartbeat stops beating with
164
+ * nothing in the logs.
165
+ */
166
+ it("asks for the affected row back so the skip is observable", async () => {
167
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
168
+ id: SERVICE_ID,
169
+ data: { lastSeenAt: new Date() },
170
+ });
171
+
172
+ expect(captured[0]!.sql).toContain(`RETURNING "_id"`);
173
+ });
174
+
175
+ it("returns true when the row was updated", async () => {
176
+ rowsToReturn = [{ _id: SERVICE_ID.toString() }];
177
+
178
+ await expect(
179
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
180
+ id: SERVICE_ID,
181
+ data: { lastSeenAt: new Date() },
182
+ }),
183
+ ).resolves.toBe(true);
184
+ });
185
+
186
+ it("returns false when the row was locked by another writer", async () => {
187
+ rowsToReturn = [];
188
+
189
+ await expect(
190
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
191
+ id: SERVICE_ID,
192
+ data: { lastSeenAt: new Date() },
193
+ }),
194
+ ).resolves.toBe(false);
195
+ });
196
+
197
+ it("returns false when the row no longer exists", async () => {
198
+ rowsToReturn = [];
199
+
200
+ await expect(
201
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
202
+ id: SERVICE_ID,
203
+ data: { lastSeenAt: new Date() },
204
+ }),
205
+ ).resolves.toBe(false);
206
+ });
207
+
208
+ it("returns false rather than throwing on an unexpected driver reply", async () => {
209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
+ rowsToReturn = undefined as any;
211
+
212
+ await expect(
213
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
214
+ id: SERVICE_ID,
215
+ data: { lastSeenAt: new Date() },
216
+ }),
217
+ ).resolves.toBe(false);
218
+ });
219
+ });
220
+
221
+ describe("parity with the blocking primitive", () => {
222
+ it("binds every value as a parameter", async () => {
223
+ const now: Date = new Date();
224
+
225
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
226
+ id: SERVICE_ID,
227
+ data: { lastSeenAt: now, serviceVersion: "1.2.3" },
228
+ });
229
+
230
+ expect(captured[0]!.params).toEqual([
231
+ now,
232
+ "1.2.3",
233
+ SERVICE_ID.toString(),
234
+ ]);
235
+ });
236
+
237
+ it("refreshes updatedAt by default", async () => {
238
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
239
+ id: SERVICE_ID,
240
+ data: { lastSeenAt: new Date() },
241
+ });
242
+
243
+ expect(captured[0]!.sql).toContain(`"updatedAt" = CURRENT_TIMESTAMP`);
244
+ });
245
+
246
+ it("leaves updatedAt alone when asked to", async () => {
247
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
248
+ id: SERVICE_ID,
249
+ data: { lastSeenAt: new Date() },
250
+ skipUpdateDateColumn: true,
251
+ });
252
+
253
+ expect(captured[0]!.sql).not.toContain("updatedAt");
254
+ });
255
+
256
+ /*
257
+ * The clamp lives at this choke point precisely so no caller can forget
258
+ * it: an over-long collector-supplied attribute would otherwise fail the
259
+ * whole statement and take lastSeenAt down with it (issue #3006).
260
+ */
261
+ it("clamps over-long strings to their column width", async () => {
262
+ const huge: string = "x".repeat(5000);
263
+
264
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
265
+ id: SERVICE_ID,
266
+ data: { lastSeenAt: new Date(), cloudRegion: huge },
267
+ });
268
+
269
+ const bound: string = captured[0]!.params[1] as string;
270
+ expect(bound.length).toBeLessThan(huge.length);
271
+ });
272
+
273
+ it("does not mutate the caller's payload when clamping", async () => {
274
+ const huge: string = "x".repeat(5000);
275
+ const data: Record<string, unknown> = {
276
+ lastSeenAt: new Date(),
277
+ cloudRegion: huge,
278
+ };
279
+
280
+ await service.updateColumnsByIdIfUnlockedWithoutHooks({
281
+ id: SERVICE_ID,
282
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
283
+ data: data as any,
284
+ });
285
+
286
+ expect(data["cloudRegion"]).toHaveLength(5000);
287
+ });
288
+
289
+ it("rejects an unknown column instead of generating invalid SQL", async () => {
290
+ await expect(
291
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
292
+ id: SERVICE_ID,
293
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
+ data: { notAColumn: "x" } as any,
295
+ }),
296
+ ).rejects.toThrow(BadDataException);
297
+
298
+ expect(captured).toHaveLength(0);
299
+ });
300
+
301
+ it("rejects SQL-expression values, which this bind path cannot express", async () => {
302
+ await expect(
303
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
304
+ id: SERVICE_ID,
305
+ data: {
306
+ lastSeenAt: () => {
307
+ return "now()";
308
+ },
309
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
+ } as any,
311
+ }),
312
+ ).rejects.toThrow(BadDataException);
313
+ });
314
+
315
+ it("requires an id", async () => {
316
+ await expect(
317
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
318
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
319
+ id: undefined as any,
320
+ data: { lastSeenAt: new Date() },
321
+ }),
322
+ ).rejects.toThrow(BadDataException);
323
+ });
324
+
325
+ /*
326
+ * An empty payload must issue nothing at all. Emitting `SET "updatedAt" =
327
+ * CURRENT_TIMESTAMP` alone would take a row lock to write a value nobody
328
+ * asked for — a pointless contender in exactly the place contention is
329
+ * the problem.
330
+ */
331
+ it("issues no statement, and reports no write, for an empty payload", async () => {
332
+ await expect(
333
+ service.updateColumnsByIdIfUnlockedWithoutHooks({
334
+ id: SERVICE_ID,
335
+ data: {},
336
+ }),
337
+ ).resolves.toBe(false);
338
+
339
+ expect(captured).toHaveLength(0);
340
+ });
341
+ });
342
+ });
@@ -1,9 +1,11 @@
1
1
  import LogDropFilterService from "../../../Server/Services/LogDropFilterService";
2
2
  import TraceDropFilterService from "../../../Server/Services/TraceDropFilterService";
3
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
3
4
  import LogDropFilter from "../../../Models/DatabaseModels/LogDropFilter";
4
5
  import TraceDropFilter from "../../../Models/DatabaseModels/TraceDropFilter";
5
6
  import LogDropFilterAction from "../../../Types/Log/LogDropFilterAction";
6
7
  import TraceDropFilterAction from "../../../Types/Trace/TraceDropFilterAction";
8
+ import { coerceNumericColumnsInJSON } from "../../../Types/Database/NumericColumnValue";
7
9
  import BadDataException from "../../../Types/Exception/BadDataException";
8
10
  import ObjectID from "../../../Types/ObjectID";
9
11
  import CreateBy from "../../../Server/Types/Database/CreateBy";
@@ -52,6 +54,7 @@ const VALID_QUERY: string = "severityText = 'Debug'";
52
54
  const SUITES: Array<{
53
55
  name: string;
54
56
  service: any;
57
+ modelType: any;
55
58
  makeModel: () => any;
56
59
  sampleAction: string;
57
60
  dropAction: string;
@@ -60,6 +63,7 @@ const SUITES: Array<{
60
63
  {
61
64
  name: "LogDropFilterService",
62
65
  service: LogDropFilterService,
66
+ modelType: LogDropFilter,
63
67
  makeModel: (): LogDropFilter => {
64
68
  return new LogDropFilter();
65
69
  },
@@ -70,6 +74,7 @@ const SUITES: Array<{
70
74
  {
71
75
  name: "TraceDropFilterService",
72
76
  service: TraceDropFilterService,
77
+ modelType: TraceDropFilter,
73
78
  makeModel: (): TraceDropFilter => {
74
79
  return new TraceDropFilter();
75
80
  },
@@ -396,3 +401,243 @@ describe.each(SUITES)(
396
401
  });
397
402
  },
398
403
  );
404
+
405
+ /*
406
+ * The reported failure, driven the way the product actually drives it.
407
+ *
408
+ * The suites above hand the hook a model built in TypeScript, where a number
409
+ * is a number by construction. Nothing reaches the hook that way in
410
+ * production: BaseAPI.createItem rebuilds the model from the request body
411
+ * with BaseModel.fromJSON, and that body comes from a dashboard form whose
412
+ * "Sample Percentage" field is an `<input type="number">` — a DOM input
413
+ * whose value is a *string*. So the hook was handed "10", the check read
414
+ * `typeof samplePercentage !== "number"`, and a user who had filled the form
415
+ * in correctly got:
416
+ *
417
+ * HTTP 400 — Sample percentage is required when the action is "Sample".
418
+ * Enter the percentage of matching logs to keep, between 1 and 99.
419
+ *
420
+ * github.com/OneUptime/oneuptime/issues/3027
421
+ *
422
+ * These go through fromJSON on purpose. A test that constructs the model
423
+ * directly cannot fail on this bug, which is precisely why the existing
424
+ * suite passed while the feature was unusable.
425
+ */
426
+ describe.each(SUITES)(
427
+ "$name create from a dashboard request body",
428
+ ({ service, modelType, sampleAction, dropAction }: any) => {
429
+ function createByFromRequestBody(body: Record<string, unknown>): any {
430
+ return {
431
+ data: BaseModel.fromJSON(body as any, modelType),
432
+ props: { isRoot: true },
433
+ };
434
+ }
435
+
436
+ it("accepts the sample percentage as the number input sends it", async () => {
437
+ await expect(
438
+ service.onBeforeCreate(
439
+ createByFromRequestBody({
440
+ name: "Sample healthcheck logs",
441
+ filterQuery: VALID_QUERY,
442
+ action: sampleAction,
443
+ samplePercentage: "10",
444
+ sortOrder: "1",
445
+ isEnabled: true,
446
+ }),
447
+ ),
448
+ ).resolves.toBeDefined();
449
+ });
450
+
451
+ it("stores it as a number, not as the string it arrived as", async () => {
452
+ const createBy: any = createByFromRequestBody({
453
+ filterQuery: VALID_QUERY,
454
+ action: sampleAction,
455
+ samplePercentage: "10",
456
+ });
457
+
458
+ await service.onBeforeCreate(createBy);
459
+
460
+ expect(createBy.data.samplePercentage).toBe(10);
461
+ expect(typeof createBy.data.samplePercentage).toBe("number");
462
+ });
463
+
464
+ it("accepts every percentage the form allows", async () => {
465
+ for (const percentage of ["1", "5", "10", "50", "99"]) {
466
+ await expect(
467
+ service.onBeforeCreate(
468
+ createByFromRequestBody({
469
+ filterQuery: VALID_QUERY,
470
+ action: sampleAction,
471
+ samplePercentage: percentage,
472
+ }),
473
+ ),
474
+ ).resolves.toBeDefined();
475
+ }
476
+ });
477
+
478
+ /*
479
+ * Coercion must not become a way around the range check — "0" and "100"
480
+ * are still the two values that mean something the Sample action cannot
481
+ * express.
482
+ */
483
+ it("still rejects an out-of-range percentage that arrives as a string", async () => {
484
+ for (const percentage of ["0", "100", "-1", "250"]) {
485
+ await expect(
486
+ service.onBeforeCreate(
487
+ createByFromRequestBody({
488
+ filterQuery: VALID_QUERY,
489
+ action: sampleAction,
490
+ samplePercentage: percentage,
491
+ }),
492
+ ),
493
+ ).rejects.toThrow(BadDataException);
494
+ }
495
+ });
496
+
497
+ it("still rejects a sample filter whose percentage field was left blank", async () => {
498
+ await expect(
499
+ service.onBeforeCreate(
500
+ createByFromRequestBody({
501
+ filterQuery: VALID_QUERY,
502
+ action: sampleAction,
503
+ samplePercentage: "",
504
+ }),
505
+ ),
506
+ ).rejects.toThrow(BadDataException);
507
+ });
508
+
509
+ it("still rejects a percentage that is not a number at all", async () => {
510
+ await expect(
511
+ service.onBeforeCreate(
512
+ createByFromRequestBody({
513
+ filterQuery: VALID_QUERY,
514
+ action: sampleAction,
515
+ samplePercentage: "abc",
516
+ }),
517
+ ),
518
+ ).rejects.toThrow(BadDataException);
519
+ });
520
+
521
+ it("accepts the drop payload, which carries no percentage at all", async () => {
522
+ await expect(
523
+ service.onBeforeCreate(
524
+ createByFromRequestBody({
525
+ name: "Drop debug",
526
+ filterQuery: VALID_QUERY,
527
+ action: dropAction,
528
+ sortOrder: "1",
529
+ isEnabled: true,
530
+ }),
531
+ ),
532
+ ).resolves.toBeDefined();
533
+ });
534
+
535
+ /*
536
+ * The other half of the same create. `droppedCount` is ingest-owned and
537
+ * never present in a create body, so it has to stay unset all the way to
538
+ * the INSERT — where the column's DEFAULT 0 applies. A create that
539
+ * carried an explicit null instead is what made this a 500 rather than a
540
+ * 400. github.com/OneUptime/oneuptime/issues/3026
541
+ */
542
+ it("leaves the ingest-owned counter unset for the column default", async () => {
543
+ const createBy: any = createByFromRequestBody({
544
+ name: "Drop debug",
545
+ filterQuery: VALID_QUERY,
546
+ action: dropAction,
547
+ });
548
+
549
+ await service.onBeforeCreate(createBy);
550
+
551
+ expect(createBy.data.droppedCount).toBeUndefined();
552
+ expect(createBy.data.lastDroppedAt).toBeUndefined();
553
+ });
554
+ },
555
+ );
556
+
557
+ /*
558
+ * The same request body, sent as an edit rather than a create.
559
+ *
560
+ * `BaseAPI.updateItem` does not build a model — it deserializes the body
561
+ * into a partial entity — so it normalizes number columns itself. Without
562
+ * that, changing a saved filter's percentage in the dashboard failed the
563
+ * same way creating one did, and only on the update verb.
564
+ */
565
+ describe.each(SUITES)(
566
+ "$name update from a dashboard request body",
567
+ ({ service, makeModel, sampleAction, dropAction }: any) => {
568
+ afterEach(() => {
569
+ jest.restoreAllMocks();
570
+ });
571
+
572
+ function updateByFromRequestBody(body: Record<string, unknown>): any {
573
+ return {
574
+ query: { _id: FILTER_ID.toString() },
575
+ data: coerceNumericColumnsInJSON(body as any, makeModel()),
576
+ props: { isRoot: true },
577
+ };
578
+ }
579
+
580
+ function mockStoredRow(row: StoredRow): void {
581
+ const model: any = makeModel();
582
+ model._id = FILTER_ID.toString();
583
+ model.action = row.action;
584
+ model.filterQuery = row.filterQuery;
585
+ if (row.samplePercentage !== undefined) {
586
+ model.samplePercentage = row.samplePercentage;
587
+ }
588
+
589
+ jest
590
+ .spyOn(service, "findBy")
591
+ .mockImplementation(async (): Promise<Array<any>> => {
592
+ return [model];
593
+ });
594
+ }
595
+
596
+ it("accepts a percentage change sent as a string", async () => {
597
+ mockStoredRow({
598
+ action: sampleAction,
599
+ samplePercentage: 10,
600
+ filterQuery: VALID_QUERY,
601
+ });
602
+
603
+ await expect(
604
+ service.onBeforeUpdate(
605
+ updateByFromRequestBody({ samplePercentage: "25" }),
606
+ ),
607
+ ).resolves.toBeDefined();
608
+ });
609
+
610
+ it("accepts flipping a drop filter to sample with a string percentage", async () => {
611
+ mockStoredRow({
612
+ action: dropAction,
613
+ samplePercentage: undefined,
614
+ filterQuery: VALID_QUERY,
615
+ });
616
+
617
+ await expect(
618
+ service.onBeforeUpdate(
619
+ updateByFromRequestBody({
620
+ action: sampleAction,
621
+ samplePercentage: "5",
622
+ }),
623
+ ),
624
+ ).resolves.toBeDefined();
625
+ });
626
+
627
+ it("still rejects an out-of-range percentage sent as a string", async () => {
628
+ mockStoredRow({
629
+ action: sampleAction,
630
+ samplePercentage: 10,
631
+ filterQuery: VALID_QUERY,
632
+ });
633
+
634
+ for (const percentage of ["0", "100"]) {
635
+ await expect(
636
+ service.onBeforeUpdate(
637
+ updateByFromRequestBody({ samplePercentage: percentage }),
638
+ ),
639
+ ).rejects.toThrow(BadDataException);
640
+ }
641
+ });
642
+ },
643
+ );