@oneuptime/common 11.7.1 → 11.7.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 (285) hide show
  1. package/Models/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
  2. package/Models/DatabaseModels/DashboardDomain.ts +1 -0
  3. package/Models/DatabaseModels/Incident.ts +2 -0
  4. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  5. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  6. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  7. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  8. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  9. package/Models/DatabaseModels/Monitor.ts +1 -0
  10. package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
  11. package/Models/DatabaseModels/NetworkSite.ts +1 -0
  12. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  14. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
  15. package/Models/DatabaseModels/Probe.ts +13 -13
  16. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  17. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  18. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  19. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
  20. package/Models/DatabaseModels/StatusPage.ts +1 -0
  21. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  22. package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
  23. package/Models/DatabaseModels/TableView.ts +40 -0
  24. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
  25. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
  26. package/Server/API/BaseAPI.ts +17 -1
  27. package/Server/API/CommonAPI.ts +32 -0
  28. package/Server/API/MicrosoftTeamsAPI.ts +99 -2
  29. package/Server/API/ProbeAPI.ts +2 -0
  30. package/Server/API/SlackAPI.ts +62 -0
  31. package/Server/API/StatusPageAPI.ts +157 -192
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  37. package/Server/Services/DatabaseService.ts +11 -3
  38. package/Server/Services/MonitorGroupService.ts +145 -0
  39. package/Server/Services/MonitorService.ts +231 -30
  40. package/Server/Services/ProbeService.ts +319 -16
  41. package/Server/Services/StatusPageService.ts +72 -42
  42. package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
  43. package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
  44. package/Server/Utils/Monitor/MonitorResource.ts +88 -35
  45. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
  46. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
  47. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
  48. package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
  49. package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
  50. package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
  51. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
  52. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
  53. package/Tests/Server/API/ProbeAPI.test.ts +6 -0
  54. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
  55. package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
  56. package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
  57. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
  58. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
  59. package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
  60. package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
  61. package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
  62. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
  63. package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
  64. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
  65. package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
  66. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
  67. package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
  68. package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
  69. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
  70. package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
  71. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
  72. package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
  73. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
  74. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
  75. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
  76. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
  77. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
  78. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
  79. package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
  80. package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
  81. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
  82. package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
  83. package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
  84. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
  85. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
  86. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
  87. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
  88. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
  89. package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
  90. package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
  91. package/Tests/Types/JSONFunctions.test.ts +103 -1
  92. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
  93. package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
  94. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
  95. package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
  96. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
  97. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
  98. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +572 -0
  99. package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
  100. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
  101. package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
  102. package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
  103. package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
  104. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
  105. package/Tests/UI/Utils/Project.test.ts +809 -0
  106. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
  107. package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
  108. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
  109. package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
  110. package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
  111. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
  112. package/Tests/Utils/UserPreferences.test.ts +563 -0
  113. package/Types/JSONFunctions.ts +7 -2
  114. package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
  115. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
  116. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
  117. package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
  118. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
  119. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  120. package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
  121. package/UI/Components/Forms/ModelForm.tsx +41 -18
  122. package/UI/Components/MasterPage/MasterPage.tsx +65 -0
  123. package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
  124. package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
  125. package/UI/Components/ModelTable/BaseModelTable.tsx +687 -34
  126. package/UI/Components/ModelTable/Column.ts +29 -0
  127. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +420 -0
  128. package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
  129. package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
  130. package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
  131. package/UI/Components/ModelTable/TableView.tsx +24 -2
  132. package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
  133. package/UI/Components/Navbar/NavBar.tsx +66 -1
  134. package/UI/Components/SideMenu/SideMenu.tsx +24 -4
  135. package/UI/Components/Table/Table.tsx +66 -14
  136. package/UI/Components/Table/TableRow.tsx +180 -175
  137. package/UI/Components/Table/Types/Column.ts +20 -0
  138. package/UI/Utils/Project.ts +214 -18
  139. package/UI/Utils/TableColumnsToCsv.ts +86 -6
  140. package/UI/esbuild-config.js +34 -0
  141. package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
  142. package/Utils/UserPreferences.ts +53 -0
  143. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
  144. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
  145. package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
  146. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  148. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  150. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  152. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  154. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  155. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  156. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  157. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  158. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  159. package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
  160. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  161. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
  162. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  163. package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
  164. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  165. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
  166. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
  167. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  168. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  169. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
  170. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  171. package/build/dist/Models/DatabaseModels/Probe.js +13 -13
  172. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  173. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  174. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  175. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  176. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  177. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  178. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  179. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
  180. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
  182. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  184. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
  186. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/TableView.js +40 -0
  188. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
  190. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  192. package/build/dist/Server/API/BaseAPI.js +12 -1
  193. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  194. package/build/dist/Server/API/CommonAPI.js +21 -0
  195. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  196. package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
  197. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  198. package/build/dist/Server/API/ProbeAPI.js +2 -0
  199. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  200. package/build/dist/Server/API/SlackAPI.js +44 -0
  201. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  202. package/build/dist/Server/API/StatusPageAPI.js +98 -152
  203. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  204. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
  205. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
  206. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
  207. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
  208. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
  209. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
  210. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
  211. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
  212. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  213. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  214. package/build/dist/Server/Services/DatabaseService.js +10 -2
  215. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  216. package/build/dist/Server/Services/MonitorGroupService.js +118 -0
  217. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  218. package/build/dist/Server/Services/MonitorService.js +198 -24
  219. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  220. package/build/dist/Server/Services/ProbeService.js +238 -9
  221. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  222. package/build/dist/Server/Services/StatusPageService.js +46 -39
  223. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  224. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
  225. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  226. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
  227. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
  228. package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
  229. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  230. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
  231. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  232. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
  233. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
  234. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
  235. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  236. package/build/dist/Types/JSONFunctions.js +8 -3
  237. package/build/dist/Types/JSONFunctions.js.map +1 -1
  238. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
  239. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
  240. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  241. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
  242. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  243. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
  244. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  245. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
  246. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
  247. package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
  248. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  249. package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
  250. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  251. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
  252. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  253. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
  254. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  255. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +472 -27
  256. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  257. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +189 -0
  258. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
  259. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
  260. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
  261. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
  262. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
  263. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
  264. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
  265. package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
  266. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  267. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
  268. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
  269. package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
  270. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  271. package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
  272. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  273. package/build/dist/UI/Components/Table/Table.js +46 -13
  274. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  275. package/build/dist/UI/Components/Table/TableRow.js +11 -6
  276. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  277. package/build/dist/UI/Utils/Project.js +161 -15
  278. package/build/dist/UI/Utils/Project.js.map +1 -1
  279. package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
  280. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
  281. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
  282. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
  283. package/build/dist/Utils/UserPreferences.js +33 -0
  284. package/build/dist/Utils/UserPreferences.js.map +1 -1
  285. package/package.json +4 -3
@@ -0,0 +1,245 @@
1
+ /**
2
+ * StatusPageService domain-to-id resolution cache tests.
3
+ *
4
+ * Every public status-page API call served on a custom domain (overview,
5
+ * incidents, announcements, polling — several per page view) starts by
6
+ * resolving Host header -> statusPageId, so resolveStatusPageIdOrNull now
7
+ * consults a per-process 60s TTL cache before querying
8
+ * StatusPageDomainService. The contract this pins is load-bearing in three
9
+ * ways:
10
+ * - only SUCCESSFUL resolutions are cached: a freshly verified domain works
11
+ * immediately, and attacker-controlled Host headers cannot fill the cache
12
+ * with negative entries
13
+ * - the cache stores the id as a STRING and hands out a fresh ObjectID per
14
+ * hit, so a caller mutating the returned id cannot poison every subsequent
15
+ * request on that domain
16
+ * - keys are exactly as case-sensitive as the underlying query, so the cache
17
+ * can never resolve a domain the database would not
18
+ *
19
+ * StatusPageDomainService.findOneBy is spied on, so no database is touched.
20
+ */
21
+
22
+ import StatusPageService from "../../../Server/Services/StatusPageService";
23
+ import StatusPageDomainService from "../../../Server/Services/StatusPageDomainService";
24
+ import StatusPageDomain from "../../../Models/DatabaseModels/StatusPageDomain";
25
+ import ObjectID from "../../../Types/ObjectID";
26
+ import {
27
+ afterEach,
28
+ beforeEach,
29
+ describe,
30
+ expect,
31
+ test,
32
+ jest,
33
+ } from "@jest/globals";
34
+
35
+ const VALID_UUID: string = "550e8400-e29b-41d4-a716-446655440000";
36
+ const SECOND_UUID: string = "660e8400-e29b-41d4-a716-446655440111";
37
+ const DOMAIN: string = "status.company.com";
38
+ const OTHER_DOMAIN: string = "status.other.com";
39
+ const TTL_MS: number = 60 * 1000;
40
+
41
+ function statusPageDomainWith(statusPageId: ObjectID): StatusPageDomain {
42
+ return {
43
+ statusPageId: statusPageId,
44
+ } as unknown as StatusPageDomain;
45
+ }
46
+
47
+ function spyOnDomainFindOneBy(): jest.SpyInstance {
48
+ return jest.spyOn(
49
+ StatusPageDomainService,
50
+ "findOneBy",
51
+ ) as unknown as jest.SpyInstance;
52
+ }
53
+
54
+ function queriedDomains(spy: jest.SpyInstance): Array<string> {
55
+ return spy.mock.calls.map((call: Array<unknown>) => {
56
+ const args: Record<string, any> = call[0] as Record<string, any>;
57
+ return args["query"]["fullDomain"] as string;
58
+ });
59
+ }
60
+
61
+ describe("StatusPageService domain resolution cache", () => {
62
+ beforeEach(() => {
63
+ /*
64
+ * The cache lives on the singleton service, so entries written by one
65
+ * test would leak into the next (and into other suites sharing this
66
+ * module registry) without an explicit clear.
67
+ */
68
+ StatusPageService.clearStatusPageDomainToIdCache();
69
+ });
70
+
71
+ afterEach(() => {
72
+ jest.restoreAllMocks();
73
+ });
74
+
75
+ test("caches a successful resolution: second call within TTL issues no query", async () => {
76
+ const domainSpy: jest.SpyInstance =
77
+ spyOnDomainFindOneBy().mockResolvedValue(
78
+ statusPageDomainWith(new ObjectID(VALID_UUID)) as never,
79
+ );
80
+
81
+ const first: ObjectID | null =
82
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
83
+ const second: ObjectID | null =
84
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
85
+
86
+ expect(first?.toString()).toBe(VALID_UUID);
87
+ expect(second?.toString()).toBe(VALID_UUID);
88
+ expect(domainSpy).toHaveBeenCalledTimes(1);
89
+ });
90
+
91
+ test("does not cache a miss: two calls for an unknown domain issue two queries", async () => {
92
+ const domainSpy: jest.SpyInstance =
93
+ spyOnDomainFindOneBy().mockResolvedValue(null as never);
94
+
95
+ await expect(
96
+ StatusPageService.resolveStatusPageIdOrNull(DOMAIN),
97
+ ).resolves.toBeNull();
98
+ await expect(
99
+ StatusPageService.resolveStatusPageIdOrNull(DOMAIN),
100
+ ).resolves.toBeNull();
101
+
102
+ expect(domainSpy).toHaveBeenCalledTimes(2);
103
+ });
104
+
105
+ test("expires after the 60s TTL and re-queries", async () => {
106
+ const base: number = Date.now();
107
+ let nowMs: number = base;
108
+ jest.spyOn(Date, "now").mockImplementation((): number => {
109
+ return nowMs;
110
+ });
111
+
112
+ const domainSpy: jest.SpyInstance =
113
+ spyOnDomainFindOneBy().mockResolvedValue(
114
+ statusPageDomainWith(new ObjectID(VALID_UUID)) as never,
115
+ );
116
+
117
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
118
+ expect(domainSpy).toHaveBeenCalledTimes(1);
119
+
120
+ // Just inside the TTL: still served from cache.
121
+ nowMs = base + TTL_MS - 1;
122
+ const withinTtl: ObjectID | null =
123
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
124
+ expect(withinTtl?.toString()).toBe(VALID_UUID);
125
+ expect(domainSpy).toHaveBeenCalledTimes(1);
126
+
127
+ // Past the TTL: the entry has expired and the domain is re-queried.
128
+ nowMs = base + TTL_MS + 1;
129
+ const afterTtl: ObjectID | null =
130
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
131
+ expect(afterTtl?.toString()).toBe(VALID_UUID);
132
+ expect(domainSpy).toHaveBeenCalledTimes(2);
133
+ });
134
+
135
+ test("keys are exactly as case-sensitive as the query: differently-cased domains each query", async () => {
136
+ const domainSpy: jest.SpyInstance =
137
+ spyOnDomainFindOneBy().mockResolvedValue(
138
+ statusPageDomainWith(new ObjectID(VALID_UUID)) as never,
139
+ );
140
+
141
+ await StatusPageService.resolveStatusPageIdOrNull("status.example.com");
142
+ await StatusPageService.resolveStatusPageIdOrNull("Status.Example.com");
143
+
144
+ /*
145
+ * If the cache normalized case it would resolve hostnames the underlying
146
+ * (case-sensitive) query would not, so a cached hit for one casing must
147
+ * not serve the other.
148
+ */
149
+ expect(domainSpy).toHaveBeenCalledTimes(2);
150
+ expect(queriedDomains(domainSpy)).toEqual([
151
+ "status.example.com",
152
+ "Status.Example.com",
153
+ ]);
154
+ });
155
+
156
+ test("clearStatusPageDomainToIdCache(domain) evicts only that domain", async () => {
157
+ const domainSpy: jest.SpyInstance =
158
+ spyOnDomainFindOneBy().mockImplementation(((
159
+ options: Record<string, any>,
160
+ ): Promise<StatusPageDomain> => {
161
+ const fullDomain: string = options["query"]["fullDomain"] as string;
162
+ return Promise.resolve(
163
+ statusPageDomainWith(
164
+ new ObjectID(fullDomain === DOMAIN ? VALID_UUID : SECOND_UUID),
165
+ ),
166
+ );
167
+ }) as never);
168
+
169
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
170
+ await StatusPageService.resolveStatusPageIdOrNull(OTHER_DOMAIN);
171
+ expect(domainSpy).toHaveBeenCalledTimes(2);
172
+
173
+ StatusPageService.clearStatusPageDomainToIdCache(DOMAIN);
174
+
175
+ const reresolved: ObjectID | null =
176
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
177
+ const stillCached: ObjectID | null =
178
+ await StatusPageService.resolveStatusPageIdOrNull(OTHER_DOMAIN);
179
+
180
+ expect(reresolved?.toString()).toBe(VALID_UUID);
181
+ expect(stillCached?.toString()).toBe(SECOND_UUID);
182
+ // Only the evicted domain re-queried; the other stayed cached.
183
+ expect(domainSpy).toHaveBeenCalledTimes(3);
184
+ expect(queriedDomains(domainSpy)).toEqual([DOMAIN, OTHER_DOMAIN, DOMAIN]);
185
+ });
186
+
187
+ test("clearStatusPageDomainToIdCache() with no argument clears everything", async () => {
188
+ const domainSpy: jest.SpyInstance =
189
+ spyOnDomainFindOneBy().mockResolvedValue(
190
+ statusPageDomainWith(new ObjectID(VALID_UUID)) as never,
191
+ );
192
+
193
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
194
+ await StatusPageService.resolveStatusPageIdOrNull(OTHER_DOMAIN);
195
+ expect(domainSpy).toHaveBeenCalledTimes(2);
196
+
197
+ StatusPageService.clearStatusPageDomainToIdCache();
198
+
199
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
200
+ await StatusPageService.resolveStatusPageIdOrNull(OTHER_DOMAIN);
201
+ expect(domainSpy).toHaveBeenCalledTimes(4);
202
+ });
203
+
204
+ test("UUID input never touches the domain cache or the domain service", async () => {
205
+ const domainSpy: jest.SpyInstance = spyOnDomainFindOneBy();
206
+
207
+ const result: ObjectID | null =
208
+ await StatusPageService.resolveStatusPageIdOrNull(VALID_UUID);
209
+
210
+ expect(result?.toString()).toBe(VALID_UUID);
211
+ expect(domainSpy).not.toHaveBeenCalled();
212
+ });
213
+
214
+ test("invalid dotless input returns null without querying", async () => {
215
+ const domainSpy: jest.SpyInstance = spyOnDomainFindOneBy();
216
+
217
+ await expect(
218
+ StatusPageService.resolveStatusPageIdOrNull("localhost"),
219
+ ).resolves.toBeNull();
220
+ expect(domainSpy).not.toHaveBeenCalled();
221
+ });
222
+
223
+ test("cached hits return a fresh ObjectID each call, so mutation cannot poison the cache", async () => {
224
+ spyOnDomainFindOneBy().mockResolvedValue(
225
+ statusPageDomainWith(new ObjectID(VALID_UUID)) as never,
226
+ );
227
+
228
+ // First call fills the cache; the next two are served from it.
229
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
230
+ const firstHit: ObjectID | null =
231
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
232
+ const secondHit: ObjectID | null =
233
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
234
+
235
+ expect(firstHit?.toString()).toBe(VALID_UUID);
236
+ expect(secondHit?.toString()).toBe(VALID_UUID);
237
+ expect(firstHit).not.toBe(secondHit);
238
+
239
+ // Mutating a returned instance must not leak into later hits.
240
+ firstHit!.id = "poisoned";
241
+ const thirdHit: ObjectID | null =
242
+ await StatusPageService.resolveStatusPageIdOrNull(DOMAIN);
243
+ expect(thirdHit?.toString()).toBe(VALID_UUID);
244
+ });
245
+ });
@@ -0,0 +1,283 @@
1
+ import StatusPageAPI from "../../../Server/API/StatusPageAPI";
2
+ import StatusPageService from "../../../Server/Services/StatusPageService";
3
+ import StatusPageGroupService from "../../../Server/Services/StatusPageGroupService";
4
+ import StatusPageResourceService from "../../../Server/Services/StatusPageResourceService";
5
+ import MonitorStatusService from "../../../Server/Services/MonitorStatusService";
6
+ import MonitorGroupService from "../../../Server/Services/MonitorGroupService";
7
+ import StatusPage from "../../../Models/DatabaseModels/StatusPage";
8
+ import ObjectID from "../../../Types/ObjectID";
9
+ import OneUptimeDate from "../../../Types/Date";
10
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
11
+
12
+ /*
13
+ * StatusPageAPI.getStatusPageResourcesAndTimelines timeline-window
14
+ * computation.
15
+ *
16
+ * The overview endpoint used to issue a SEPARATE one-column StatusPage query
17
+ * (statusPageForDays) just to read showUptimeHistoryInDays and compute the
18
+ * monitor-timeline window. That query was deleted: the helper's
19
+ * startDateForMonitorTimeline/endDateForMonitorTimeline params became
20
+ * optional, and when omitted the window is computed from the status page row
21
+ * the helper ALREADY fetches — showUptimeHistoryInDays clamped to 1..90 days
22
+ * (falsy hits `|| 90` BEFORE the < 1 clamp, so 0 means 90, not 1), ending
23
+ * now. The computed window is also RETURNED so the overview handler can keep
24
+ * serving it to the client.
25
+ *
26
+ * These tests pin: verbatim forwarding of explicit dates, the computed
27
+ * window and its clamp ORDER, that the returned dates are exactly what the
28
+ * timeline query received, and that the helper issues exactly ONE
29
+ * StatusPageService.findOneBy — so the deleted per-request query cannot
30
+ * sneak back on the hottest public endpoint in the product.
31
+ *
32
+ * All service reads are spied on — no database is touched.
33
+ */
34
+
35
+ type ResourcesAndTimelinesResult = Awaited<
36
+ ReturnType<StatusPageAPI["getStatusPageResourcesAndTimelines"]>
37
+ >;
38
+
39
+ type TimelineHelperArgs = {
40
+ monitorIds: Array<ObjectID>;
41
+ startDate: Date;
42
+ endDate: Date;
43
+ };
44
+
45
+ function makeStatusPage(showUptimeHistoryInDays: number | undefined): {
46
+ statusPageId: ObjectID;
47
+ statusPage: StatusPage;
48
+ } {
49
+ const statusPageId: ObjectID = ObjectID.generate();
50
+ const statusPage: StatusPage = new StatusPage();
51
+ statusPage._id = statusPageId.toString();
52
+ statusPage.projectId = ObjectID.generate();
53
+ if (showUptimeHistoryInDays !== undefined) {
54
+ statusPage.showUptimeHistoryInDays = showUptimeHistoryInDays;
55
+ }
56
+ return { statusPageId, statusPage };
57
+ }
58
+
59
+ type Spies = {
60
+ findOneBySpy: jest.SpyInstance;
61
+ timelineSpy: jest.SpyInstance;
62
+ };
63
+
64
+ /*
65
+ * The page is kept EMPTY (no resources, no groups) so every downstream loop
66
+ * short-circuits and the only interesting behavior left is the window
67
+ * computation itself.
68
+ */
69
+ function mockEmptyStatusPage(statusPage: StatusPage): Spies {
70
+ const findOneBySpy: jest.SpyInstance = (
71
+ jest.spyOn(StatusPageService, "findOneBy") as unknown as jest.SpyInstance
72
+ ).mockResolvedValue(statusPage as never);
73
+
74
+ (
75
+ jest.spyOn(MonitorStatusService, "findBy") as unknown as jest.SpyInstance
76
+ ).mockResolvedValue([] as never);
77
+
78
+ (
79
+ jest.spyOn(StatusPageGroupService, "findBy") as unknown as jest.SpyInstance
80
+ ).mockResolvedValue([] as never);
81
+
82
+ (
83
+ jest.spyOn(
84
+ StatusPageResourceService,
85
+ "findBy",
86
+ ) as unknown as jest.SpyInstance
87
+ ).mockResolvedValue([] as never);
88
+
89
+ (
90
+ jest.spyOn(
91
+ MonitorGroupService,
92
+ "getMonitorGroupResourcesByGroupIds",
93
+ ) as unknown as jest.SpyInstance
94
+ ).mockResolvedValue({} as never);
95
+
96
+ (
97
+ jest.spyOn(
98
+ MonitorGroupService,
99
+ "getCurrentStatusesForMonitorGroups",
100
+ ) as unknown as jest.SpyInstance
101
+ ).mockResolvedValue({} as never);
102
+
103
+ const timelineSpy: jest.SpyInstance = (
104
+ jest.spyOn(
105
+ StatusPageService,
106
+ "getMonitorStatusTimelineForStatusPage",
107
+ ) as unknown as jest.SpyInstance
108
+ ).mockResolvedValue([] as never);
109
+
110
+ return { findOneBySpy, timelineSpy };
111
+ }
112
+
113
+ function timelineArgs(timelineSpy: jest.SpyInstance): TimelineHelperArgs {
114
+ expect(timelineSpy).toHaveBeenCalledTimes(1);
115
+ return timelineSpy.mock.calls[0]![0] as TimelineHelperArgs;
116
+ }
117
+
118
+ function dayDifference(startDate: Date, endDate: Date): number {
119
+ const msPerDay: number = 24 * 60 * 60 * 1000;
120
+ return (endDate.getTime() - startDate.getTime()) / msPerDay;
121
+ }
122
+
123
+ async function runWithConfiguredDays(data: {
124
+ showUptimeHistoryInDays: number | undefined;
125
+ }): Promise<{
126
+ result: ResourcesAndTimelinesResult;
127
+ spies: Spies;
128
+ }> {
129
+ const { statusPageId, statusPage } = makeStatusPage(
130
+ data.showUptimeHistoryInDays,
131
+ );
132
+ const spies: Spies = mockEmptyStatusPage(statusPage);
133
+ const api: StatusPageAPI = new StatusPageAPI();
134
+
135
+ const result: ResourcesAndTimelinesResult =
136
+ await api.getStatusPageResourcesAndTimelines({
137
+ statusPageId: statusPageId,
138
+ });
139
+
140
+ return { result, spies };
141
+ }
142
+
143
+ async function expectComputedWindowOfDays(data: {
144
+ showUptimeHistoryInDays: number | undefined;
145
+ expectedDays: number;
146
+ }): Promise<void> {
147
+ const { result, spies } = await runWithConfiguredDays({
148
+ showUptimeHistoryInDays: data.showUptimeHistoryInDays,
149
+ });
150
+
151
+ const args: TimelineHelperArgs = timelineArgs(spies.timelineSpy);
152
+
153
+ // endDate is "now" (generous tolerance for slow CI).
154
+ expect(
155
+ Math.abs(args.endDate.getTime() - OneUptimeDate.getCurrentDate().getTime()),
156
+ ).toBeLessThan(10 * 1000);
157
+
158
+ // The window spans exactly the clamped number of days.
159
+ expect(dayDifference(args.startDate, args.endDate)).toBeCloseTo(
160
+ data.expectedDays,
161
+ 2,
162
+ );
163
+
164
+ // The returned window is EXACTLY what the timeline query received.
165
+ expect(result.startDateForMonitorTimeline).toBe(args.startDate);
166
+ expect(result.endDateForMonitorTimeline).toBe(args.endDate);
167
+
168
+ // Exactly one StatusPage row fetch — the deleted query must not return.
169
+ expect(spies.findOneBySpy).toHaveBeenCalledTimes(1);
170
+ }
171
+
172
+ describe("StatusPageAPI.getStatusPageResourcesAndTimelines timeline window", () => {
173
+ afterEach(() => {
174
+ jest.restoreAllMocks();
175
+ });
176
+
177
+ describe("explicit dates", () => {
178
+ test("are forwarded verbatim to the timeline query and returned unchanged", async () => {
179
+ const { statusPageId, statusPage } = makeStatusPage(45);
180
+ const spies: Spies = mockEmptyStatusPage(statusPage);
181
+ const getSomeDaysAgoSpy: jest.SpyInstance = jest.spyOn(
182
+ OneUptimeDate,
183
+ "getSomeDaysAgo",
184
+ ) as unknown as jest.SpyInstance;
185
+ const api: StatusPageAPI = new StatusPageAPI();
186
+
187
+ const explicitStartDate: Date = new Date("2026-01-05T00:00:00.000Z");
188
+ const explicitEndDate: Date = new Date("2026-01-19T00:00:00.000Z");
189
+
190
+ const result: ResourcesAndTimelinesResult =
191
+ await api.getStatusPageResourcesAndTimelines({
192
+ statusPageId: statusPageId,
193
+ startDateForMonitorTimeline: explicitStartDate,
194
+ endDateForMonitorTimeline: explicitEndDate,
195
+ });
196
+
197
+ const args: TimelineHelperArgs = timelineArgs(spies.timelineSpy);
198
+ // Same Date INSTANCES, not recomputed lookalikes.
199
+ expect(args.startDate).toBe(explicitStartDate);
200
+ expect(args.endDate).toBe(explicitEndDate);
201
+ expect(result.startDateForMonitorTimeline).toBe(explicitStartDate);
202
+ expect(result.endDateForMonitorTimeline).toBe(explicitEndDate);
203
+
204
+ // No window computation happened (showUptimeHistoryInDays ignored).
205
+ expect(getSomeDaysAgoSpy).not.toHaveBeenCalled();
206
+ expect(spies.findOneBySpy).toHaveBeenCalledTimes(1);
207
+ });
208
+
209
+ test("supplying only ONE of the two dates recomputes BOTH from the page config", async () => {
210
+ /*
211
+ * The guard is `!startDate || !endDate` — a half-specified window is
212
+ * treated as unspecified, so the caller-provided half is DISCARDED in
213
+ * favor of the page-configured window.
214
+ */
215
+ const { statusPageId, statusPage } = makeStatusPage(45);
216
+ const spies: Spies = mockEmptyStatusPage(statusPage);
217
+ const api: StatusPageAPI = new StatusPageAPI();
218
+
219
+ const explicitStartDate: Date = new Date("2026-01-05T00:00:00.000Z");
220
+
221
+ const result: ResourcesAndTimelinesResult =
222
+ await api.getStatusPageResourcesAndTimelines({
223
+ statusPageId: statusPageId,
224
+ startDateForMonitorTimeline: explicitStartDate,
225
+ });
226
+
227
+ const args: TimelineHelperArgs = timelineArgs(spies.timelineSpy);
228
+ expect(args.startDate).not.toBe(explicitStartDate);
229
+ expect(dayDifference(args.startDate, args.endDate)).toBeCloseTo(45, 2);
230
+ expect(result.startDateForMonitorTimeline).toBe(args.startDate);
231
+ expect(result.endDateForMonitorTimeline).toBe(args.endDate);
232
+ });
233
+ });
234
+
235
+ describe("omitted dates: window computed from showUptimeHistoryInDays", () => {
236
+ test("45 configured days -> 45-day window ending now", async () => {
237
+ await expectComputedWindowOfDays({
238
+ showUptimeHistoryInDays: 45,
239
+ expectedDays: 45,
240
+ });
241
+ });
242
+
243
+ test("200 configured days clamps DOWN to a 90-day window", async () => {
244
+ await expectComputedWindowOfDays({
245
+ showUptimeHistoryInDays: 200,
246
+ expectedDays: 90,
247
+ });
248
+ });
249
+
250
+ test("undefined (column never set) defaults to a 90-day window", async () => {
251
+ await expectComputedWindowOfDays({
252
+ showUptimeHistoryInDays: undefined,
253
+ expectedDays: 90,
254
+ });
255
+ });
256
+
257
+ test("0 hits the `|| 90` falsy default BEFORE the < 1 clamp -> 90 days, NOT 1", async () => {
258
+ /*
259
+ * ORDER matters and is pinned: `showUptimeHistoryInDays || 90` runs
260
+ * first, so a falsy 0 becomes the 90-day default. Only truthy
261
+ * sub-1 values (fractions, negatives) reach the < 1 clamp below it.
262
+ */
263
+ await expectComputedWindowOfDays({
264
+ showUptimeHistoryInDays: 0,
265
+ expectedDays: 90,
266
+ });
267
+ });
268
+
269
+ test("0.5 (truthy but < 1) clamps UP to a 1-day window", async () => {
270
+ await expectComputedWindowOfDays({
271
+ showUptimeHistoryInDays: 0.5,
272
+ expectedDays: 1,
273
+ });
274
+ });
275
+
276
+ test("-5 (truthy, < 1) clamps UP to a 1-day window", async () => {
277
+ await expectComputedWindowOfDays({
278
+ showUptimeHistoryInDays: -5,
279
+ expectedDays: 1,
280
+ });
281
+ });
282
+ });
283
+ });
@@ -18,7 +18,14 @@ import StatusPageDomainService from "../../../Server/Services/StatusPageDomainSe
18
18
  import StatusPage from "../../../Models/DatabaseModels/StatusPage";
19
19
  import StatusPageDomain from "../../../Models/DatabaseModels/StatusPageDomain";
20
20
  import ObjectID from "../../../Types/ObjectID";
21
- import { afterEach, describe, expect, test, jest } from "@jest/globals";
21
+ import {
22
+ afterEach,
23
+ beforeEach,
24
+ describe,
25
+ expect,
26
+ test,
27
+ jest,
28
+ } from "@jest/globals";
22
29
 
23
30
  const VALID_UUID: string = "550e8400-e29b-41d4-a716-446655440000";
24
31
  const DOMAIN: string = "status.company.com";
@@ -48,6 +55,15 @@ function spyOnDomainFindOneBy(): jest.SpyInstance {
48
55
  }
49
56
 
50
57
  describe("StatusPageService MCP gate", () => {
58
+ beforeEach(() => {
59
+ /*
60
+ * resolveStatusPageIdOrNull caches successful domain resolutions on the
61
+ * singleton service, so a hit in one test would short-circuit the mocked
62
+ * domain query in the next.
63
+ */
64
+ StatusPageService.clearStatusPageDomainToIdCache();
65
+ });
66
+
51
67
  afterEach(() => {
52
68
  jest.restoreAllMocks();
53
69
  });