@oneuptime/common 11.7.3 → 11.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
  2. package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
  3. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  4. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  5. package/Models/DatabaseModels/Index.ts +5 -0
  6. package/Models/DatabaseModels/LogDropFilter.ts +89 -0
  7. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
  8. package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
  9. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  10. package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
  11. package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
  12. package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
  13. package/Server/API/AlertAPI.ts +32 -17
  14. package/Server/API/BaseAPI.ts +31 -2
  15. package/Server/API/BaseAnalyticsAPI.ts +36 -2
  16. package/Server/API/CommonAPI.ts +57 -0
  17. package/Server/API/IncidentAPI.ts +60 -33
  18. package/Server/API/IncidentEpisodeAPI.ts +31 -16
  19. package/Server/API/MonitorAPI.ts +30 -0
  20. package/Server/API/MonitorTemplateAPI.ts +8 -0
  21. package/Server/API/ProbeAPI.ts +196 -15
  22. package/Server/API/ProjectAPI.ts +79 -1
  23. package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
  24. package/Server/API/StatusPageAPI.ts +74 -37
  25. package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  31. package/Server/Services/AlertEpisodeService.ts +47 -0
  32. package/Server/Services/AlertService.ts +125 -0
  33. package/Server/Services/BillingService.ts +98 -0
  34. package/Server/Services/DatabaseService.ts +184 -20
  35. package/Server/Services/IncidentEpisodeService.ts +47 -0
  36. package/Server/Services/IncidentService.ts +123 -0
  37. package/Server/Services/IncidentTemplateService.ts +123 -1
  38. package/Server/Services/Index.ts +2 -0
  39. package/Server/Services/LogDropFilterService.ts +133 -0
  40. package/Server/Services/MonitorProbeService.ts +94 -4
  41. package/Server/Services/MonitorService.ts +48 -43
  42. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
  43. package/Server/Services/ProbeService.ts +66 -0
  44. package/Server/Services/ProjectService.ts +131 -7
  45. package/Server/Services/RecommendationDismissalService.ts +52 -0
  46. package/Server/Services/RunbookAgentJobService.ts +68 -7
  47. package/Server/Services/ScheduledMaintenanceService.ts +103 -0
  48. package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
  49. package/Server/Services/StatusPageGroupService.ts +186 -0
  50. package/Server/Services/TraceDropFilterService.ts +133 -0
  51. package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
  52. package/Server/Types/Workflow/Components/API/Get.ts +12 -0
  53. package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
  54. package/Server/Types/Workflow/Components/API/Post.ts +12 -0
  55. package/Server/Types/Workflow/Components/API/Put.ts +13 -0
  56. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
  57. package/Server/Utils/DropFilterValidation.ts +127 -0
  58. package/Server/Utils/Errors.ts +5 -0
  59. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  60. package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
  61. package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
  62. package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
  63. package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
  64. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
  65. package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
  66. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
  67. package/Tests/Server/API/ProbeAPI.test.ts +422 -53
  68. package/Tests/Server/API/ProjectAPI.test.ts +180 -0
  69. package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
  70. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
  71. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
  72. package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
  73. package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
  74. package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
  75. package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
  76. package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
  77. package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
  78. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
  79. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
  80. package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
  81. package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
  82. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
  83. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
  84. package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
  85. package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
  86. package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
  87. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
  88. package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
  89. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
  90. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
  91. package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
  92. package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
  93. package/Tests/Types/DateUserTimezone.test.ts +52 -0
  94. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
  95. package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
  96. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
  97. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
  98. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
  99. package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
  100. package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
  101. package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
  102. package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
  103. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
  104. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
  105. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
  106. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
  107. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
  108. package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
  109. package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
  110. package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
  111. package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
  112. package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
  113. package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
  114. package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
  115. package/Tests/UI/Components/InfoCard.test.tsx +358 -0
  116. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
  117. package/Tests/UI/Components/Modal.test.tsx +43 -5
  118. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
  119. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +427 -0
  120. package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
  121. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
  122. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
  123. package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
  124. package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
  125. package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
  126. package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
  127. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
  128. package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
  129. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
  130. package/Types/Date.ts +15 -6
  131. package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
  132. package/Types/Monitor/MonitorStep.ts +10 -1
  133. package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
  134. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
  135. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
  136. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
  137. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
  138. package/Types/Permission.ts +45 -0
  139. package/Types/Recommendation/RecommendationType.ts +20 -0
  140. package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
  141. package/Types/Runbook/RunbookStepTimeout.ts +202 -0
  142. package/Types/Telemetry/DropFilterSampling.ts +92 -0
  143. package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
  144. package/UI/Components/Accordion/Accordion.tsx +10 -2
  145. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
  146. package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
  147. package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
  148. package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
  149. package/UI/Components/Error/PageError.tsx +0 -4
  150. package/UI/Components/ErrorBoundary.tsx +252 -15
  151. package/UI/Components/InfoCard/InfoCard.tsx +58 -1
  152. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
  153. package/UI/Components/Modal/Modal.tsx +16 -60
  154. package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
  155. package/UI/Components/ModelTable/BaseModelTable.tsx +116 -67
  156. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +150 -47
  157. package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
  158. package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
  159. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
  160. package/UI/Utils/DropdownAlignment.ts +162 -0
  161. package/UI/Utils/TableFilterUrlState.ts +59 -20
  162. package/Utils/API.ts +106 -2
  163. package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
  164. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
  165. package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
  166. package/Utils/StatusPage/GroupTree.ts +307 -0
  167. package/Utils/StatusPage/ResourceUptime.ts +90 -17
  168. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
  169. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
  170. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
  171. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  172. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  173. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  174. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  175. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  176. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  177. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  178. package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
  179. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  180. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
  181. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  182. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
  183. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
  184. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  185. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
  187. package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
  189. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
  191. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  192. package/build/dist/Server/API/AlertAPI.js +22 -13
  193. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  194. package/build/dist/Server/API/BaseAPI.js +22 -1
  195. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  196. package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
  197. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  198. package/build/dist/Server/API/CommonAPI.js +42 -0
  199. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  200. package/build/dist/Server/API/IncidentAPI.js +43 -26
  201. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  202. package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
  203. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  204. package/build/dist/Server/API/MonitorAPI.js +23 -0
  205. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  206. package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
  207. package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
  208. package/build/dist/Server/API/ProbeAPI.js +131 -15
  209. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  210. package/build/dist/Server/API/ProjectAPI.js +62 -3
  211. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  212. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
  213. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  214. package/build/dist/Server/API/StatusPageAPI.js +61 -33
  215. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  216. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
  217. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
  218. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
  219. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
  220. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
  221. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  228. package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
  229. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  230. package/build/dist/Server/Services/AlertService.js +95 -0
  231. package/build/dist/Server/Services/AlertService.js.map +1 -1
  232. package/build/dist/Server/Services/BillingService.js +71 -0
  233. package/build/dist/Server/Services/BillingService.js.map +1 -1
  234. package/build/dist/Server/Services/DatabaseService.js +140 -17
  235. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  236. package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
  237. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  238. package/build/dist/Server/Services/IncidentService.js +100 -0
  239. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  240. package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
  241. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  242. package/build/dist/Server/Services/Index.js +2 -0
  243. package/build/dist/Server/Services/Index.js.map +1 -1
  244. package/build/dist/Server/Services/LogDropFilterService.js +93 -0
  245. package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
  246. package/build/dist/Server/Services/MonitorProbeService.js +73 -5
  247. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  248. package/build/dist/Server/Services/MonitorService.js +34 -39
  249. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  250. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
  251. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  252. package/build/dist/Server/Services/ProbeService.js +64 -0
  253. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  254. package/build/dist/Server/Services/ProjectService.js +103 -7
  255. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  256. package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
  257. package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
  258. package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
  259. package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
  260. package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
  261. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  262. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
  263. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  264. package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
  265. package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
  266. package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
  267. package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
  268. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
  269. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  270. package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
  271. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  272. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
  273. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  274. package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
  275. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  276. package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
  277. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  278. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
  279. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
  280. package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
  281. package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
  282. package/build/dist/Server/Utils/Errors.js +3 -0
  283. package/build/dist/Server/Utils/Errors.js.map +1 -1
  284. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
  285. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  286. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
  287. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  288. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
  289. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  290. package/build/dist/Types/Date.js +14 -4
  291. package/build/dist/Types/Date.js.map +1 -1
  292. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
  293. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  294. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  295. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  296. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
  297. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  298. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
  299. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
  300. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
  301. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
  302. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
  303. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
  304. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
  305. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
  306. package/build/dist/Types/Permission.js +40 -0
  307. package/build/dist/Types/Permission.js.map +1 -1
  308. package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
  309. package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
  310. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
  311. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
  312. package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
  313. package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
  314. package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
  315. package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
  316. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
  317. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  318. package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
  319. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  320. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
  321. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  322. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
  323. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  324. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
  325. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
  326. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
  327. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  328. package/build/dist/UI/Components/Error/PageError.js +1 -3
  329. package/build/dist/UI/Components/Error/PageError.js.map +1 -1
  330. package/build/dist/UI/Components/ErrorBoundary.js +145 -11
  331. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  332. package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
  333. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  334. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
  335. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  336. package/build/dist/UI/Components/Modal/Modal.js +4 -32
  337. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  338. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  339. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +69 -33
  340. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  341. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +73 -32
  342. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  343. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
  344. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
  345. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
  346. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
  347. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
  348. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  349. package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
  350. package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
  351. package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
  352. package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
  353. package/build/dist/Utils/API.js +39 -2
  354. package/build/dist/Utils/API.js.map +1 -1
  355. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
  356. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
  357. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
  358. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
  359. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
  360. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
  361. package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
  362. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
  363. package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
  364. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  365. package/package.json +1 -1
@@ -0,0 +1,196 @@
1
+ import MonitorProbe from "../../../Models/DatabaseModels/MonitorProbe";
2
+ import { ColumnAccessControl } from "../../../Types/BaseDatabase/AccessControl";
3
+ import Permission, { PermissionHelper } from "../../../Types/Permission";
4
+ import { describe, expect, it } from "@jest/globals";
5
+
6
+ /*
7
+ * https://github.com/OneUptime/oneuptime/issues/2899
8
+ *
9
+ * Monitor > Probes is where a user goes to change which probe watches a
10
+ * resource. The table is editable and declares a required "Probe" dropdown -
11
+ * but MonitorProbe.probe and MonitorProbe.probeId are create-only, so ModelForm
12
+ * applied the column's *update* ACL, found it empty, and dropped the field from
13
+ * the form and from the request. The Edit modal opened with nothing but an
14
+ * "Enabled" toggle, no message, and a "Save Changes" button; the table could
15
+ * not delete either. So a probe attached by mistake was permanent, and every
16
+ * attempt to change it looked like an edit that was thrown away.
17
+ *
18
+ * The fix keeps the columns create-only (a MonitorProbe row *is* the
19
+ * assignment; replacing it means deleting the row) and instead marks the form
20
+ * field doNotShowWhenEditing and turns row deletion on. These tests pin both
21
+ * halves of that contract, because breaking either one silently restores the
22
+ * original symptom.
23
+ */
24
+
25
+ // What a signed-in project owner actually carries.
26
+ const OWNER_PERMISSIONS: Array<Permission> = [
27
+ Permission.Public,
28
+ Permission.User,
29
+ Permission.CurrentUser,
30
+ Permission.ProjectOwner,
31
+ ];
32
+
33
+ /*
34
+ * ModelForm.hasPermissionOnField, reproduced. It reads the create ACL on a
35
+ * Create form and the update ACL on an Update form, and drops the field when
36
+ * the user's permissions do not intersect it - with no error unless *every*
37
+ * field is dropped.
38
+ */
39
+ type WouldFormShowFieldFunction = (data: {
40
+ accessControl: Record<string, ColumnAccessControl>;
41
+ columnName: string;
42
+ isUpdateForm: boolean;
43
+ userPermissions: Array<Permission>;
44
+ }) => boolean;
45
+
46
+ const wouldFormShowField: WouldFormShowFieldFunction = (data: {
47
+ accessControl: Record<string, ColumnAccessControl>;
48
+ columnName: string;
49
+ isUpdateForm: boolean;
50
+ userPermissions: Array<Permission>;
51
+ }): boolean => {
52
+ const column: ColumnAccessControl | undefined =
53
+ data.accessControl[data.columnName];
54
+
55
+ const fieldPermissions: Array<Permission> =
56
+ (data.isUpdateForm ? column?.update : column?.create) || [];
57
+
58
+ return PermissionHelper.doesPermissionsIntersect(
59
+ data.userPermissions,
60
+ fieldPermissions,
61
+ );
62
+ };
63
+
64
+ describe("MonitorProbe column access control", () => {
65
+ const accessControl: Record<string, ColumnAccessControl> =
66
+ new MonitorProbe().getColumnAccessControlForAllColumns();
67
+
68
+ it("lets a project owner choose the probe when attaching one", () => {
69
+ expect(
70
+ wouldFormShowField({
71
+ accessControl,
72
+ columnName: "probe",
73
+ isUpdateForm: false,
74
+ userPermissions: OWNER_PERMISSIONS,
75
+ }),
76
+ ).toBe(true);
77
+ });
78
+
79
+ it("keeps the probe relation create-only, so a row always points at one probe", () => {
80
+ /*
81
+ * This is the assumption Pages/Monitor/View/Probes.tsx has to encode with
82
+ * doNotShowWhenEditing. If someone later gives these columns an update
83
+ * ACL, that flag becomes wrong and should be removed with it.
84
+ */
85
+ expect(accessControl["probe"]?.update || []).toEqual([]);
86
+ expect(accessControl["probeId"]?.update || []).toEqual([]);
87
+ });
88
+
89
+ it("would silently drop a Probe field from the edit form for anyone at all", () => {
90
+ /*
91
+ * The exact mechanism behind the bug report: not "you may not do that",
92
+ * but a field that renders on create and vanishes on edit. Even a project
93
+ * owner loses it - which is why the form must not offer it when editing.
94
+ */
95
+ const everyPermission: Array<Permission> = Object.values(Permission);
96
+
97
+ expect(
98
+ wouldFormShowField({
99
+ accessControl,
100
+ columnName: "probe",
101
+ isUpdateForm: true,
102
+ userPermissions: everyPermission,
103
+ }),
104
+ ).toBe(false);
105
+ });
106
+
107
+ it("still lets a project owner toggle isEnabled, so the edit form is not empty", () => {
108
+ /*
109
+ * ModelForm only surfaces a permission error when it drops *every* field.
110
+ * isEnabled surviving is why the silent drop of "Probe" produced no
111
+ * message at all.
112
+ */
113
+ expect(
114
+ wouldFormShowField({
115
+ accessControl,
116
+ columnName: "isEnabled",
117
+ isUpdateForm: true,
118
+ userPermissions: OWNER_PERMISSIONS,
119
+ }),
120
+ ).toBe(true);
121
+ });
122
+
123
+ it("lets a user with only granular monitor-probe permissions manage the row", () => {
124
+ const granularPermissions: Array<Permission> = [
125
+ Permission.Public,
126
+ Permission.User,
127
+ Permission.CurrentUser,
128
+ Permission.CreateMonitorProbe,
129
+ Permission.ReadMonitorProbe,
130
+ Permission.EditMonitorProbe,
131
+ Permission.DeleteMonitorProbe,
132
+ ];
133
+
134
+ expect(
135
+ wouldFormShowField({
136
+ accessControl,
137
+ columnName: "probe",
138
+ isUpdateForm: false,
139
+ userPermissions: granularPermissions,
140
+ }),
141
+ ).toBe(true);
142
+
143
+ expect(
144
+ wouldFormShowField({
145
+ accessControl,
146
+ columnName: "isEnabled",
147
+ isUpdateForm: true,
148
+ userPermissions: granularPermissions,
149
+ }),
150
+ ).toBe(true);
151
+ });
152
+
153
+ it("reads back the probe relation, so the table can name the probe it shows", () => {
154
+ for (const columnName of ["probe", "probeId", "isEnabled"]) {
155
+ expect(
156
+ PermissionHelper.doesPermissionsIntersect(
157
+ OWNER_PERMISSIONS,
158
+ accessControl[columnName]?.read || [],
159
+ ),
160
+ ).toBe(true);
161
+ }
162
+ });
163
+ });
164
+
165
+ describe("MonitorProbe table access control", () => {
166
+ const model: MonitorProbe = new MonitorProbe();
167
+
168
+ it("allows deletion, which is the only way to undo attaching the wrong probe", () => {
169
+ /*
170
+ * Monitor > Probes sets isDeleteable, and BaseModelTable gates that button
171
+ * on these permissions. Without them the button never renders and the dead
172
+ * end this issue reported comes straight back.
173
+ */
174
+ expect(model.deleteRecordPermissions).toContain(Permission.ProjectOwner);
175
+ expect(model.deleteRecordPermissions).toContain(Permission.ProjectAdmin);
176
+ expect(model.deleteRecordPermissions).toContain(
177
+ Permission.DeleteMonitorProbe,
178
+ );
179
+ });
180
+
181
+ it("allows creation, so the removed probe can be replaced with the right one", () => {
182
+ expect(model.createRecordPermissions).toContain(
183
+ Permission.CreateMonitorProbe,
184
+ );
185
+ });
186
+
187
+ it("keeps the Edit button reachable for the roles that can update a row", () => {
188
+ // BaseModelTable renders Edit on the table-level update permission.
189
+ expect(
190
+ PermissionHelper.doesPermissionsIntersect(
191
+ OWNER_PERMISSIONS,
192
+ model.updateRecordPermissions,
193
+ ),
194
+ ).toBe(true);
195
+ });
196
+ });
@@ -0,0 +1,269 @@
1
+ /**
2
+ * RecommendationDismissal model wiring.
3
+ *
4
+ * Everything that makes this model work is decorator metadata, and decorator
5
+ * metadata has no runtime that would ever exercise it in a unit test. It is
6
+ * read by the generic CRUD API, by BaseModelTable and by the team-permissions
7
+ * screen — all of which fail *quietly* when a decorator is wrong:
8
+ *
9
+ * - a wrong @TenantColumn does not throw, it drops the automatic projectId
10
+ * filter, so one project's dismissals start hiding another project's cards
11
+ * - a changed @CrudApiEndpoint does not throw, it 404s the dismiss button in
12
+ * the browser while every server test still passes
13
+ * - a @TableColumn added to resourceId with a modelType would turn a
14
+ * deliberately polymorphic pointer into a relation, and TypeORM would then
15
+ * try to join against a table that does not exist for seven of the eight
16
+ * resource kinds
17
+ * - a Permission enum member with no entry in the permission-description
18
+ * table throws BadDataException from PermissionHelper.getDescription, which
19
+ * is only ever called when someone opens Project Settings > Teams
20
+ *
21
+ * None of these are caught by tsc, and none are caught by any test that
22
+ * exercises the service. So they are pinned here.
23
+ */
24
+
25
+ import Project from "../../Models/DatabaseModels/Project";
26
+ import RecommendationDismissal from "../../Models/DatabaseModels/RecommendationDismissal";
27
+ import Columns from "../../Types/Database/Columns";
28
+ import { TableColumnMetadata } from "../../Types/Database/TableColumn";
29
+ import TableColumnType from "../../Types/Database/TableColumnType";
30
+ import Permission, {
31
+ PermissionHelper,
32
+ PermissionProps,
33
+ } from "../../Types/Permission";
34
+ import RecommendationType from "../../Types/Recommendation/RecommendationType";
35
+ import { describe, expect, test } from "@jest/globals";
36
+ import { getMetadataArgsStorage } from "typeorm";
37
+ import { RelationMetadataArgs } from "typeorm/metadata-args/RelationMetadataArgs";
38
+
39
+ const RECOMMENDATION_DISMISSAL_PERMISSIONS: Array<Permission> = [
40
+ Permission.CreateRecommendationDismissal,
41
+ Permission.ReadRecommendationDismissal,
42
+ Permission.EditRecommendationDismissal,
43
+ Permission.DeleteRecommendationDismissal,
44
+ ];
45
+
46
+ function model(): RecommendationDismissal {
47
+ return new RecommendationDismissal();
48
+ }
49
+
50
+ function relationArgs(propertyName: string): RelationMetadataArgs | undefined {
51
+ return getMetadataArgsStorage().relations.find(
52
+ (relation: RelationMetadataArgs) => {
53
+ return (
54
+ relation.target === RecommendationDismissal &&
55
+ relation.propertyName === propertyName
56
+ );
57
+ },
58
+ );
59
+ }
60
+
61
+ function permissionProps(permission: Permission): PermissionProps | undefined {
62
+ return PermissionHelper.getAllPermissionProps().find(
63
+ (props: PermissionProps) => {
64
+ return props.permission === permission;
65
+ },
66
+ );
67
+ }
68
+
69
+ describe("RecommendationDismissal table wiring", () => {
70
+ test("is tenant-scoped by projectId", () => {
71
+ /*
72
+ * DatabaseService reads this string to inject the tenant filter on every
73
+ * read, update and delete. If it is null or names a column that does not
74
+ * exist, queries stop being scoped and a dismissal made in one project
75
+ * starts hiding recommendations in another.
76
+ */
77
+ expect(model().getTenantColumn()).toBe("projectId");
78
+ });
79
+
80
+ test("is served at /recommendation-dismissal", () => {
81
+ /*
82
+ * The dashboard calls this path through ModelAPI. It is a string on both
83
+ * sides with no shared constant, so a rename here breaks Dismiss in the
84
+ * browser and nothing else.
85
+ */
86
+ expect(model().getCrudApiPath()?.toString()).toBe(
87
+ "/recommendation-dismissal",
88
+ );
89
+ });
90
+
91
+ test("maps to the RecommendationDismissal table", () => {
92
+ expect(model().tableName).toBe("RecommendationDismissal");
93
+ });
94
+
95
+ test("declares every column the dismissal flow writes and reads", () => {
96
+ const columns: Columns = model().getTableColumns();
97
+
98
+ for (const columnName of [
99
+ "recommendationType",
100
+ "recommendationId",
101
+ "resourceType",
102
+ "resourceId",
103
+ "dismissalReason",
104
+ "projectId",
105
+ ]) {
106
+ expect(columns.hasColumn(columnName)).toBe(true);
107
+ }
108
+ });
109
+
110
+ test("requires the two columns that identify what was dismissed", () => {
111
+ /*
112
+ * required: true is what makes DatabaseService reject a create that omits
113
+ * them. It is the layer underneath RecommendationDismissalService's own
114
+ * validation; both exist because a row missing either field hides nothing
115
+ * yet still consumes the unique slot for its (project, type, resource).
116
+ */
117
+ const columns: RecommendationDismissal = model();
118
+
119
+ expect(columns.getTableColumnMetadata("recommendationType").required).toBe(
120
+ true,
121
+ );
122
+ expect(columns.getTableColumnMetadata("recommendationId").required).toBe(
123
+ true,
124
+ );
125
+ });
126
+
127
+ test("keeps resourceType and resourceId optional for unscoped recommendations", () => {
128
+ /*
129
+ * A future project-wide recommendation kind has no resource to point at.
130
+ * Marking either column required would make that kind undismissable.
131
+ */
132
+ const columns: RecommendationDismissal = model();
133
+
134
+ expect(columns.getTableColumnMetadata("resourceType").required).toBeFalsy();
135
+ expect(columns.getTableColumnMetadata("resourceId").required).toBeFalsy();
136
+ });
137
+
138
+ test("documents recommendationType with a value that is actually in the enum", () => {
139
+ /*
140
+ * The example feeds the generated API documentation, and it is the only
141
+ * place an integrator learns what this free-text column accepts. Since the
142
+ * service rejects anything outside the enum — case-sensitively — an
143
+ * example that has drifted from the enum documents a request that always
144
+ * 400s.
145
+ */
146
+ const example: unknown =
147
+ model().getTableColumnMetadata("recommendationType").example;
148
+
149
+ expect(Object.values(RecommendationType)).toContain(example);
150
+ });
151
+ });
152
+
153
+ describe("RecommendationDismissal.resourceId is a polymorphic pointer", () => {
154
+ test("carries no relation metadata, unlike the project relation", () => {
155
+ /*
156
+ * resourceId points at a KubernetesCluster, a Host, a DockerHost or five
157
+ * other tables depending on resourceType, so there is no single table a
158
+ * foreign key could name. The contrast with `project` below is the point:
159
+ * a relation column declares modelType and manyToOneRelationColumn, and
160
+ * anything reading this metadata (the API docs generator, the populate
161
+ * logic, BaseModelTable) branches on their presence. Give resourceId a
162
+ * modelType and those readers will start trying to join it.
163
+ */
164
+ const resourceId: TableColumnMetadata =
165
+ model().getTableColumnMetadata("resourceId");
166
+
167
+ expect(resourceId.type).toBe(TableColumnType.ObjectID);
168
+ expect(resourceId.modelType).toBeUndefined();
169
+ expect(resourceId.manyToOneRelationColumn).toBeUndefined();
170
+
171
+ const project: TableColumnMetadata =
172
+ model().getTableColumnMetadata("project");
173
+
174
+ expect(project.type).toBe(TableColumnType.Entity);
175
+ expect(project.modelType).toBe(Project);
176
+ expect(project.manyToOneRelationColumn).toBe("projectId");
177
+ });
178
+
179
+ test("has no sibling entity property and no TypeORM relation", () => {
180
+ /*
181
+ * The relation-column convention in this schema is a pair: an ObjectID
182
+ * column plus an entity property carrying the @ManyToOne (projectId +
183
+ * project). resourceId deliberately has no partner, and there must be no
184
+ * @ManyToOne registered against it either — TypeORM would resolve the FK
185
+ * at connection time and fail to start, or worse, join every dismissal to
186
+ * one arbitrary table.
187
+ */
188
+ expect(model().getTableColumns().hasColumn("resource")).toBe(false);
189
+ expect(relationArgs("resourceId")).toBeUndefined();
190
+ expect(relationArgs("resource")).toBeUndefined();
191
+
192
+ // The project relation is registered, so the assertions above mean something.
193
+ expect(relationArgs("project")).toBeDefined();
194
+ });
195
+ });
196
+
197
+ describe("RecommendationDismissal permissions", () => {
198
+ test("every granular permission has a description entry", () => {
199
+ /*
200
+ * PermissionHelper.getDescription throws BadDataException for a permission
201
+ * that is missing from getAllPermissionProps. Project Settings > Teams
202
+ * calls it for every permission it renders, so one missing entry does not
203
+ * hide a checkbox — it breaks the whole permissions screen, and only for
204
+ * whoever opens it after the model ships.
205
+ */
206
+ for (const permission of RECOMMENDATION_DISMISSAL_PERMISSIONS) {
207
+ const props: PermissionProps | undefined = permissionProps(permission);
208
+
209
+ expect(props).toBeDefined();
210
+ expect(props?.title.length).toBeGreaterThan(0);
211
+ expect(props?.description.length).toBeGreaterThan(0);
212
+
213
+ expect(() => {
214
+ return PermissionHelper.getDescription(permission);
215
+ }).not.toThrow();
216
+ expect(() => {
217
+ return PermissionHelper.getTitle(permission);
218
+ }).not.toThrow();
219
+ }
220
+ });
221
+
222
+ test("granular permissions are assignable to a team", () => {
223
+ /*
224
+ * A permission that is not assignable to a tenant never appears in the
225
+ * team permission picker, which makes it unreachable for anyone who is not
226
+ * already a project owner or admin.
227
+ */
228
+ for (const permission of RECOMMENDATION_DISMISSAL_PERMISSIONS) {
229
+ expect(permissionProps(permission)?.isAssignableToTenant).toBe(true);
230
+ }
231
+ });
232
+
233
+ test("each granular permission is wired into the matching table access control", () => {
234
+ /*
235
+ * Declaring the permissions without attaching them to the table would
236
+ * leave four checkboxes in the UI that grant nothing at all.
237
+ */
238
+ const dismissal: RecommendationDismissal = model();
239
+
240
+ expect(dismissal.createRecordPermissions).toContain(
241
+ Permission.CreateRecommendationDismissal,
242
+ );
243
+ expect(dismissal.readRecordPermissions).toContain(
244
+ Permission.ReadRecommendationDismissal,
245
+ );
246
+ expect(dismissal.updateRecordPermissions).toContain(
247
+ Permission.EditRecommendationDismissal,
248
+ );
249
+ expect(dismissal.deleteRecordPermissions).toContain(
250
+ Permission.DeleteRecommendationDismissal,
251
+ );
252
+ });
253
+
254
+ test("allows delete, because un-dismissing is a delete and not a flag", () => {
255
+ /*
256
+ * There is no isDismissed column to flip back. Restoring a recommendation
257
+ * removes the row, so without delete permission for ordinary project
258
+ * members a dismissal made by mistake is permanent for everyone.
259
+ */
260
+ const dismissal: RecommendationDismissal = model();
261
+
262
+ expect(dismissal.deleteRecordPermissions).toContain(
263
+ Permission.ProjectMember,
264
+ );
265
+ expect(dismissal.createRecordPermissions).toContain(
266
+ Permission.ProjectMember,
267
+ );
268
+ });
269
+ });