@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,264 @@
1
+ import {
2
+ applyIncidentRelatedRecordPrivacyFilter,
3
+ applyIncidentSelfPrivacyFilter,
4
+ getIncidentRelatedRecordPrivacyRaw,
5
+ getIncidentSelfPrivacyRaw,
6
+ shouldBypassIncidentPrivacy,
7
+ } from "../../../../Server/Utils/Incident/IncidentPrivacyFilter";
8
+ import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
9
+ import ObjectID from "../../../../Types/ObjectID";
10
+ import Permission, {
11
+ UserPermission,
12
+ UserTenantAccessPermission,
13
+ } from "../../../../Types/Permission";
14
+ import { describe, expect, test } from "@jest/globals";
15
+ import { FindOperator } from "typeorm";
16
+
17
+ /*
18
+ * IncidentPrivacyFilter decides who may see a private incident. Getting it
19
+ * wrong either leaks private incidents to every project member, or hides an
20
+ * owner's own incidents from them - so each branch is pinned here against the
21
+ * SQL the Raw clause actually emits, not just its presence.
22
+ */
23
+
24
+ const userId: ObjectID = ObjectID.generate();
25
+ const tenantId: ObjectID = ObjectID.generate();
26
+
27
+ type MakePermFunction = (
28
+ permission: Permission,
29
+ ) => DatabaseCommonInteractionProps;
30
+
31
+ // A logged-in project member holding exactly the one permission named.
32
+ const propsWithProjectPermission: MakePermFunction = (
33
+ permission: Permission,
34
+ ): DatabaseCommonInteractionProps => {
35
+ const userPermission: UserPermission = {
36
+ _type: "UserPermission",
37
+ permission: permission,
38
+ labelIds: [],
39
+ };
40
+
41
+ const tenantPermission: UserTenantAccessPermission = {
42
+ _type: "UserTenantAccessPermission",
43
+ projectId: tenantId,
44
+ permissions: [userPermission],
45
+ };
46
+
47
+ return {
48
+ userId: userId,
49
+ tenantId: tenantId,
50
+ userTenantAccessPermission: {
51
+ [tenantId.toString()]: tenantPermission,
52
+ },
53
+ };
54
+ };
55
+
56
+ // Pull the SQL + bound params out of a typeorm Raw() FindOperator.
57
+ /* eslint-disable @typescript-eslint/no-explicit-any */
58
+ type RawParts = { sql: string; params: Record<string, string> };
59
+
60
+ const rawParts: (clause: unknown) => RawParts = (clause: unknown): RawParts => {
61
+ const operator: any = clause as any;
62
+
63
+ return {
64
+ sql: String(operator._getSql("COLUMN")),
65
+ params: (operator._objectLiteralParameters || {}) as Record<string, string>,
66
+ };
67
+ };
68
+ /* eslint-enable @typescript-eslint/no-explicit-any */
69
+
70
+ describe("shouldBypassIncidentPrivacy", () => {
71
+ test("root and master-admin contexts see everything", () => {
72
+ expect(shouldBypassIncidentPrivacy({ isRoot: true })).toBe(true);
73
+ expect(shouldBypassIncidentPrivacy({ isMasterAdmin: true })).toBe(true);
74
+ });
75
+
76
+ test("project owners and admins bypass the filter", () => {
77
+ expect(
78
+ shouldBypassIncidentPrivacy(
79
+ propsWithProjectPermission(Permission.ProjectOwner),
80
+ ),
81
+ ).toBe(true);
82
+ expect(
83
+ shouldBypassIncidentPrivacy(
84
+ propsWithProjectPermission(Permission.ProjectAdmin),
85
+ ),
86
+ ).toBe(true);
87
+ });
88
+
89
+ test("an ordinary project member does NOT bypass", () => {
90
+ expect(
91
+ shouldBypassIncidentPrivacy(
92
+ propsWithProjectPermission(Permission.ProjectMember),
93
+ ),
94
+ ).toBe(false);
95
+ });
96
+
97
+ test("an admin permission carried under a DIFFERENT tenant does not count", () => {
98
+ const props: DatabaseCommonInteractionProps = propsWithProjectPermission(
99
+ Permission.ProjectOwner,
100
+ );
101
+
102
+ // Caller now claims a tenant they hold no permission entry for.
103
+ props.tenantId = ObjectID.generate();
104
+
105
+ expect(shouldBypassIncidentPrivacy(props)).toBe(false);
106
+ });
107
+
108
+ test("no tenant / no permissions is not a bypass", () => {
109
+ expect(shouldBypassIncidentPrivacy({ userId: userId })).toBe(false);
110
+ expect(shouldBypassIncidentPrivacy({})).toBe(false);
111
+ });
112
+ });
113
+
114
+ describe("getIncidentSelfPrivacyRaw", () => {
115
+ test("returns undefined for a caller who bypasses the filter", () => {
116
+ expect(getIncidentSelfPrivacyRaw({ isRoot: true })).toBeUndefined();
117
+ expect(
118
+ getIncidentSelfPrivacyRaw(
119
+ propsWithProjectPermission(Permission.ProjectAdmin),
120
+ ),
121
+ ).toBeUndefined();
122
+ });
123
+
124
+ test("an anonymous caller only ever sees non-private incidents, with no bound params", () => {
125
+ const clause: unknown = getIncidentSelfPrivacyRaw({});
126
+
127
+ expect(clause).toBeInstanceOf(FindOperator);
128
+
129
+ const { sql, params }: RawParts = rawParts(clause);
130
+
131
+ // Non-private rows only: NULL or FALSE. No owner escape hatch.
132
+ expect(sql).toContain("COLUMN IS NULL OR COLUMN = FALSE");
133
+ expect(sql).not.toContain("IncidentOwnerUser");
134
+ expect(Object.keys(params)).toHaveLength(0);
135
+ });
136
+
137
+ test("a logged-in user also sees private incidents they own directly or via a team", () => {
138
+ const clause: unknown = getIncidentSelfPrivacyRaw({ userId: userId });
139
+
140
+ const { sql, params }: RawParts = rawParts(clause);
141
+
142
+ expect(sql).toContain("COLUMN IS NULL OR COLUMN = FALSE");
143
+ expect(sql).toContain("IncidentOwnerUser");
144
+ expect(sql).toContain("IncidentOwnerTeam");
145
+ expect(sql).toContain("TeamMember");
146
+ // Soft-deleted ownership rows must not grant visibility.
147
+ expect(sql).toContain(`iou."deletedAt" IS NULL`);
148
+ expect(sql).toContain(`tm."deletedAt" IS NULL`);
149
+
150
+ // The clause is bound to exactly this user, once.
151
+ const values: Array<string> = Object.values(params);
152
+ expect(values).toEqual([userId.toString()]);
153
+ });
154
+
155
+ test("uses a fresh parameter name per call so two clauses cannot collide", () => {
156
+ const first: RawParts = rawParts(getIncidentSelfPrivacyRaw({ userId }));
157
+ const second: RawParts = rawParts(getIncidentSelfPrivacyRaw({ userId }));
158
+
159
+ expect(Object.keys(first.params)).not.toEqual(Object.keys(second.params));
160
+ });
161
+ });
162
+
163
+ describe("getIncidentRelatedRecordPrivacyRaw", () => {
164
+ test("returns undefined for a bypassing caller", () => {
165
+ expect(
166
+ getIncidentRelatedRecordPrivacyRaw({ isRoot: true }),
167
+ ).toBeUndefined();
168
+ });
169
+
170
+ test("anonymous callers are scoped to child rows of non-private incidents only", () => {
171
+ const clause: unknown = getIncidentRelatedRecordPrivacyRaw({});
172
+
173
+ const { sql, params }: RawParts = rawParts(clause);
174
+
175
+ // Child rows are gated on the parent Incident being non-private.
176
+ expect(sql).toContain(`FROM "Incident" i`);
177
+ expect(sql).toContain(`i."isPrivate" IS NULL OR i."isPrivate" = FALSE`);
178
+ expect(sql).not.toContain("IncidentOwnerUser");
179
+ expect(Object.keys(params)).toHaveLength(0);
180
+ });
181
+
182
+ test("a logged-in user's child rows also include incidents they own", () => {
183
+ const clause: unknown = getIncidentRelatedRecordPrivacyRaw({ userId });
184
+
185
+ const { sql, params }: RawParts = rawParts(clause);
186
+
187
+ expect(sql).toContain("IncidentOwnerUser");
188
+ expect(sql).toContain("IncidentOwnerTeam");
189
+ expect(Object.values(params)).toEqual([userId.toString()]);
190
+ });
191
+ });
192
+
193
+ describe("applyIncidentSelfPrivacyFilter", () => {
194
+ test("returns the query untouched for a bypassing caller", () => {
195
+ const query: { projectId: ObjectID } = { projectId: tenantId };
196
+ const result: { projectId: ObjectID; isPrivate?: unknown } =
197
+ applyIncidentSelfPrivacyFilter(query, { isRoot: true });
198
+
199
+ expect(result).toBe(query);
200
+ expect(result.isPrivate).toBeUndefined();
201
+ });
202
+
203
+ test("builds a fresh query object when none is supplied", () => {
204
+ const result: { isPrivate?: unknown } = applyIncidentSelfPrivacyFilter(
205
+ undefined as unknown as { isPrivate?: unknown },
206
+ { userId },
207
+ );
208
+
209
+ expect(result.isPrivate).toBeInstanceOf(FindOperator);
210
+ });
211
+
212
+ test("sets the privacy clause on isPrivate while leaving other keys intact", () => {
213
+ const projectId: ObjectID = ObjectID.generate();
214
+ const result: { projectId: ObjectID; isPrivate?: unknown } =
215
+ applyIncidentSelfPrivacyFilter({ projectId }, { userId });
216
+
217
+ expect(result.projectId).toBe(projectId);
218
+ expect(result.isPrivate).toBeInstanceOf(FindOperator);
219
+ });
220
+
221
+ test("ANDs an existing isPrivate value together with the forced clause", () => {
222
+ const result: { isPrivate?: unknown } = applyIncidentSelfPrivacyFilter(
223
+ { isPrivate: false },
224
+ { userId },
225
+ );
226
+
227
+ // combineWithPrivacyClause wraps the caller value + clause in And(...).
228
+ const rendered: unknown = result.isPrivate;
229
+ expect(rendered).toBeInstanceOf(FindOperator);
230
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
231
+ expect(String((rendered as any)._type)).toBe("and");
232
+ });
233
+ });
234
+
235
+ describe("applyIncidentRelatedRecordPrivacyFilter", () => {
236
+ test("returns the query untouched for a bypassing caller", () => {
237
+ const query: { incidentId: ObjectID } = { incidentId: ObjectID.generate() };
238
+ const result: { incidentId: unknown } =
239
+ applyIncidentRelatedRecordPrivacyFilter(query, { isMasterAdmin: true });
240
+
241
+ expect(result).toBe(query);
242
+ });
243
+
244
+ test("builds { incidentId: clause } when no query is supplied", () => {
245
+ const result: { incidentId?: unknown } =
246
+ applyIncidentRelatedRecordPrivacyFilter(
247
+ undefined as unknown as { incidentId?: unknown },
248
+ { userId },
249
+ );
250
+
251
+ expect(result.incidentId).toBeInstanceOf(FindOperator);
252
+ });
253
+
254
+ test("ANDs an existing incidentId filter so a per-incident query cannot widen", () => {
255
+ const incidentId: ObjectID = ObjectID.generate();
256
+ const result: { incidentId?: unknown } =
257
+ applyIncidentRelatedRecordPrivacyFilter({ incidentId }, { userId });
258
+
259
+ const rendered: unknown = result.incidentId;
260
+ expect(rendered).toBeInstanceOf(FindOperator);
261
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
262
+ expect(String((rendered as any)._type)).toBe("and");
263
+ });
264
+ });
@@ -0,0 +1,175 @@
1
+ import {
2
+ applyIncidentEpisodeRelatedRecordPrivacyFilter,
3
+ applyIncidentEpisodeSelfPrivacyFilter,
4
+ getIncidentEpisodeRelatedRecordPrivacyRaw,
5
+ getIncidentEpisodeSelfPrivacyRaw,
6
+ shouldBypassIncidentEpisodePrivacy,
7
+ } from "../../../../Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter";
8
+ import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
9
+ import ObjectID from "../../../../Types/ObjectID";
10
+ import Permission, {
11
+ UserPermission,
12
+ UserTenantAccessPermission,
13
+ } from "../../../../Types/Permission";
14
+ import { describe, expect, test } from "@jest/globals";
15
+ import { FindOperator } from "typeorm";
16
+
17
+ /*
18
+ * Incident-episode privacy mirrors incident privacy: an episode is private when
19
+ * its incident is, and only owners (plus project owners/admins/root) may see
20
+ * private episodes. The emitted SQL is pinned so a wrong table/column or a
21
+ * dropped soft-delete guard is caught.
22
+ */
23
+
24
+ const userId: ObjectID = ObjectID.generate();
25
+ const tenantId: ObjectID = ObjectID.generate();
26
+
27
+ const propsWithProjectPermission: (
28
+ permission: Permission,
29
+ ) => DatabaseCommonInteractionProps = (
30
+ permission: Permission,
31
+ ): DatabaseCommonInteractionProps => {
32
+ const userPermission: UserPermission = {
33
+ _type: "UserPermission",
34
+ permission: permission,
35
+ labelIds: [],
36
+ };
37
+
38
+ const tenantPermission: UserTenantAccessPermission = {
39
+ _type: "UserTenantAccessPermission",
40
+ projectId: tenantId,
41
+ permissions: [userPermission],
42
+ };
43
+
44
+ return {
45
+ userId: userId,
46
+ tenantId: tenantId,
47
+ userTenantAccessPermission: {
48
+ [tenantId.toString()]: tenantPermission,
49
+ },
50
+ };
51
+ };
52
+
53
+ /* eslint-disable @typescript-eslint/no-explicit-any */
54
+ type RawParts = { sql: string; params: Record<string, string> };
55
+
56
+ const rawParts: (clause: unknown) => RawParts = (clause: unknown): RawParts => {
57
+ const operator: any = clause as any;
58
+
59
+ return {
60
+ sql: String(operator._getSql("COLUMN")),
61
+ params: (operator._objectLiteralParameters || {}) as Record<string, string>,
62
+ };
63
+ };
64
+ /* eslint-enable @typescript-eslint/no-explicit-any */
65
+
66
+ describe("shouldBypassIncidentEpisodePrivacy", () => {
67
+ test("root, master-admin, owner and admin bypass; member does not", () => {
68
+ expect(shouldBypassIncidentEpisodePrivacy({ isRoot: true })).toBe(true);
69
+ expect(shouldBypassIncidentEpisodePrivacy({ isMasterAdmin: true })).toBe(
70
+ true,
71
+ );
72
+ expect(
73
+ shouldBypassIncidentEpisodePrivacy(
74
+ propsWithProjectPermission(Permission.ProjectOwner),
75
+ ),
76
+ ).toBe(true);
77
+ expect(
78
+ shouldBypassIncidentEpisodePrivacy(
79
+ propsWithProjectPermission(Permission.ProjectMember),
80
+ ),
81
+ ).toBe(false);
82
+ });
83
+ });
84
+
85
+ describe("getIncidentEpisodeSelfPrivacyRaw", () => {
86
+ test("bypassing callers get no clause", () => {
87
+ expect(getIncidentEpisodeSelfPrivacyRaw({ isRoot: true })).toBeUndefined();
88
+ });
89
+
90
+ test("anonymous callers only see non-private episodes, no params bound", () => {
91
+ const { sql, params }: RawParts = rawParts(
92
+ getIncidentEpisodeSelfPrivacyRaw({}),
93
+ );
94
+
95
+ expect(sql).toContain("COLUMN IS NULL OR COLUMN = FALSE");
96
+ expect(sql).not.toContain("IncidentEpisodeOwnerUser");
97
+ expect(Object.keys(params)).toHaveLength(0);
98
+ });
99
+
100
+ test("a user also sees episodes they own, bound to their id", () => {
101
+ const { sql, params }: RawParts = rawParts(
102
+ getIncidentEpisodeSelfPrivacyRaw({ userId }),
103
+ );
104
+
105
+ expect(sql).toContain("IncidentEpisodeOwnerUser");
106
+ expect(sql).toContain("IncidentEpisodeOwnerTeam");
107
+ expect(sql).toContain(`ieou."deletedAt" IS NULL`);
108
+ expect(Object.values(params)).toEqual([userId.toString()]);
109
+ });
110
+
111
+ test("fresh parameter name per call", () => {
112
+ const first: RawParts = rawParts(
113
+ getIncidentEpisodeSelfPrivacyRaw({ userId }),
114
+ );
115
+ const second: RawParts = rawParts(
116
+ getIncidentEpisodeSelfPrivacyRaw({ userId }),
117
+ );
118
+ expect(Object.keys(first.params)).not.toEqual(Object.keys(second.params));
119
+ });
120
+ });
121
+
122
+ describe("getIncidentEpisodeRelatedRecordPrivacyRaw", () => {
123
+ test("anonymous callers are scoped to child rows of non-private episodes", () => {
124
+ const { sql }: RawParts = rawParts(
125
+ getIncidentEpisodeRelatedRecordPrivacyRaw({}),
126
+ );
127
+
128
+ expect(sql).toContain(`FROM "IncidentEpisode" ie`);
129
+ expect(sql).toContain(`ie."isPrivate" IS NULL OR ie."isPrivate" = FALSE`);
130
+ });
131
+
132
+ test("a user's child rows include episodes they own", () => {
133
+ const { sql, params }: RawParts = rawParts(
134
+ getIncidentEpisodeRelatedRecordPrivacyRaw({ userId }),
135
+ );
136
+
137
+ expect(sql).toContain("IncidentEpisodeOwnerUser");
138
+ expect(Object.values(params)).toEqual([userId.toString()]);
139
+ });
140
+ });
141
+
142
+ describe("apply* incident-episode filters", () => {
143
+ test("bypassing callers get their query back untouched", () => {
144
+ const query: { isPrivate?: unknown } = {};
145
+ expect(applyIncidentEpisodeSelfPrivacyFilter(query, { isRoot: true })).toBe(
146
+ query,
147
+ );
148
+ });
149
+
150
+ test("self filter sets isPrivate; related filter builds { incidentEpisodeId }", () => {
151
+ const self: { isPrivate?: unknown } = applyIncidentEpisodeSelfPrivacyFilter(
152
+ {},
153
+ { userId },
154
+ );
155
+ expect(self.isPrivate).toBeInstanceOf(FindOperator);
156
+
157
+ const related: { incidentEpisodeId?: unknown } =
158
+ applyIncidentEpisodeRelatedRecordPrivacyFilter(
159
+ undefined as unknown as { incidentEpisodeId?: unknown },
160
+ { userId },
161
+ );
162
+ expect(related.incidentEpisodeId).toBeInstanceOf(FindOperator);
163
+ });
164
+
165
+ test("an existing incidentEpisodeId filter is ANDed, not replaced", () => {
166
+ const incidentEpisodeId: ObjectID = ObjectID.generate();
167
+ const combined: { incidentEpisodeId?: unknown } =
168
+ applyIncidentEpisodeRelatedRecordPrivacyFilter(
169
+ { incidentEpisodeId },
170
+ { userId },
171
+ );
172
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
173
+ expect(String((combined.incidentEpisodeId as any)._type)).toBe("and");
174
+ });
175
+ });
@@ -0,0 +1,181 @@
1
+ import MonitorStepsProjectValidator from "../../../../Server/Utils/Monitor/MonitorStepsProjectValidator";
2
+ import AlertSeverityService from "../../../../Server/Services/AlertSeverityService";
3
+ import IncidentSeverityService from "../../../../Server/Services/IncidentSeverityService";
4
+ import MonitorStatusService from "../../../../Server/Services/MonitorStatusService";
5
+ import AlertSeverity from "../../../../Models/DatabaseModels/AlertSeverity";
6
+ import IncidentSeverity from "../../../../Models/DatabaseModels/IncidentSeverity";
7
+ import MonitorStatus from "../../../../Models/DatabaseModels/MonitorStatus";
8
+ import BadDataException from "../../../../Types/Exception/BadDataException";
9
+ import ObjectID from "../../../../Types/ObjectID";
10
+ import { JSONObject } from "../../../../Types/JSON";
11
+ import { afterEach, describe, expect, it, jest } from "@jest/globals";
12
+
13
+ /*
14
+ * Contract under test: monitorSteps embeds ids of project-scoped records (the
15
+ * default monitor status, the status each criteria switches to, and the
16
+ * severities used to open incidents and alerts). Saving one that belongs to a
17
+ * *different* project is what tied two projects together in production and
18
+ * left the referenced project undeletable, so it must be rejected on write.
19
+ *
20
+ * The check has to stay narrow: monitorSteps is full of unrelated uuids (step
21
+ * ids, criteria ids, incident/alert template ids) that match no record at all,
22
+ * and those must keep saving fine.
23
+ */
24
+
25
+ const PROJECT_ID: ObjectID = new ObjectID(
26
+ "4af3a31b-58b0-4746-8025-f9cd4db1945e",
27
+ );
28
+ const OTHER_PROJECT_ID: ObjectID = new ObjectID(
29
+ "3376855b-361c-427c-8982-bad7ada30414",
30
+ );
31
+
32
+ const OWN_STATUS_ID: string = "59b5ab80-63f6-4ffd-b3df-31c6ad127695";
33
+ const FOREIGN_STATUS_ID: string = "cfc2f04f-79cb-4344-8c54-dafe5e3a290c";
34
+ const FOREIGN_ALERT_SEVERITY_ID: string =
35
+ "a2eb67d4-bd2e-4186-9187-dad799c9316c";
36
+
37
+ type FoundRecords = {
38
+ monitorStatuses?: Array<MonitorStatus>;
39
+ incidentSeverities?: Array<IncidentSeverity>;
40
+ alertSeverities?: Array<AlertSeverity>;
41
+ };
42
+
43
+ const monitorStatus: (id: string, projectId: ObjectID) => MonitorStatus = (
44
+ id: string,
45
+ projectId: ObjectID,
46
+ ): MonitorStatus => {
47
+ const status: MonitorStatus = new MonitorStatus();
48
+ status._id = id;
49
+ status.name = "Operational";
50
+ status.projectId = projectId;
51
+ return status;
52
+ };
53
+
54
+ const alertSeverity: (id: string, projectId: ObjectID) => AlertSeverity = (
55
+ id: string,
56
+ projectId: ObjectID,
57
+ ): AlertSeverity => {
58
+ const severity: AlertSeverity = new AlertSeverity();
59
+ severity._id = id;
60
+ severity.name = "High";
61
+ severity.projectId = projectId;
62
+ return severity;
63
+ };
64
+
65
+ const stubLookups: (found: FoundRecords) => void = (
66
+ found: FoundRecords,
67
+ ): void => {
68
+ jest
69
+ .spyOn(MonitorStatusService, "findBy")
70
+ .mockResolvedValue(found.monitorStatuses || []);
71
+ jest
72
+ .spyOn(IncidentSeverityService, "findBy")
73
+ .mockResolvedValue(found.incidentSeverities || []);
74
+ jest
75
+ .spyOn(AlertSeverityService, "findBy")
76
+ .mockResolvedValue(found.alertSeverities || []);
77
+ };
78
+
79
+ const stepsReferencing: (ids: Array<string>) => JSONObject = (
80
+ ids: Array<string>,
81
+ ): JSONObject => {
82
+ return {
83
+ _type: "MonitorSteps",
84
+ value: {
85
+ defaultMonitorStatusId: ids[0],
86
+ monitorStepsInstanceArray: ids.slice(1).map((id: string) => {
87
+ return { _type: "MonitorStep", value: { id: id } };
88
+ }),
89
+ },
90
+ };
91
+ };
92
+
93
+ describe("MonitorStepsProjectValidator", () => {
94
+ afterEach(() => {
95
+ jest.restoreAllMocks();
96
+ });
97
+
98
+ it("rejects a monitor status that belongs to another project", async () => {
99
+ stubLookups({
100
+ monitorStatuses: [monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID)],
101
+ });
102
+
103
+ await expect(
104
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
105
+ monitorSteps: stepsReferencing([FOREIGN_STATUS_ID]),
106
+ projectId: PROJECT_ID,
107
+ }),
108
+ ).rejects.toThrow(BadDataException);
109
+ });
110
+
111
+ it("names every offending record in the message", async () => {
112
+ stubLookups({
113
+ monitorStatuses: [monitorStatus(FOREIGN_STATUS_ID, OTHER_PROJECT_ID)],
114
+ alertSeverities: [
115
+ alertSeverity(FOREIGN_ALERT_SEVERITY_ID, OTHER_PROJECT_ID),
116
+ ],
117
+ });
118
+
119
+ await expect(
120
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
121
+ monitorSteps: stepsReferencing([
122
+ FOREIGN_STATUS_ID,
123
+ FOREIGN_ALERT_SEVERITY_ID,
124
+ ]),
125
+ projectId: PROJECT_ID,
126
+ }),
127
+ ).rejects.toThrow(
128
+ /Monitor Status "Operational".*Alert Severity "High"|Alert Severity "High".*Monitor Status "Operational"/,
129
+ );
130
+ });
131
+
132
+ it("accepts records that belong to the monitor's own project", async () => {
133
+ stubLookups({
134
+ monitorStatuses: [monitorStatus(OWN_STATUS_ID, PROJECT_ID)],
135
+ });
136
+
137
+ await expect(
138
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
139
+ monitorSteps: stepsReferencing([OWN_STATUS_ID]),
140
+ projectId: PROJECT_ID,
141
+ }),
142
+ ).resolves.toBeUndefined();
143
+ });
144
+
145
+ it("ignores uuids that are not project-scoped records", async () => {
146
+ // Step ids and criteria ids are uuids too, and match nothing.
147
+ stubLookups({});
148
+
149
+ await expect(
150
+ MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
151
+ monitorSteps: stepsReferencing([
152
+ OWN_STATUS_ID,
153
+ "679a113f-669e-4b0f-b3db-31eb2a794ed5",
154
+ "ff11aa22-bb33-cc44-dd55-000000000ee1",
155
+ ]),
156
+ projectId: PROJECT_ID,
157
+ }),
158
+ ).resolves.toBeUndefined();
159
+ });
160
+
161
+ it("does no lookups when there are no monitorSteps or no project", async () => {
162
+ /*
163
+ * stubLookups installs the spy, so the lookup is asserted on the service
164
+ * method itself. Annotating the spy handle is not an option here: this
165
+ * file takes jest from @jest/globals, so spyOn returns jest-mock's type
166
+ * while the jest.SpyInstance namespace resolves to @types/jest's.
167
+ */
168
+ stubLookups({});
169
+
170
+ await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
171
+ monitorSteps: undefined,
172
+ projectId: PROJECT_ID,
173
+ });
174
+ await MonitorStepsProjectValidator.validateMonitorStepsBelongToProject({
175
+ monitorSteps: stepsReferencing([OWN_STATUS_ID]),
176
+ projectId: undefined,
177
+ });
178
+
179
+ expect(MonitorStatusService.findBy).not.toHaveBeenCalled();
180
+ });
181
+ });
@@ -76,7 +76,7 @@ function mockPipeline(options?: {
76
76
  );
77
77
  deviceUpdateSpy = jest
78
78
  .spyOn(NetworkDeviceService, "updateOneById")
79
- .mockResolvedValue(undefined);
79
+ .mockResolvedValue(1);
80
80
  monitorFindSpy = jest
81
81
  .spyOn(MonitorService, "findBy")
82
82
  .mockResolvedValue(options?.monitors || []);
@@ -53,13 +53,13 @@ function mockServices(existingInterfaces: Array<NetworkInterface> = []): void {
53
53
  .mockResolvedValue(ownedDevice);
54
54
  deviceUpdateSpy = jest
55
55
  .spyOn(NetworkDeviceService, "updateOneById")
56
- .mockResolvedValue(undefined);
56
+ .mockResolvedValue(1);
57
57
  interfaceFindSpy = jest
58
58
  .spyOn(NetworkInterfaceService, "findBy")
59
59
  .mockResolvedValue(existingInterfaces);
60
60
  interfaceUpdateSpy = jest
61
61
  .spyOn(NetworkInterfaceService, "updateOneById")
62
- .mockResolvedValue(undefined);
62
+ .mockResolvedValue(1);
63
63
  interfaceCreateSpy = jest
64
64
  .spyOn(NetworkInterfaceService, "create")
65
65
  .mockResolvedValue(new NetworkInterface());