@oneuptime/common 11.7.2 → 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 (414) hide show
  1. package/Models/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
  2. package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
  3. package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
  4. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  5. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  6. package/Models/DatabaseModels/Index.ts +5 -0
  7. package/Models/DatabaseModels/LogDropFilter.ts +89 -0
  8. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
  9. package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
  10. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  11. package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
  12. package/Models/DatabaseModels/TableView.ts +40 -0
  13. package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
  14. package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
  15. package/Server/API/AlertAPI.ts +32 -17
  16. package/Server/API/BaseAPI.ts +31 -2
  17. package/Server/API/BaseAnalyticsAPI.ts +36 -2
  18. package/Server/API/CommonAPI.ts +57 -0
  19. package/Server/API/IncidentAPI.ts +60 -33
  20. package/Server/API/IncidentEpisodeAPI.ts +31 -16
  21. package/Server/API/MonitorAPI.ts +30 -0
  22. package/Server/API/MonitorTemplateAPI.ts +8 -0
  23. package/Server/API/ProbeAPI.ts +196 -15
  24. package/Server/API/ProjectAPI.ts +79 -1
  25. package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
  26. package/Server/API/StatusPageAPI.ts +74 -37
  27. package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  34. package/Server/Services/AlertEpisodeService.ts +47 -0
  35. package/Server/Services/AlertService.ts +125 -0
  36. package/Server/Services/BillingService.ts +98 -0
  37. package/Server/Services/DatabaseService.ts +184 -20
  38. package/Server/Services/IncidentEpisodeService.ts +47 -0
  39. package/Server/Services/IncidentService.ts +123 -0
  40. package/Server/Services/IncidentTemplateService.ts +123 -1
  41. package/Server/Services/Index.ts +2 -0
  42. package/Server/Services/LogDropFilterService.ts +133 -0
  43. package/Server/Services/MonitorProbeService.ts +94 -4
  44. package/Server/Services/MonitorService.ts +48 -43
  45. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
  46. package/Server/Services/ProbeService.ts +66 -0
  47. package/Server/Services/ProjectService.ts +131 -7
  48. package/Server/Services/RecommendationDismissalService.ts +52 -0
  49. package/Server/Services/RunbookAgentJobService.ts +68 -7
  50. package/Server/Services/ScheduledMaintenanceService.ts +103 -0
  51. package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
  52. package/Server/Services/StatusPageGroupService.ts +186 -0
  53. package/Server/Services/TraceDropFilterService.ts +133 -0
  54. package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
  55. package/Server/Types/Workflow/Components/API/Get.ts +12 -0
  56. package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
  57. package/Server/Types/Workflow/Components/API/Post.ts +12 -0
  58. package/Server/Types/Workflow/Components/API/Put.ts +13 -0
  59. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
  60. package/Server/Utils/DropFilterValidation.ts +127 -0
  61. package/Server/Utils/Errors.ts +5 -0
  62. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  63. package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
  64. package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
  65. package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
  66. package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
  67. package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
  68. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
  69. package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
  70. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
  71. package/Tests/Server/API/ProbeAPI.test.ts +422 -53
  72. package/Tests/Server/API/ProjectAPI.test.ts +180 -0
  73. package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
  74. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
  75. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
  76. package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
  77. package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
  78. package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
  79. package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
  80. package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
  81. package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
  82. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
  83. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
  84. package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
  85. package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
  86. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
  87. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
  88. package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
  89. package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
  90. package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
  91. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
  92. package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
  93. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
  94. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
  95. package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
  96. package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
  97. package/Tests/Types/DateUserTimezone.test.ts +52 -0
  98. package/Tests/Types/JSONFunctions.test.ts +103 -1
  99. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
  100. package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
  101. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
  102. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
  103. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
  104. package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
  105. package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
  106. package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
  107. package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
  108. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
  109. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
  110. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
  111. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
  112. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
  113. package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
  114. package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
  115. package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
  116. package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
  117. package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
  118. package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
  119. package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
  120. package/Tests/UI/Components/InfoCard.test.tsx +358 -0
  121. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
  122. package/Tests/UI/Components/Modal.test.tsx +43 -5
  123. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
  124. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
  125. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +999 -0
  126. package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
  127. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
  128. package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
  129. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
  130. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
  131. package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
  132. package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
  133. package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
  134. package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
  135. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
  136. package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
  137. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
  138. package/Tests/Utils/UserPreferences.test.ts +563 -0
  139. package/Types/Date.ts +15 -6
  140. package/Types/JSONFunctions.ts +7 -2
  141. package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
  142. package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
  143. package/Types/Monitor/MonitorStep.ts +10 -1
  144. package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
  145. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
  146. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
  147. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
  148. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
  149. package/Types/Permission.ts +45 -0
  150. package/Types/Recommendation/RecommendationType.ts +20 -0
  151. package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
  152. package/Types/Runbook/RunbookStepTimeout.ts +202 -0
  153. package/Types/Telemetry/DropFilterSampling.ts +92 -0
  154. package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
  155. package/UI/Components/Accordion/Accordion.tsx +10 -2
  156. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
  157. package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
  158. package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
  159. package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
  160. package/UI/Components/Error/PageError.tsx +0 -4
  161. package/UI/Components/ErrorBoundary.tsx +252 -15
  162. package/UI/Components/InfoCard/InfoCard.tsx +58 -1
  163. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
  164. package/UI/Components/MasterPage/MasterPage.tsx +65 -0
  165. package/UI/Components/Modal/Modal.tsx +16 -60
  166. package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
  167. package/UI/Components/ModelTable/BaseModelTable.tsx +503 -71
  168. package/UI/Components/ModelTable/Column.ts +17 -0
  169. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +523 -0
  170. package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
  171. package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
  172. package/UI/Components/ModelTable/TableView.tsx +24 -2
  173. package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
  174. package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
  175. package/UI/Components/SideMenu/SideMenu.tsx +24 -4
  176. package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
  177. package/UI/Components/Table/Table.tsx +14 -1
  178. package/UI/Components/Table/TableRow.tsx +180 -175
  179. package/UI/Components/Table/Types/Column.ts +2 -0
  180. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
  181. package/UI/Utils/DropdownAlignment.ts +162 -0
  182. package/UI/Utils/TableFilterUrlState.ts +59 -20
  183. package/Utils/API.ts +106 -2
  184. package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
  185. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
  186. package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
  187. package/Utils/StatusPage/GroupTree.ts +307 -0
  188. package/Utils/StatusPage/ResourceUptime.ts +90 -17
  189. package/Utils/UserPreferences.ts +53 -0
  190. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
  191. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
  193. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
  195. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  197. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  199. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  201. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
  203. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
  205. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
  207. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
  208. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  209. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
  211. package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/TableView.js +40 -0
  213. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  216. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
  217. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  218. package/build/dist/Server/API/AlertAPI.js +22 -13
  219. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  220. package/build/dist/Server/API/BaseAPI.js +22 -1
  221. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  222. package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
  223. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  224. package/build/dist/Server/API/CommonAPI.js +42 -0
  225. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  226. package/build/dist/Server/API/IncidentAPI.js +43 -26
  227. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  228. package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
  229. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  230. package/build/dist/Server/API/MonitorAPI.js +23 -0
  231. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  232. package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
  233. package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
  234. package/build/dist/Server/API/ProbeAPI.js +131 -15
  235. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  236. package/build/dist/Server/API/ProjectAPI.js +62 -3
  237. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  238. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
  239. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  240. package/build/dist/Server/API/StatusPageAPI.js +61 -33
  241. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  242. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
  243. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
  249. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  256. package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
  257. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  258. package/build/dist/Server/Services/AlertService.js +95 -0
  259. package/build/dist/Server/Services/AlertService.js.map +1 -1
  260. package/build/dist/Server/Services/BillingService.js +71 -0
  261. package/build/dist/Server/Services/BillingService.js.map +1 -1
  262. package/build/dist/Server/Services/DatabaseService.js +140 -17
  263. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  264. package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
  265. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  266. package/build/dist/Server/Services/IncidentService.js +100 -0
  267. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  268. package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
  269. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  270. package/build/dist/Server/Services/Index.js +2 -0
  271. package/build/dist/Server/Services/Index.js.map +1 -1
  272. package/build/dist/Server/Services/LogDropFilterService.js +93 -0
  273. package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
  274. package/build/dist/Server/Services/MonitorProbeService.js +73 -5
  275. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  276. package/build/dist/Server/Services/MonitorService.js +34 -39
  277. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  278. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
  279. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  280. package/build/dist/Server/Services/ProbeService.js +64 -0
  281. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  282. package/build/dist/Server/Services/ProjectService.js +103 -7
  283. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  284. package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
  285. package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
  286. package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
  287. package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
  288. package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
  289. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  290. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
  291. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  292. package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
  293. package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
  294. package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
  295. package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
  296. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
  297. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  298. package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
  299. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  300. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
  301. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  302. package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
  303. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  304. package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
  305. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  306. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
  307. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
  308. package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
  309. package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
  310. package/build/dist/Server/Utils/Errors.js +3 -0
  311. package/build/dist/Server/Utils/Errors.js.map +1 -1
  312. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
  313. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  314. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
  315. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  316. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
  317. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  318. package/build/dist/Types/Date.js +14 -4
  319. package/build/dist/Types/Date.js.map +1 -1
  320. package/build/dist/Types/JSONFunctions.js +8 -3
  321. package/build/dist/Types/JSONFunctions.js.map +1 -1
  322. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
  323. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
  324. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  325. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  326. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  327. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
  328. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  329. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
  330. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
  331. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
  332. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
  333. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
  334. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
  335. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
  336. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
  337. package/build/dist/Types/Permission.js +40 -0
  338. package/build/dist/Types/Permission.js.map +1 -1
  339. package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
  340. package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
  341. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
  342. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
  343. package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
  344. package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
  345. package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
  346. package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
  347. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
  348. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  349. package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
  350. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  351. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
  352. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  353. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
  354. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  355. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
  356. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
  357. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
  358. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  359. package/build/dist/UI/Components/Error/PageError.js +1 -3
  360. package/build/dist/UI/Components/Error/PageError.js.map +1 -1
  361. package/build/dist/UI/Components/ErrorBoundary.js +145 -11
  362. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  363. package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
  364. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  365. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
  366. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  367. package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
  368. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  369. package/build/dist/UI/Components/Modal/Modal.js +4 -32
  370. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  371. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  372. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +308 -40
  373. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  374. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +230 -0
  375. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
  376. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
  377. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
  378. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
  379. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
  380. package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
  381. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  382. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
  383. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
  384. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
  385. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
  386. package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
  387. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  388. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
  389. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
  390. package/build/dist/UI/Components/Table/Table.js +15 -2
  391. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  392. package/build/dist/UI/Components/Table/TableRow.js +11 -6
  393. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  394. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
  395. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  396. package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
  397. package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
  398. package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
  399. package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
  400. package/build/dist/Utils/API.js +39 -2
  401. package/build/dist/Utils/API.js.map +1 -1
  402. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
  403. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
  404. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
  405. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
  406. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
  407. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
  408. package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
  409. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
  410. package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
  411. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  412. package/build/dist/Utils/UserPreferences.js +33 -0
  413. package/build/dist/Utils/UserPreferences.js.map +1 -1
  414. package/package.json +1 -1
@@ -0,0 +1,901 @@
1
+ import MonitorRecommendationCatalog, {
2
+ MonitorRecommendationResourceTypeDefinition,
3
+ } from "../../../../Types/Monitor/Recommendation/MonitorRecommendationCatalog";
4
+ import MonitorRecommendationUtil, {
5
+ MonitorRecommendationFingerprint,
6
+ } from "../../../../Types/Monitor/Recommendation/MonitorRecommendationUtil";
7
+ import {
8
+ MonitorRecommendation,
9
+ MonitorRecommendationArgs,
10
+ MonitorRecommendationNotificationMode,
11
+ MonitorRecommendationNotificationSettings,
12
+ MonitorRecommendationResourceType,
13
+ } from "../../../../Types/Monitor/Recommendation/MonitorRecommendationTypes";
14
+ import MonitorStep from "../../../../Types/Monitor/MonitorStep";
15
+ import MonitorCriteriaInstance from "../../../../Types/Monitor/MonitorCriteriaInstance";
16
+ import MonitorType from "../../../../Types/Monitor/MonitorType";
17
+ import ObjectID from "../../../../Types/ObjectID";
18
+
19
+ /*
20
+ * `getCoveredRecommendationMonitorIds` is the richer sibling of
21
+ * `getCoveredRecommendationIds`: same diff, but it remembers WHICH monitor
22
+ * already watches each recommendation so the card can link to it.
23
+ *
24
+ * Two classes of failure motivate this file:
25
+ *
26
+ * 1. Drift between the two methods. They compute the same diff twice, and
27
+ * the two answers are rendered in two different places — the side-menu
28
+ * badge counts what is NOT covered from one of them, the page greys out
29
+ * cards from the other. If they ever disagree, the badge says "3 to
30
+ * create" while the page shows a different number of live cards, and
31
+ * nothing crashes to tell anyone. The agreement tests below run both over
32
+ * the same inputs for all eight resource types.
33
+ *
34
+ * 2. A wrong link target. The Map's VALUE is a monitor id that becomes a
35
+ * navigable link. Pointing it at the wrong monitor is worse than not
36
+ * linking at all: the user opens a monitor, sees thresholds that do not
37
+ * match the card they clicked, and concludes the feature lies. The
38
+ * duplicate-monitor and multi-step cases below pin exactly which id wins.
39
+ *
40
+ * The fixture style (buildArgs factory, generated ObjectID constants) mirrors
41
+ * MonitorRecommendationUtil.test.ts so both files describe the same world.
42
+ */
43
+
44
+ const ONLINE_STATUS_ID: ObjectID = ObjectID.generate();
45
+ const OFFLINE_STATUS_ID: ObjectID = ObjectID.generate();
46
+ const INCIDENT_SEVERITY_ID: ObjectID = ObjectID.generate();
47
+ const ALERT_SEVERITY_ID: ObjectID = ObjectID.generate();
48
+
49
+ const RESOURCE_IDENTIFIER: string = "prod-cluster-01";
50
+
51
+ /*
52
+ * Structurally identical to the inline element type
53
+ * `getCoveredRecommendationMonitorIds` accepts. Named here only so the
54
+ * helpers below can be typed.
55
+ */
56
+ interface ExistingRecommendationMonitor {
57
+ monitorId: ObjectID;
58
+ monitorSteps: Array<MonitorStep>;
59
+ }
60
+
61
+ function buildArgs(
62
+ overrides?: Partial<MonitorRecommendationArgs>,
63
+ ): MonitorRecommendationArgs {
64
+ return {
65
+ resourceIdentifier: RESOURCE_IDENTIFIER,
66
+ onlineMonitorStatusId: ONLINE_STATUS_ID,
67
+ offlineMonitorStatusId: OFFLINE_STATUS_ID,
68
+ defaultIncidentSeverityId: INCIDENT_SEVERITY_ID,
69
+ defaultAlertSeverityId: ALERT_SEVERITY_ID,
70
+ monitorName: "Test Monitor",
71
+ ...overrides,
72
+ };
73
+ }
74
+
75
+ /*
76
+ * Build the step a monitor created from this recommendation would actually
77
+ * carry — including the name prefixing the create flow applies. Building it
78
+ * with the raw args instead would still pass today (the name is not part of
79
+ * the fingerprint) but would stop these tests from noticing if the name ever
80
+ * leaked into the fingerprint.
81
+ */
82
+ function buildCreatedStep(
83
+ recommendation: MonitorRecommendation,
84
+ args: MonitorRecommendationArgs,
85
+ ): MonitorStep {
86
+ return recommendation.getMonitorStep({
87
+ ...args,
88
+ monitorName: MonitorRecommendationUtil.getMonitorName({
89
+ recommendation: recommendation,
90
+ resourceDisplayName: args.monitorName,
91
+ }),
92
+ });
93
+ }
94
+
95
+ /*
96
+ * What the diff actually compares for a recommendation, as a string. Used only
97
+ * to pick recommendations the diff can tell apart.
98
+ */
99
+ function getSerializedFingerprint(
100
+ recommendation: MonitorRecommendation,
101
+ args: MonitorRecommendationArgs,
102
+ ): string {
103
+ const fingerprint: MonitorRecommendationFingerprint | undefined =
104
+ MonitorRecommendationUtil.getFingerprintFromMonitorStep(
105
+ buildCreatedStep(recommendation, args),
106
+ );
107
+
108
+ if (!fingerprint) {
109
+ throw new Error(
110
+ `${recommendation.recommendationId} built a step with no infrastructure config.`,
111
+ );
112
+ }
113
+
114
+ return MonitorRecommendationUtil.serializeFingerprint(fingerprint);
115
+ }
116
+
117
+ function buildMonitorFor(
118
+ recommendations: Array<MonitorRecommendation>,
119
+ args: MonitorRecommendationArgs,
120
+ ): ExistingRecommendationMonitor {
121
+ return {
122
+ monitorId: ObjectID.generate(),
123
+ monitorSteps: recommendations.map(
124
+ (recommendation: MonitorRecommendation) => {
125
+ return buildCreatedStep(recommendation, args);
126
+ },
127
+ ),
128
+ };
129
+ }
130
+
131
+ /*
132
+ * A step that has `data` but no infrastructure sub-config at all — what an
133
+ * HTTP/Ping monitor on the same project looks like. Distinct from
134
+ * `new MonitorStep()` (no `data` whatsoever), and the more realistic of the
135
+ * two: a bare step could be short-circuited by a null check that a populated
136
+ * one would sail past and then read `undefined.clusterIdentifier` from.
137
+ */
138
+ function buildNonInfrastructureStep(): MonitorStep {
139
+ return MonitorStep.getDefaultMonitorStep({
140
+ monitorName: "Ping Monitor",
141
+ monitorType: MonitorType.Ping,
142
+ onlineMonitorStatusId: ONLINE_STATUS_ID,
143
+ offlineMonitorStatusId: OFFLINE_STATUS_ID,
144
+ defaultIncidentSeverityId: INCIDENT_SEVERITY_ID,
145
+ defaultAlertSeverityId: ALERT_SEVERITY_ID,
146
+ });
147
+ }
148
+
149
+ function getCriteriaInstances(
150
+ monitorStep: MonitorStep,
151
+ ): Array<MonitorCriteriaInstance> {
152
+ return (
153
+ monitorStep.data?.monitorCriteria?.data?.monitorCriteriaInstanceArray || []
154
+ );
155
+ }
156
+
157
+ function flattenSteps(
158
+ monitors: Array<ExistingRecommendationMonitor>,
159
+ ): Array<MonitorStep> {
160
+ return monitors.flatMap((monitor: ExistingRecommendationMonitor) => {
161
+ return monitor.monitorSteps;
162
+ });
163
+ }
164
+
165
+ const RESOURCE_TYPE_DEFINITIONS: Array<MonitorRecommendationResourceTypeDefinition> =
166
+ MonitorRecommendationCatalog.getResourceTypeDefinitions();
167
+
168
+ const KUBERNETES_RECOMMENDATIONS: Array<MonitorRecommendation> =
169
+ MonitorRecommendationCatalog.getRecommendations(
170
+ MonitorRecommendationResourceType.Kubernetes,
171
+ );
172
+
173
+ describe("MonitorRecommendationUtil.getCoveredRecommendationMonitorIds", () => {
174
+ it("maps a covered recommendation to the id of the monitor that watches it", () => {
175
+ const args: MonitorRecommendationArgs = buildArgs();
176
+ const created: MonitorRecommendation = KUBERNETES_RECOMMENDATIONS[0]!;
177
+ const existingMonitor: ExistingRecommendationMonitor = buildMonitorFor(
178
+ [created],
179
+ args,
180
+ );
181
+
182
+ const covered: Map<string, ObjectID> =
183
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
184
+ recommendations: KUBERNETES_RECOMMENDATIONS,
185
+ existingMonitors: [existingMonitor],
186
+ args: args,
187
+ });
188
+
189
+ expect(covered.size).toBe(1);
190
+ /*
191
+ * `toBe`, not `toEqual`: the caller hands the id straight to a link, so
192
+ * the method must pass the caller's own ObjectID through rather than
193
+ * reconstructing one from a string somewhere in the middle.
194
+ */
195
+ expect(covered.get(created.recommendationId)).toBe(
196
+ existingMonitor.monitorId,
197
+ );
198
+ });
199
+
200
+ it("leaves recommendations that no monitor watches out of the map", () => {
201
+ /*
202
+ * Absence, not a `undefined` value under the key: the page renders a
203
+ * "create" card for anything missing from the map, and a present key with
204
+ * no id would render an "already created" card whose link goes nowhere.
205
+ */
206
+ const args: MonitorRecommendationArgs = buildArgs();
207
+ const created: MonitorRecommendation = KUBERNETES_RECOMMENDATIONS[0]!;
208
+ const notCreated: MonitorRecommendation = KUBERNETES_RECOMMENDATIONS[1]!;
209
+
210
+ const covered: Map<string, ObjectID> =
211
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
212
+ recommendations: KUBERNETES_RECOMMENDATIONS,
213
+ existingMonitors: [buildMonitorFor([created], args)],
214
+ args: args,
215
+ });
216
+
217
+ expect(covered.has(notCreated.recommendationId)).toBe(false);
218
+ expect(KUBERNETES_RECOMMENDATIONS.length).toBeGreaterThan(1);
219
+ });
220
+
221
+ it("is empty when the resource has no monitors at all", () => {
222
+ expect(
223
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
224
+ recommendations: KUBERNETES_RECOMMENDATIONS,
225
+ existingMonitors: [],
226
+ args: buildArgs(),
227
+ }).size,
228
+ ).toBe(0);
229
+ });
230
+
231
+ it("is empty when every existing monitor carries no infrastructure config", () => {
232
+ /*
233
+ * A project's non-infrastructure monitors (HTTP, Ping, ...) are handed to
234
+ * this method too. A fingerprint of `("", [], [])` for all of them would
235
+ * collide with any recommendation that also produced an empty
236
+ * fingerprint, marking real work as already done.
237
+ */
238
+ const covered: Map<string, ObjectID> =
239
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
240
+ recommendations: KUBERNETES_RECOMMENDATIONS,
241
+ existingMonitors: [
242
+ { monitorId: ObjectID.generate(), monitorSteps: [new MonitorStep()] },
243
+ {
244
+ monitorId: ObjectID.generate(),
245
+ monitorSteps: [buildNonInfrastructureStep()],
246
+ },
247
+ { monitorId: ObjectID.generate(), monitorSteps: [] },
248
+ ],
249
+ args: buildArgs(),
250
+ });
251
+
252
+ expect(covered.size).toBe(0);
253
+ });
254
+
255
+ it("keeps the first monitor in the array when two monitors watch the same thing", () => {
256
+ /*
257
+ * Reachable by hand: create a recommendation, then create it again from a
258
+ * second browser tab that still shows the stale "not created" card. Both
259
+ * monitors exist and fingerprint identically. The API returns them sorted,
260
+ * so honouring the array order makes the link target stable across page
261
+ * loads instead of flipping with whatever row order Postgres felt like.
262
+ */
263
+ const args: MonitorRecommendationArgs = buildArgs();
264
+ const created: MonitorRecommendation = KUBERNETES_RECOMMENDATIONS[0]!;
265
+
266
+ const firstMonitor: ExistingRecommendationMonitor = buildMonitorFor(
267
+ [created],
268
+ args,
269
+ );
270
+ const secondMonitor: ExistingRecommendationMonitor = buildMonitorFor(
271
+ [created],
272
+ args,
273
+ );
274
+
275
+ expect(
276
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
277
+ recommendations: KUBERNETES_RECOMMENDATIONS,
278
+ existingMonitors: [firstMonitor, secondMonitor],
279
+ args: args,
280
+ }).get(created.recommendationId),
281
+ ).toBe(firstMonitor.monitorId);
282
+
283
+ /*
284
+ * Reversing the input flips the answer. Without this the assertion above
285
+ * would also pass an implementation that picked the LAST monitor and
286
+ * happened to be handed them in a lucky order.
287
+ */
288
+ expect(
289
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
290
+ recommendations: KUBERNETES_RECOMMENDATIONS,
291
+ existingMonitors: [secondMonitor, firstMonitor],
292
+ args: args,
293
+ }).get(created.recommendationId),
294
+ ).toBe(secondMonitor.monitorId);
295
+ });
296
+
297
+ it("credits every step of a monitor that has several", () => {
298
+ /*
299
+ * A monitor is not limited to one step, and nothing stops a user from
300
+ * hand-adding a second infrastructure step to a monitor the feature
301
+ * created. Only looking at step 0 would resurface an already-watched
302
+ * recommendation as "not created" and invite a duplicate monitor.
303
+ */
304
+ const args: MonitorRecommendationArgs = buildArgs();
305
+ const createdRecommendations: Array<MonitorRecommendation> =
306
+ KUBERNETES_RECOMMENDATIONS.slice(0, 3);
307
+
308
+ const multiStepMonitor: ExistingRecommendationMonitor = buildMonitorFor(
309
+ createdRecommendations,
310
+ args,
311
+ );
312
+
313
+ const covered: Map<string, ObjectID> =
314
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
315
+ recommendations: KUBERNETES_RECOMMENDATIONS,
316
+ existingMonitors: [multiStepMonitor],
317
+ args: args,
318
+ });
319
+
320
+ expect(createdRecommendations.length).toBe(3);
321
+ for (const recommendation of createdRecommendations) {
322
+ expect(covered.get(recommendation.recommendationId)).toBe(
323
+ multiStepMonitor.monitorId,
324
+ );
325
+ }
326
+ });
327
+
328
+ it("still credits a real step sitting next to a non-infrastructure one", () => {
329
+ /*
330
+ * The undefined fingerprint of the unrelated step must be skipped, not
331
+ * treated as the monitor's answer and used to abandon the rest.
332
+ */
333
+ const args: MonitorRecommendationArgs = buildArgs();
334
+ const created: MonitorRecommendation = KUBERNETES_RECOMMENDATIONS[0]!;
335
+ const monitorId: ObjectID = ObjectID.generate();
336
+
337
+ const covered: Map<string, ObjectID> =
338
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
339
+ recommendations: KUBERNETES_RECOMMENDATIONS,
340
+ existingMonitors: [
341
+ {
342
+ monitorId: monitorId,
343
+ monitorSteps: [
344
+ buildNonInfrastructureStep(),
345
+ buildCreatedStep(created, args),
346
+ ],
347
+ },
348
+ ],
349
+ args: args,
350
+ });
351
+
352
+ expect(covered.get(created.recommendationId)).toBe(monitorId);
353
+ });
354
+
355
+ it("covers nothing when the existing monitors are scoped to another resource", () => {
356
+ /*
357
+ * Same recommendation, different cluster. Coverage is per resource: the
358
+ * cluster being viewed genuinely is not watched, and reporting otherwise
359
+ * would hide a real gap on every cluster after the first.
360
+ */
361
+ const otherResourceMonitor: ExistingRecommendationMonitor = buildMonitorFor(
362
+ KUBERNETES_RECOMMENDATIONS,
363
+ buildArgs({ resourceIdentifier: "some-other-cluster" }),
364
+ );
365
+
366
+ const covered: Map<string, ObjectID> =
367
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
368
+ recommendations: KUBERNETES_RECOMMENDATIONS,
369
+ existingMonitors: [otherResourceMonitor],
370
+ args: buildArgs(),
371
+ });
372
+
373
+ expect(covered.size).toBe(0);
374
+ });
375
+
376
+ it("is unaffected by the on-call policies, labels and severities applied at create time", () => {
377
+ /*
378
+ * Monitors created through this feature have had notification settings
379
+ * written into their criteria; the recommendation the diff rebuilds has
380
+ * not. If any of that reached the fingerprint, every monitor created with
381
+ * an on-call policy would immediately show up as "not created" again.
382
+ */
383
+ const args: MonitorRecommendationArgs = buildArgs();
384
+ const created: MonitorRecommendation = KUBERNETES_RECOMMENDATIONS[0]!;
385
+ const monitorId: ObjectID = ObjectID.generate();
386
+
387
+ const existingStep: MonitorStep =
388
+ MonitorRecommendationUtil.applyNotificationSettingsToMonitorStep({
389
+ monitorStep: buildCreatedStep(created, args),
390
+ notificationSettings: {
391
+ onCallPolicyIds: [ObjectID.generate(), ObjectID.generate()],
392
+ labelIds: [ObjectID.generate()],
393
+ ownerTeamIds: [ObjectID.generate()],
394
+ ownerUserIds: [ObjectID.generate()],
395
+ incidentSeverityIdBySeverity: {
396
+ Critical: ObjectID.generate(),
397
+ Warning: ObjectID.generate(),
398
+ },
399
+ alertSeverityIdBySeverity: {
400
+ Critical: ObjectID.generate(),
401
+ Warning: ObjectID.generate(),
402
+ },
403
+ },
404
+ severity: created.severity,
405
+ });
406
+
407
+ const covered: Map<string, ObjectID> =
408
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
409
+ recommendations: KUBERNETES_RECOMMENDATIONS,
410
+ existingMonitors: [
411
+ { monitorId: monitorId, monitorSteps: [existingStep] },
412
+ ],
413
+ args: args,
414
+ });
415
+
416
+ expect(covered.get(created.recommendationId)).toBe(monitorId);
417
+ });
418
+
419
+ it("is unaffected by the notification mode the monitor was created with", () => {
420
+ /*
421
+ * The Alert / Incident / Both choice flips `createIncidents` and
422
+ * `createAlerts`, which decides what a breach OPENS — not what the
423
+ * monitor WATCHES. A monitor created as alert-only watches exactly the
424
+ * same metrics as one created as incident-only, so both must still cover
425
+ * their recommendation. Were the flags in the fingerprint, a user who
426
+ * picked "Alert" would be offered the same monitor again forever.
427
+ */
428
+ const args: MonitorRecommendationArgs = buildArgs();
429
+ const created: MonitorRecommendation = KUBERNETES_RECOMMENDATIONS[0]!;
430
+
431
+ const modes: Array<MonitorRecommendationNotificationMode> = [
432
+ MonitorRecommendationNotificationMode.Alert,
433
+ MonitorRecommendationNotificationMode.Incident,
434
+ MonitorRecommendationNotificationMode.Both,
435
+ ];
436
+
437
+ for (const mode of modes) {
438
+ const notificationSettings: MonitorRecommendationNotificationSettings = {
439
+ notificationMode: mode,
440
+ };
441
+
442
+ const existingStep: MonitorStep =
443
+ MonitorRecommendationUtil.applyNotificationSettingsToMonitorStep({
444
+ monitorStep: buildCreatedStep(created, args),
445
+ notificationSettings: notificationSettings,
446
+ severity: created.severity,
447
+ });
448
+
449
+ const monitorId: ObjectID = ObjectID.generate();
450
+
451
+ const covered: Map<string, ObjectID> =
452
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
453
+ recommendations: KUBERNETES_RECOMMENDATIONS,
454
+ existingMonitors: [
455
+ { monitorId: monitorId, monitorSteps: [existingStep] },
456
+ ],
457
+ args: args,
458
+ });
459
+
460
+ expect(covered.get(created.recommendationId)).toBe(monitorId);
461
+ }
462
+
463
+ /*
464
+ * Guard against a vacuous version of the loop above: if the mode stopped
465
+ * changing anything on the step, every iteration would trivially pass
466
+ * while proving nothing. Alert mode must actually turn incident creation
467
+ * off somewhere on the step.
468
+ */
469
+ const alertOnlyStep: MonitorStep =
470
+ MonitorRecommendationUtil.applyNotificationSettingsToMonitorStep({
471
+ monitorStep: buildCreatedStep(created, args),
472
+ notificationSettings: {
473
+ notificationMode: MonitorRecommendationNotificationMode.Alert,
474
+ },
475
+ severity: created.severity,
476
+ });
477
+
478
+ const bothStep: MonitorStep =
479
+ MonitorRecommendationUtil.applyNotificationSettingsToMonitorStep({
480
+ monitorStep: buildCreatedStep(created, args),
481
+ notificationSettings: {
482
+ notificationMode: MonitorRecommendationNotificationMode.Both,
483
+ },
484
+ severity: created.severity,
485
+ });
486
+
487
+ const alertOnlyIncidentFlags: Array<boolean | undefined> =
488
+ getCriteriaInstances(alertOnlyStep).map(
489
+ (instance: MonitorCriteriaInstance) => {
490
+ return instance.data?.createIncidents;
491
+ },
492
+ );
493
+
494
+ const bothIncidentFlags: Array<boolean | undefined> = getCriteriaInstances(
495
+ bothStep,
496
+ ).map((instance: MonitorCriteriaInstance) => {
497
+ return instance.data?.createIncidents;
498
+ });
499
+
500
+ expect(alertOnlyIncidentFlags).not.toEqual(bothIncidentFlags);
501
+ });
502
+
503
+ it("agrees exactly with getCoveredRecommendationIds for every resource type", () => {
504
+ /*
505
+ * The divergence this rules out: the side-menu badge counts the
506
+ * uncovered recommendations from one method and the page greys cards out
507
+ * using the other. Any difference in how the two walk the existing
508
+ * monitors — a `continue` in one and not the other, a different empty
509
+ * check — shows up as a badge that contradicts the page, with no error
510
+ * anywhere.
511
+ *
512
+ * Every other recommendation is "created", each on its own monitor, so
513
+ * both a covered and an uncovered answer are exercised for all eight
514
+ * resource types.
515
+ */
516
+ expect(RESOURCE_TYPE_DEFINITIONS.length).toBe(8);
517
+
518
+ for (const definition of RESOURCE_TYPE_DEFINITIONS) {
519
+ const recommendations: Array<MonitorRecommendation> =
520
+ definition.getRecommendations();
521
+ const args: MonitorRecommendationArgs = buildArgs();
522
+
523
+ const createdRecommendations: Array<MonitorRecommendation> =
524
+ recommendations.filter(
525
+ (_recommendation: MonitorRecommendation, index: number) => {
526
+ return index % 2 === 0;
527
+ },
528
+ );
529
+
530
+ const existingMonitors: Array<ExistingRecommendationMonitor> =
531
+ createdRecommendations.map((recommendation: MonitorRecommendation) => {
532
+ return buildMonitorFor([recommendation], args);
533
+ });
534
+
535
+ const coveredMap: Map<string, ObjectID> =
536
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
537
+ recommendations: recommendations,
538
+ existingMonitors: existingMonitors,
539
+ args: args,
540
+ });
541
+
542
+ const coveredSet: Set<string> =
543
+ MonitorRecommendationUtil.getCoveredRecommendationIds({
544
+ recommendations: recommendations,
545
+ existingMonitorSteps: flattenSteps(existingMonitors),
546
+ args: args,
547
+ });
548
+
549
+ expect(new Set<string>(coveredMap.keys())).toEqual(coveredSet);
550
+
551
+ // Non-vacuity: both answers must actually contain the created ones.
552
+ expect(createdRecommendations.length).toBeGreaterThan(0);
553
+ for (const recommendation of createdRecommendations) {
554
+ expect(coveredMap.has(recommendation.recommendationId)).toBe(true);
555
+ expect(coveredSet.has(recommendation.recommendationId)).toBe(true);
556
+ }
557
+
558
+ // ...and neither may claim everything, or agreement would be trivial.
559
+ expect(coveredMap.size).toBeLessThan(recommendations.length);
560
+ }
561
+ });
562
+
563
+ it("agrees with getCoveredRecommendationIds on the empty and unmatched cases", () => {
564
+ /*
565
+ * The two methods take their early-out on an empty fingerprint index
566
+ * separately. A mismatch here would mean a resource with only HTTP
567
+ * monitors renders one way in the badge and another on the page.
568
+ */
569
+ const args: MonitorRecommendationArgs = buildArgs();
570
+
571
+ const noiseMonitors: Array<ExistingRecommendationMonitor> = [
572
+ { monitorId: ObjectID.generate(), monitorSteps: [] },
573
+ {
574
+ monitorId: ObjectID.generate(),
575
+ monitorSteps: [buildNonInfrastructureStep(), new MonitorStep()],
576
+ },
577
+ buildMonitorFor(
578
+ [KUBERNETES_RECOMMENDATIONS[0]!],
579
+ buildArgs({ resourceIdentifier: "unrelated-cluster" }),
580
+ ),
581
+ ];
582
+
583
+ for (const existingMonitors of [
584
+ [] as Array<ExistingRecommendationMonitor>,
585
+ noiseMonitors,
586
+ ]) {
587
+ const coveredMap: Map<string, ObjectID> =
588
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
589
+ recommendations: KUBERNETES_RECOMMENDATIONS,
590
+ existingMonitors: existingMonitors,
591
+ args: args,
592
+ });
593
+
594
+ const coveredSet: Set<string> =
595
+ MonitorRecommendationUtil.getCoveredRecommendationIds({
596
+ recommendations: KUBERNETES_RECOMMENDATIONS,
597
+ existingMonitorSteps: flattenSteps(existingMonitors),
598
+ args: args,
599
+ });
600
+
601
+ expect(new Set<string>(coveredMap.keys())).toEqual(coveredSet);
602
+ expect(coveredMap.size).toBe(0);
603
+ }
604
+ });
605
+
606
+ it("resolves the right monitor id for every resource type in the catalog", () => {
607
+ /*
608
+ * Each resource type renames the identifier field on its way into the
609
+ * template module (clusterIdentifier / hostIdentifier / fleetIdentifier).
610
+ * A type whose rename is wrong produces a step scoped to "" on both sides
611
+ * of the diff, which still matches — so this asserts the id of the RIGHT
612
+ * monitor among two, which an all-empty fingerprint could not do: with
613
+ * every fingerprint collapsed to `("", [], [])` both recommendations would
614
+ * resolve to the first monitor and this fails.
615
+ *
616
+ * The pair is chosen by fingerprint rather than by taking index 0 and 1,
617
+ * because Proxmox and Ceph ship several templates that watch the same
618
+ * metric with different thresholds and therefore fingerprint identically
619
+ * — see `serializeFingerprint`, which reads metric names and formulas
620
+ * only. Those are genuinely indistinguishable to the diff (reported
621
+ * separately); this test is about the identifier plumbing, so it uses a
622
+ * pair the diff can tell apart.
623
+ */
624
+ for (const definition of RESOURCE_TYPE_DEFINITIONS) {
625
+ const recommendations: Array<MonitorRecommendation> =
626
+ definition.getRecommendations();
627
+ const args: MonitorRecommendationArgs = buildArgs();
628
+
629
+ const first: MonitorRecommendation = recommendations[0]!;
630
+ const firstFingerprint: string = getSerializedFingerprint(first, args);
631
+
632
+ const second: MonitorRecommendation | undefined = recommendations.find(
633
+ (recommendation: MonitorRecommendation) => {
634
+ return (
635
+ getSerializedFingerprint(recommendation, args) !== firstFingerprint
636
+ );
637
+ },
638
+ );
639
+
640
+ expect(second).toBeDefined();
641
+
642
+ const firstMonitor: ExistingRecommendationMonitor = buildMonitorFor(
643
+ [first],
644
+ args,
645
+ );
646
+ const secondMonitor: ExistingRecommendationMonitor = buildMonitorFor(
647
+ [second!],
648
+ args,
649
+ );
650
+
651
+ const covered: Map<string, ObjectID> =
652
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
653
+ recommendations: recommendations,
654
+ existingMonitors: [firstMonitor, secondMonitor],
655
+ args: args,
656
+ });
657
+
658
+ expect(covered.get(first.recommendationId)).toBe(firstMonitor.monitorId);
659
+ expect(covered.get(second!.recommendationId)).toBe(
660
+ secondMonitor.monitorId,
661
+ );
662
+ }
663
+ });
664
+
665
+ /*
666
+ * The side-menu badge (RecommendationsSideMenuItem) computes this same diff,
667
+ * and it does so WITHOUT fetching the project's monitor statuses or its
668
+ * incident and alert severities — it passes ObjectID.getZeroObjectID() for
669
+ * all four, because those ids are written into the criteria a template
670
+ * builds but never reach the fingerprint the diff compares. That saves four
671
+ * project-wide list requests on every page of every resource.
672
+ *
673
+ * It also means a change on the template side that DID let one of those ids
674
+ * into the metric config would make the badge disagree with the page it
675
+ * links to: the badge would find nothing covered and nag about
676
+ * recommendations the page shows as already created. Nothing about that
677
+ * failure is visible from either file, so the invariant is pinned here
678
+ * rather than described in a comment over there.
679
+ */
680
+ it("ignores the monitor-status and severity args entirely", () => {
681
+ for (const definition of RESOURCE_TYPE_DEFINITIONS) {
682
+ const recommendations: Array<MonitorRecommendation> =
683
+ definition.getRecommendations();
684
+
685
+ const realArgs: MonitorRecommendationArgs = buildArgs();
686
+
687
+ const placeholderArgs: MonitorRecommendationArgs = buildArgs({
688
+ onlineMonitorStatusId: ObjectID.getZeroObjectID(),
689
+ offlineMonitorStatusId: ObjectID.getZeroObjectID(),
690
+ defaultIncidentSeverityId: ObjectID.getZeroObjectID(),
691
+ defaultAlertSeverityId: ObjectID.getZeroObjectID(),
692
+ });
693
+
694
+ /*
695
+ * The monitor was created with the project's real ids; the diff is run
696
+ * with placeholders. This is exactly the asymmetry the badge relies on.
697
+ */
698
+ const existingMonitor: ExistingRecommendationMonitor = buildMonitorFor(
699
+ recommendations,
700
+ realArgs,
701
+ );
702
+
703
+ const coveredWithPlaceholders: Map<string, ObjectID> =
704
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
705
+ recommendations: recommendations,
706
+ existingMonitors: [existingMonitor],
707
+ args: placeholderArgs,
708
+ });
709
+
710
+ const coveredWithRealIds: Map<string, ObjectID> =
711
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
712
+ recommendations: recommendations,
713
+ existingMonitors: [existingMonitor],
714
+ args: realArgs,
715
+ });
716
+
717
+ expect(coveredWithPlaceholders.size).toBe(recommendations.length);
718
+ expect([...coveredWithPlaceholders.keys()].sort()).toEqual(
719
+ [...coveredWithRealIds.keys()].sort(),
720
+ );
721
+ }
722
+ });
723
+ /*
724
+ * Every shipped template must fingerprint differently from every other
725
+ * template FOR THE SAME RESOURCE TYPE. This is the property the whole
726
+ * already-created diff rests on, and it is not obvious: the fingerprint is
727
+ * derived, not declared, so two templates can collide without either author
728
+ * doing anything wrong.
729
+ *
730
+ * They did. Before the fingerprint was widened past (identifier, metric
731
+ * names, formulas), thirteen groups collided — eight Ceph templates share
732
+ * the single metric `ceph_health_detail` and differ only in the health-check
733
+ * name they filter on, and "Cluster Near Full" differs from "Cluster Full"
734
+ * only in its threshold. Creating "Ceph Daemon Crash" therefore marked
735
+ * clock-skew, OSD-nearfull, OSD-backfillfull and OSD-full as already
736
+ * created, hiding four real monitoring gaps behind cards that read as
737
+ * handled, and pointed each of their "View monitor" links at a monitor with
738
+ * unrelated thresholds.
739
+ *
740
+ * Nothing about that is visible from the page, from the templates, or from
741
+ * any other test. It is caught here or not at all.
742
+ */
743
+ it("gives every template a distinct fingerprint within its resource type", () => {
744
+ for (const definition of RESOURCE_TYPE_DEFINITIONS) {
745
+ const args: MonitorRecommendationArgs = buildArgs();
746
+
747
+ const idsByFingerprint: Map<string, Array<string>> = new Map<
748
+ string,
749
+ Array<string>
750
+ >();
751
+
752
+ for (const recommendation of definition.getRecommendations()) {
753
+ const fingerprint: string = getSerializedFingerprint(
754
+ recommendation,
755
+ args,
756
+ );
757
+
758
+ const ids: Array<string> = idsByFingerprint.get(fingerprint) || [];
759
+ ids.push(recommendation.recommendationId);
760
+ idsByFingerprint.set(fingerprint, ids);
761
+ }
762
+
763
+ const collisions: Array<Array<string>> = [
764
+ ...idsByFingerprint.values(),
765
+ ].filter((ids: Array<string>) => {
766
+ return ids.length > 1;
767
+ });
768
+
769
+ /*
770
+ * Asserted as the colliding ids rather than as a count, so a failure
771
+ * names the two templates that need separating instead of saying "3".
772
+ */
773
+ expect(collisions).toEqual([]);
774
+ }
775
+ });
776
+
777
+ /*
778
+ * The same property across resource types. The page never mixes them — it
779
+ * queries monitors by monitorType — but nothing in the fingerprint enforced
780
+ * that, and Docker, Docker Swarm and Podman ship byte-identical metric
781
+ * configs ("container.cpu.utilization" and friends). A future caller that
782
+ * diffs across types, or a resource type that starts accepting another's
783
+ * monitor type, would silently mark half its recommendations as handled.
784
+ */
785
+ it("gives every template in the whole catalog a distinct fingerprint", () => {
786
+ const args: MonitorRecommendationArgs = buildArgs();
787
+
788
+ const idsByFingerprint: Map<string, Array<string>> = new Map<
789
+ string,
790
+ Array<string>
791
+ >();
792
+
793
+ for (const recommendation of MonitorRecommendationCatalog.getAllRecommendations()) {
794
+ const fingerprint: string = getSerializedFingerprint(
795
+ recommendation,
796
+ args,
797
+ );
798
+
799
+ const ids: Array<string> = idsByFingerprint.get(fingerprint) || [];
800
+ ids.push(recommendation.recommendationId);
801
+ idsByFingerprint.set(fingerprint, ids);
802
+ }
803
+
804
+ const collisions: Array<Array<string>> = [
805
+ ...idsByFingerprint.values(),
806
+ ].filter((ids: Array<string>) => {
807
+ return ids.length > 1;
808
+ });
809
+
810
+ expect(collisions).toEqual([]);
811
+ });
812
+
813
+ /*
814
+ * The concrete regressions, named. The tests above would catch these too,
815
+ * but they would report them as an anonymous collision list; these say what
816
+ * broke and what the user loses when it does.
817
+ */
818
+ it("does not let one Ceph health-detail template cover the others", () => {
819
+ const cephRecommendations: Array<MonitorRecommendation> =
820
+ MonitorRecommendationCatalog.getRecommendations(
821
+ MonitorRecommendationResourceType.Ceph,
822
+ );
823
+
824
+ const daemonCrash: MonitorRecommendation | undefined =
825
+ cephRecommendations.find((recommendation: MonitorRecommendation) => {
826
+ return recommendation.templateId === "ceph-daemon-crash";
827
+ });
828
+
829
+ expect(daemonCrash).toBeDefined();
830
+
831
+ const args: MonitorRecommendationArgs = buildArgs();
832
+
833
+ const covered: Map<string, ObjectID> =
834
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
835
+ recommendations: cephRecommendations,
836
+ existingMonitors: [buildMonitorFor([daemonCrash!], args)],
837
+ args: args,
838
+ });
839
+
840
+ expect([...covered.keys()]).toEqual([daemonCrash!.recommendationId]);
841
+ });
842
+
843
+ it("does not let a near-full threshold cover the full threshold", () => {
844
+ const cephRecommendations: Array<MonitorRecommendation> =
845
+ MonitorRecommendationCatalog.getRecommendations(
846
+ MonitorRecommendationResourceType.Ceph,
847
+ );
848
+
849
+ const nearFull: MonitorRecommendation | undefined =
850
+ cephRecommendations.find((recommendation: MonitorRecommendation) => {
851
+ return recommendation.templateId === "ceph-cluster-near-full";
852
+ });
853
+ const full: MonitorRecommendation | undefined = cephRecommendations.find(
854
+ (recommendation: MonitorRecommendation) => {
855
+ return recommendation.templateId === "ceph-cluster-full";
856
+ },
857
+ );
858
+
859
+ expect(nearFull).toBeDefined();
860
+ expect(full).toBeDefined();
861
+
862
+ const args: MonitorRecommendationArgs = buildArgs();
863
+
864
+ const covered: Map<string, ObjectID> =
865
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
866
+ recommendations: cephRecommendations,
867
+ existingMonitors: [buildMonitorFor([nearFull!], args)],
868
+ args: args,
869
+ });
870
+
871
+ expect(covered.has(nearFull!.recommendationId)).toBe(true);
872
+ expect(covered.has(full!.recommendationId)).toBe(false);
873
+ });
874
+
875
+ it("does not let a Docker monitor cover the identical Podman recommendation", () => {
876
+ /*
877
+ * Docker and Podman ship the same metric names, the same aliases and the
878
+ * same thresholds for container CPU. Only the step's own config kind
879
+ * (dockerMonitor vs podmanMonitor) tells them apart.
880
+ */
881
+ const args: MonitorRecommendationArgs = buildArgs();
882
+
883
+ const dockerRecommendations: Array<MonitorRecommendation> =
884
+ MonitorRecommendationCatalog.getRecommendations(
885
+ MonitorRecommendationResourceType.Docker,
886
+ );
887
+ const podmanRecommendations: Array<MonitorRecommendation> =
888
+ MonitorRecommendationCatalog.getRecommendations(
889
+ MonitorRecommendationResourceType.Podman,
890
+ );
891
+
892
+ const covered: Map<string, ObjectID> =
893
+ MonitorRecommendationUtil.getCoveredRecommendationMonitorIds({
894
+ recommendations: podmanRecommendations,
895
+ existingMonitors: [buildMonitorFor(dockerRecommendations, args)],
896
+ args: args,
897
+ });
898
+
899
+ expect(covered.size).toBe(0);
900
+ });
901
+ });