@oneuptime/common 12.0.22 → 12.0.24

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 (345) hide show
  1. package/Models/DatabaseModels/DetectionRule.ts +55 -0
  2. package/Models/DatabaseModels/Index.ts +2 -0
  3. package/Models/DatabaseModels/NetworkDevice.ts +44 -0
  4. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +584 -0
  5. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +105 -0
  6. package/Models/DatabaseModels/Project.ts +68 -0
  7. package/Models/DatabaseModels/User.ts +68 -0
  8. package/Server/API/TelemetryAPI.ts +29 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.ts +60 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.ts +26 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.ts +26 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  16. package/Server/Middleware/GzipRequestBody.ts +311 -0
  17. package/Server/Middleware/MultipartFormData.ts +87 -3
  18. package/Server/Services/DetectionRuleService.ts +73 -6
  19. package/Server/Services/Index.ts +207 -0
  20. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +917 -0
  21. package/Server/Services/NetworkDeviceAutoImportRuleService.ts +181 -0
  22. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
  23. package/Server/Services/NetworkDeviceService.ts +148 -0
  24. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  25. package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
  26. package/Server/Services/ProjectService.ts +29 -19
  27. package/Server/Services/SecurityEventService.ts +90 -13
  28. package/Server/Services/StatusPageResourceService.ts +223 -0
  29. package/Server/Services/TelemetryAttributeService.ts +17 -0
  30. package/Server/Services/UserService.ts +4 -6
  31. package/Server/Types/Markdown.ts +161 -67
  32. package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
  33. package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
  34. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
  35. package/Server/Utils/Attribution.ts +4 -0
  36. package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
  37. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +39 -1
  38. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
  39. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
  40. package/Server/Utils/StartServer.ts +12 -32
  41. package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
  42. package/Server/Utils/Telemetry/LlmMetricSpend.ts +130 -0
  43. package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
  44. package/Server/Views/Partials/Head.ejs +2 -1
  45. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
  46. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
  47. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
  48. package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
  49. package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
  50. package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
  51. package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
  52. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
  53. package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
  54. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
  55. package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
  56. package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
  57. package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
  58. package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
  59. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
  60. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
  61. package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
  62. package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
  63. package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
  64. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
  65. package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
  66. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
  67. package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
  68. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
  69. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
  70. package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
  71. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
  72. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
  73. package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
  74. package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
  75. package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
  76. package/Tests/Server/Services/AutoImportScanCredentialSelect.test.ts +205 -0
  77. package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
  78. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
  79. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
  80. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +933 -0
  81. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
  82. package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
  83. package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
  84. package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
  85. package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
  86. package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
  87. package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
  88. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
  89. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
  90. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
  91. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +97 -1
  92. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
  93. package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
  94. package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +368 -4
  95. package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +245 -0
  96. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
  97. package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
  98. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
  99. package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
  100. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
  101. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
  102. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
  103. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
  104. package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +193 -0
  105. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
  106. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
  107. package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
  108. package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
  109. package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
  110. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
  111. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
  112. package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
  113. package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
  114. package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
  115. package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
  116. package/Tests/UI/Components/SideOver.test.tsx +1 -1
  117. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
  118. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
  119. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
  120. package/Tests/UI/Components/Toggle.test.tsx +25 -0
  121. package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
  122. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
  123. package/Tests/Utils/NetworkDiscovery/AutoImportRuleMatcher.test.ts +516 -0
  124. package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +272 -0
  125. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
  126. package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
  127. package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +164 -0
  128. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
  129. package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +472 -0
  130. package/Types/Code/YamlSyntax.ts +396 -0
  131. package/Types/Exception/ExceptionCode.ts +1 -0
  132. package/Types/Exception/PayloadTooLargeException.ts +8 -0
  133. package/Types/Marketing/Attribution.ts +10 -0
  134. package/Types/Marketing/MarketingEvent.ts +4 -0
  135. package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
  136. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
  137. package/Types/NetworkAutomation/RuleRunResult.ts +84 -0
  138. package/Types/Permission.ts +47 -0
  139. package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
  140. package/Types/Telemetry/LlmMetricConventions.ts +124 -0
  141. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
  142. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
  143. package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
  144. package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
  145. package/UI/Components/Detail/Detail.tsx +10 -0
  146. package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
  147. package/UI/Components/Forms/Fields/FormField.tsx +31 -0
  148. package/UI/Components/Forms/Types/Field.ts +8 -0
  149. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  150. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
  151. package/UI/Components/Forms/Validation.ts +46 -0
  152. package/UI/Components/Input/Input.tsx +3 -0
  153. package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
  154. package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
  155. package/UI/Components/ModelTable/Column.ts +8 -0
  156. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
  157. package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
  158. package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
  159. package/UI/Components/SideOver/SideOver.tsx +5 -5
  160. package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
  161. package/UI/Components/StatusPage/ResourceList.tsx +197 -38
  162. package/UI/Components/Toggle/Toggle.tsx +9 -1
  163. package/UI/Components/Types/FieldType.ts +1 -0
  164. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
  165. package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +257 -0
  166. package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +183 -0
  167. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +95 -0
  168. package/Utils/NetworkDiscovery/DiscoveryImportEligibility.ts +66 -0
  169. package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
  170. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +82 -0
  171. package/Utils/StatusPage/ResourceExplorer.ts +258 -0
  172. package/Utils/Telemetry/LlmMetricQuery.ts +204 -0
  173. package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
  174. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  175. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  176. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  177. package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
  178. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  179. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +609 -0
  180. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -0
  181. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +97 -0
  182. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/Project.js +76 -0
  184. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/User.js +76 -0
  186. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  187. package/build/dist/Server/API/TelemetryAPI.js +17 -0
  188. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.js +35 -0
  190. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.js.map +1 -0
  191. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.js +19 -0
  192. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.js.map +1 -0
  193. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.js +19 -0
  194. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.js.map +1 -0
  195. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
  196. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
  197. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
  198. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
  199. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
  200. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
  201. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  202. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  203. package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
  204. package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
  205. package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
  206. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  207. package/build/dist/Server/Services/DetectionRuleService.js +49 -4
  208. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  209. package/build/dist/Server/Services/Index.js +206 -0
  210. package/build/dist/Server/Services/Index.js.map +1 -1
  211. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +711 -0
  212. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -0
  213. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +155 -0
  214. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -0
  215. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
  216. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  217. package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
  218. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  219. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
  220. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  221. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
  222. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
  223. package/build/dist/Server/Services/ProjectService.js +25 -45
  224. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  225. package/build/dist/Server/Services/SecurityEventService.js +57 -5
  226. package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
  227. package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
  228. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  229. package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
  230. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  231. package/build/dist/Server/Services/UserService.js +2 -12
  232. package/build/dist/Server/Services/UserService.js.map +1 -1
  233. package/build/dist/Server/Types/Markdown.js +147 -61
  234. package/build/dist/Server/Types/Markdown.js.map +1 -1
  235. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
  236. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
  237. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
  238. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  239. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
  240. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  241. package/build/dist/Server/Utils/Attribution.js.map +1 -1
  242. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
  243. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
  244. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +26 -0
  245. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  246. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
  247. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  248. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
  249. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  250. package/build/dist/Server/Utils/StartServer.js +12 -21
  251. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  252. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
  253. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
  254. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +102 -0
  255. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
  256. package/build/dist/Types/Code/YamlSyntax.js +212 -0
  257. package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
  258. package/build/dist/Types/Exception/ExceptionCode.js +1 -0
  259. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  260. package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
  261. package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
  262. package/build/dist/Types/Marketing/Attribution.js +10 -0
  263. package/build/dist/Types/Marketing/Attribution.js.map +1 -1
  264. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
  265. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  266. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
  267. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  268. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +24 -0
  269. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  270. package/build/dist/Types/Permission.js +41 -0
  271. package/build/dist/Types/Permission.js.map +1 -1
  272. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
  273. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  274. package/build/dist/Types/Telemetry/LlmMetricConventions.js +98 -0
  275. package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
  276. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  277. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  278. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
  279. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  280. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
  281. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  282. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
  283. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
  284. package/build/dist/UI/Components/Detail/Detail.js +9 -0
  285. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  286. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
  287. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  288. package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
  289. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  290. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  291. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  292. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  293. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
  294. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
  295. package/build/dist/UI/Components/Forms/Validation.js +27 -0
  296. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  297. package/build/dist/UI/Components/Input/Input.js +1 -1
  298. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  299. package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
  300. package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
  301. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
  302. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  303. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
  304. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  305. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
  306. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
  307. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
  308. package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
  309. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
  310. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  311. package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
  312. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  313. package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
  314. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  315. package/build/dist/UI/Components/Types/FieldType.js +1 -0
  316. package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
  317. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
  318. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
  319. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js +149 -0
  320. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -0
  321. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +121 -0
  322. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -0
  323. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +75 -0
  324. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -0
  325. package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js +59 -0
  326. package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js.map +1 -0
  327. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
  328. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
  329. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +60 -0
  330. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
  331. package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
  332. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
  333. package/build/dist/Utils/Telemetry/LlmMetricQuery.js +119 -0
  334. package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
  335. package/jest.config.json +2 -0
  336. package/package.json +1 -1
  337. package/tsconfig.json +2 -1
  338. package/Tests/UI/Components/Toast.test.tsx +0 -197
  339. package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
  340. package/UI/Components/Toast/Toast.tsx +0 -189
  341. package/UI/Components/Toast/ToastInit.tsx +0 -110
  342. package/build/dist/UI/Components/Toast/Toast.js +0 -123
  343. package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
  344. package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
  345. package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
@@ -0,0 +1,1122 @@
1
+ import CriteriaFilterUtil from "../../../../App/FeatureSet/Dashboard/src/Utils/Form/Monitor/CriteriaFilter";
2
+ import MonitorCriteriaAlignmentUtil, {
3
+ CriteriaSeedOptions,
4
+ MonitorStepsAlignmentResult,
5
+ } from "../../../../App/FeatureSet/Dashboard/src/Utils/Form/Monitor/MonitorCriteriaAlignment";
6
+ import URL from "../../../Types/API/URL";
7
+ import FilterCondition from "../../../Types/Filter/FilterCondition";
8
+ import { CriteriaIncident } from "../../../Types/Monitor/CriteriaIncident";
9
+ import {
10
+ CheckOn,
11
+ CriteriaFilter,
12
+ FilterType,
13
+ } from "../../../Types/Monitor/CriteriaFilter";
14
+ import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
15
+ import MonitorStep from "../../../Types/Monitor/MonitorStep";
16
+ import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
17
+ import MonitorType, {
18
+ MonitorTypeHelper,
19
+ } from "../../../Types/Monitor/MonitorType";
20
+ import ObjectID from "../../../Types/ObjectID";
21
+ import { DropdownOption } from "../../../UI/Components/Dropdown/Dropdown";
22
+ import { describe, expect, test } from "@jest/globals";
23
+
24
+ /*
25
+ * Monitor type and monitor criteria are on different steps of the monitor
26
+ * create form, and the criteria step's fields unmount while the user is on
27
+ * another step. So a user can seed criteria for one monitor type and then
28
+ * change their mind: visit the criteria step, walk back, pick a different
29
+ * type, come forward again. The criteria that come back with them were
30
+ * written against the type they abandoned.
31
+ *
32
+ * Left alone, those criteria name checks the new monitor type does not
33
+ * offer - "Is Online" on an External Status Page monitor, say - and the
34
+ * criteria form's dropdowns fall back to react-select's empty "Select..."
35
+ * placeholder because they cannot find an option matching what is stored.
36
+ * The rule is not empty; the form cannot draw it. Saved as-is it also never
37
+ * fires, because the server's comparators switch on a filter type they no
38
+ * longer recognise.
39
+ *
40
+ * This is the same class of defect as #3412 (the Filter Condition dropdown
41
+ * on the seeded External Status Page criteria, pinned in
42
+ * CriteriaFilterDefaults.test.ts), arriving by a different route: there the
43
+ * seed itself was unrenderable, here a renderable seed is outlived by the
44
+ * monitor type it was written for.
45
+ *
46
+ * The fix has two halves, and this file pins both:
47
+ *
48
+ * 1. Criteria still identical to the defaults of the type they were
49
+ * seeded for carry nothing of the user's, so they are re-seeded
50
+ * outright for the new type - the user gets what picking that type
51
+ * first would have given them.
52
+ * 2. Criteria the user has edited are repaired filter by filter instead.
53
+ * Their names, incidents, alerts and every filter that still applies
54
+ * to the new type survive; filters the new type cannot express are
55
+ * dropped, never swapped for a fabricated one - see the inversion
56
+ * test below for why that distinction is the important one.
57
+ *
58
+ * And over both halves, the invariant that matters: whatever monitor type
59
+ * the criteria were seeded for and whichever one the user lands on, every
60
+ * filter that reaches the form is renderable.
61
+ */
62
+
63
+ const SEED_OPTIONS: CriteriaSeedOptions = {
64
+ monitorName: "Acme",
65
+ onlineMonitorStatusId: new ObjectID("aaaaaaaaaaaaaaaaaaaaaaaa"),
66
+ offlineMonitorStatusId: new ObjectID("bbbbbbbbbbbbbbbbbbbbbbbb"),
67
+ defaultIncidentSeverityId: new ObjectID("cccccccccccccccccccccccc"),
68
+ defaultAlertSeverityId: new ObjectID("dddddddddddddddddddddddd"),
69
+ };
70
+
71
+ const ALL_MONITOR_TYPES: Array<MonitorType> = Object.values(
72
+ MonitorType,
73
+ ) as Array<MonitorType>;
74
+
75
+ function optionValues(options: Array<DropdownOption>): Array<string> {
76
+ return options.map((option: DropdownOption) => {
77
+ return option.value.toString();
78
+ });
79
+ }
80
+
81
+ function checkOnOptionsFor(monitorType: MonitorType): Array<string> {
82
+ return optionValues(
83
+ CriteriaFilterUtil.getCheckOnOptionsByMonitorType(monitorType),
84
+ );
85
+ }
86
+
87
+ function filterTypeOptionsFor(checkOn: CheckOn): Array<string> {
88
+ return optionValues(
89
+ CriteriaFilterUtil.getFilterTypeOptionsByCheckOn(checkOn),
90
+ );
91
+ }
92
+
93
+ // The monitor steps the criteria form seeds when a monitor type is picked.
94
+ function stepsSeededFor(monitorType: MonitorType): MonitorSteps {
95
+ return MonitorSteps.getDefaultMonitorSteps({
96
+ monitorType: monitorType,
97
+ monitorName: SEED_OPTIONS.monitorName,
98
+ defaultMonitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
99
+ onlineMonitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
100
+ offlineMonitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
101
+ defaultIncidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
102
+ defaultAlertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
103
+ });
104
+ }
105
+
106
+ /*
107
+ * Monitor types the criteria form is ever drawn for. Manual is not one:
108
+ * nothing polls a manual monitor, getCheckOnOptionsByMonitorType returns
109
+ * an empty list for it, and the Criteria page renders an empty state in
110
+ * place of the form - so "the criteria are renderable" is not a question
111
+ * that can be asked of it.
112
+ */
113
+ const MONITOR_TYPES_WITH_CRITERIA: Array<MonitorType> =
114
+ ALL_MONITOR_TYPES.filter((monitorType: MonitorType) => {
115
+ return MonitorTypeHelper.doesMonitorTypeHaveCriteria(monitorType);
116
+ });
117
+
118
+ /*
119
+ * Monitor types that seed no real criteria. Only Manual: nothing polls a
120
+ * manual monitor, so getCheckOnOptionsByMonitorType returns an empty list
121
+ * for it and there is no check its criteria form could ever draw.
122
+ *
123
+ * The metric-backed family (Docker, Host, Podman, Docker Swarm, Proxmox,
124
+ * Ceph and IoT Device) and Profiles once belonged here too - they fell
125
+ * through getDefaultOfflineMonitorCriteriaInstance to the constructor's
126
+ * blank "Is Online" placeholder their own narrowed option lists did not
127
+ * offer. That gap was closed in "seed real criteria for the metric and
128
+ * profile monitor types": they now seed a real default on the one check
129
+ * their evaluator reads (MetricValue for the metric family, ProfileCount
130
+ * for Profiles), so they seed something the form can draw and have moved to
131
+ * the WITH list, swept as both sources and targets like every other type.
132
+ *
133
+ * This list pins the gap from both sides - the assertions below fail if a
134
+ * type here quietly grows a real default, and if a type not here quietly
135
+ * loses one.
136
+ */
137
+ const MONITOR_TYPES_WITHOUT_SEEDED_CRITERIA: Array<MonitorType> = [
138
+ MonitorType.Manual,
139
+ ];
140
+
141
+ const MONITOR_TYPES_WITH_SEEDED_CRITERIA: Array<MonitorType> =
142
+ MONITOR_TYPES_WITH_CRITERIA.filter((monitorType: MonitorType) => {
143
+ return !MONITOR_TYPES_WITHOUT_SEEDED_CRITERIA.includes(monitorType);
144
+ });
145
+
146
+ /*
147
+ * The types a sweep starts from, given the type it is aligning to.
148
+ * "Changing" to the type you are already on is the one case where nothing
149
+ * is meant to happen, and it is pinned separately by the no-churn tests.
150
+ */
151
+ function sourceTypesFor(monitorType: MonitorType): Array<MonitorType> {
152
+ return MONITOR_TYPES_WITH_SEEDED_CRITERIA.filter((candidate: MonitorType) => {
153
+ return candidate !== monitorType;
154
+ });
155
+ }
156
+
157
+ function alignTo(
158
+ monitorSteps: MonitorSteps,
159
+ monitorType: MonitorType,
160
+ ): MonitorStepsAlignmentResult {
161
+ return MonitorCriteriaAlignmentUtil.alignMonitorStepsWithMonitorType({
162
+ monitorSteps: monitorSteps,
163
+ monitorType: monitorType,
164
+ seedOptions: SEED_OPTIONS,
165
+ });
166
+ }
167
+
168
+ function criteriaOf(
169
+ monitorSteps: MonitorSteps,
170
+ ): Array<MonitorCriteriaInstance> {
171
+ return (monitorSteps.data?.monitorStepsInstanceArray || []).flatMap(
172
+ (monitorStep: MonitorStep) => {
173
+ return (
174
+ monitorStep.data?.monitorCriteria.data?.monitorCriteriaInstanceArray ||
175
+ []
176
+ );
177
+ },
178
+ );
179
+ }
180
+
181
+ function filtersOf(monitorSteps: MonitorSteps): Array<CriteriaFilter> {
182
+ return criteriaOf(monitorSteps).flatMap(
183
+ (instance: MonitorCriteriaInstance) => {
184
+ return instance.data?.filters || [];
185
+ },
186
+ );
187
+ }
188
+
189
+ function criteriaNamesOf(monitorSteps: MonitorSteps): Array<string> {
190
+ return criteriaOf(monitorSteps).map((instance: MonitorCriteriaInstance) => {
191
+ return instance.data!.name;
192
+ });
193
+ }
194
+
195
+ // Every filter as "<check> <condition> <value>", so a failure reads.
196
+ function describeFilters(monitorSteps: MonitorSteps): Array<string> {
197
+ return filtersOf(monitorSteps).map((filter: CriteriaFilter) => {
198
+ return `${filter.checkOn} ${filter.filterType} ${filter.value}`;
199
+ });
200
+ }
201
+
202
+ /*
203
+ * Everything about `monitorSteps` the criteria form for `monitorType`
204
+ * cannot draw, as readable sentences - an empty array is the assertion,
205
+ * and anything else names what broke.
206
+ */
207
+ function unrenderable(
208
+ monitorSteps: MonitorSteps,
209
+ monitorType: MonitorType,
210
+ ): Array<string> {
211
+ const offeredChecks: Array<string> = checkOnOptionsFor(monitorType);
212
+ const problems: Array<string> = [];
213
+
214
+ for (const instance of criteriaOf(monitorSteps)) {
215
+ for (const filter of instance.data?.filters || []) {
216
+ if (!offeredChecks.includes(filter.checkOn?.toString())) {
217
+ problems.push(
218
+ `"${instance.data!.name}" filters on check "${filter.checkOn}", which ${monitorType} does not offer`,
219
+ );
220
+ continue;
221
+ }
222
+
223
+ if (!filter.filterType) {
224
+ problems.push(
225
+ `"${instance.data!.name}" filters on check "${filter.checkOn}" with no condition at all`,
226
+ );
227
+ continue;
228
+ }
229
+
230
+ const offeredConditions: Array<string> = filterTypeOptionsFor(
231
+ filter.checkOn,
232
+ );
233
+
234
+ if (!offeredConditions.includes(filter.filterType.toString())) {
235
+ problems.push(
236
+ `"${instance.data!.name}" filters on check "${filter.checkOn}" with condition "${filter.filterType}", which is not one of [${offeredConditions.join(", ")}]`,
237
+ );
238
+ }
239
+ }
240
+ }
241
+
242
+ return problems;
243
+ }
244
+
245
+ describe("Criteria after the monitor type changes under them", () => {
246
+ describe("the reported defect", () => {
247
+ test("criteria seeded for a Website are unrenderable once the type is External Status Page", () => {
248
+ const seededForWebsite: MonitorSteps = stepsSeededFor(
249
+ MonitorType.Website,
250
+ );
251
+
252
+ /*
253
+ * The bug, stated: a Website's criteria check "Is Online" and
254
+ * "Response Status Code", and an External Status Page monitor offers
255
+ * neither, so both of that filter's dropdowns render empty.
256
+ */
257
+ expect(
258
+ unrenderable(seededForWebsite, MonitorType.ExternalStatusPage),
259
+ ).not.toEqual([]);
260
+
261
+ const aligned: MonitorStepsAlignmentResult = alignTo(
262
+ seededForWebsite,
263
+ MonitorType.ExternalStatusPage,
264
+ );
265
+
266
+ expect(aligned.didChange).toBe(true);
267
+ expect(
268
+ unrenderable(aligned.monitorSteps, MonitorType.ExternalStatusPage),
269
+ ).toEqual([]);
270
+ });
271
+
272
+ test("the External Status Page criteria the user ends up with are the ones picking that type first would have given them", () => {
273
+ const aligned: MonitorStepsAlignmentResult = alignTo(
274
+ stepsSeededFor(MonitorType.Website),
275
+ MonitorType.ExternalStatusPage,
276
+ );
277
+
278
+ expect(describeFilters(aligned.monitorSteps)).toEqual(
279
+ describeFilters(stepsSeededFor(MonitorType.ExternalStatusPage)),
280
+ );
281
+
282
+ expect(criteriaNamesOf(aligned.monitorSteps)).toEqual(
283
+ criteriaNamesOf(stepsSeededFor(MonitorType.ExternalStatusPage)),
284
+ );
285
+ });
286
+ });
287
+
288
+ describe("every monitor type change leaves criteria the form can draw", () => {
289
+ test.each(MONITOR_TYPES_WITH_CRITERIA)(
290
+ "criteria seeded for any other type are renderable once aligned to %s",
291
+ (monitorType: MonitorType) => {
292
+ const problems: Array<string> = [];
293
+
294
+ for (const seededFor of sourceTypesFor(monitorType)) {
295
+ const aligned: MonitorStepsAlignmentResult = alignTo(
296
+ stepsSeededFor(seededFor),
297
+ monitorType,
298
+ );
299
+
300
+ for (const problem of unrenderable(
301
+ aligned.monitorSteps,
302
+ monitorType,
303
+ )) {
304
+ problems.push(`seeded for ${seededFor}: ${problem}`);
305
+ }
306
+ }
307
+
308
+ expect(problems).toEqual([]);
309
+ },
310
+ );
311
+
312
+ test.each(MONITOR_TYPES_WITH_SEEDED_CRITERIA)(
313
+ "%s: criteria the user has edited are still renderable once aligned to every other type",
314
+ (seededFor: MonitorType) => {
315
+ /*
316
+ * An edited criteria takes the repair path rather than the
317
+ * re-seed path, so sweep that path over every pair too. The edit
318
+ * is deliberately one that survives - a renamed criteria - so what
319
+ * is under test is the filters, not whether the edit was noticed.
320
+ */
321
+ const problems: Array<string> = [];
322
+
323
+ for (const monitorType of sourceTypesFor(seededFor)) {
324
+ const edited: MonitorSteps = stepsSeededFor(seededFor);
325
+ criteriaOf(edited)[0]!.setName("My own rule");
326
+
327
+ const aligned: MonitorStepsAlignmentResult = alignTo(
328
+ edited,
329
+ monitorType,
330
+ );
331
+
332
+ for (const problem of unrenderable(
333
+ aligned.monitorSteps,
334
+ monitorType,
335
+ )) {
336
+ problems.push(`aligned to ${monitorType}: ${problem}`);
337
+ }
338
+ }
339
+
340
+ expect(problems).toEqual([]);
341
+ },
342
+ );
343
+ });
344
+
345
+ describe("criteria that already suit the monitor type are left alone", () => {
346
+ test.each(ALL_MONITOR_TYPES)(
347
+ "%s: its own freshly seeded criteria come back untouched",
348
+ (monitorType: MonitorType) => {
349
+ const seeded: MonitorSteps = stepsSeededFor(monitorType);
350
+
351
+ const aligned: MonitorStepsAlignmentResult = alignTo(
352
+ seeded,
353
+ monitorType,
354
+ );
355
+
356
+ expect(aligned.didChange).toBe(false);
357
+ /*
358
+ * Same object, not just equal content. The criteria form writes
359
+ * the result back into the form on change, and re-seeding criteria
360
+ * that were already right would churn their generated ids and mark
361
+ * an untouched form dirty every time it mounted.
362
+ */
363
+ expect(aligned.monitorSteps).toBe(seeded);
364
+ },
365
+ );
366
+
367
+ test("criteria carrying an edit that is still valid are not re-seeded", () => {
368
+ const edited: MonitorSteps = stepsSeededFor(MonitorType.Website);
369
+ criteriaOf(edited)[0]!.setName("Down for two checks in a row");
370
+
371
+ const aligned: MonitorStepsAlignmentResult = alignTo(
372
+ edited,
373
+ MonitorType.Website,
374
+ );
375
+
376
+ expect(aligned.didChange).toBe(false);
377
+ expect(criteriaNamesOf(aligned.monitorSteps)).toContain(
378
+ "Down for two checks in a row",
379
+ );
380
+ });
381
+ });
382
+
383
+ describe("untouched defaults are re-seeded for the new monitor type", () => {
384
+ test("Website to Incoming Request swaps in the Incoming Request defaults wholesale", () => {
385
+ const aligned: MonitorStepsAlignmentResult = alignTo(
386
+ stepsSeededFor(MonitorType.Website),
387
+ MonitorType.IncomingRequest,
388
+ );
389
+
390
+ /*
391
+ * Repairing rather than re-seeding here would have left the user
392
+ * with "Check if Acme is offline" over a blank "not received in
393
+ * minutes" threshold - the first check Incoming Request offers -
394
+ * which is not a rule anybody asked for. What they get instead is
395
+ * the pair of request-body rules picking Incoming Request first
396
+ * would have seeded.
397
+ */
398
+ expect(describeFilters(aligned.monitorSteps)).toEqual([
399
+ `${CheckOn.RequestBody} ${FilterType.Contains} ${MonitorCriteriaInstance.DEFAULT_INCOMING_BODY_ERROR_KEYWORD}`,
400
+ `${CheckOn.RequestBody} ${FilterType.NotContains} ${MonitorCriteriaInstance.DEFAULT_INCOMING_BODY_ERROR_KEYWORD}`,
401
+ ]);
402
+
403
+ expect(describeFilters(aligned.monitorSteps)).toEqual(
404
+ describeFilters(stepsSeededFor(MonitorType.IncomingRequest)),
405
+ );
406
+ });
407
+
408
+ test("the incidents the new monitor type seeds come with it", () => {
409
+ /*
410
+ * Re-seeding has to bring the whole criteria across, not just the
411
+ * filters - a Logs monitor whose criteria change the monitor status
412
+ * but open no incident is not what picking Logs gives you.
413
+ */
414
+ const aligned: MonitorStepsAlignmentResult = alignTo(
415
+ stepsSeededFor(MonitorType.Ping),
416
+ MonitorType.Logs,
417
+ );
418
+
419
+ expect(describeFilters(aligned.monitorSteps)).toEqual(
420
+ describeFilters(stepsSeededFor(MonitorType.Logs)),
421
+ );
422
+
423
+ const incidents: Array<CriteriaIncident> = criteriaOf(
424
+ aligned.monitorSteps,
425
+ ).flatMap((instance: MonitorCriteriaInstance) => {
426
+ return instance.data?.incidents || [];
427
+ });
428
+
429
+ expect(incidents.length).toBeGreaterThan(0);
430
+ expect(incidents[0]!.incidentSeverityId?.toString()).toBe(
431
+ SEED_OPTIONS.defaultIncidentSeverityId.toString(),
432
+ );
433
+ });
434
+
435
+ test("what the user configured on the monitor step itself is not collateral damage", () => {
436
+ /*
437
+ * Only the criteria are re-seeded. The destination, request method
438
+ * and everything else on the step belong to the user even when the
439
+ * criteria on it are untouched defaults.
440
+ *
441
+ * The pair matters: Website and API seed byte-identical criteria, so
442
+ * aligning between them short-circuits on "already untouched
443
+ * defaults for this type" and hands the very same object back -
444
+ * which would make this assertion pass no matter what the re-seed
445
+ * branch did to the step. External Status Page actually re-seeds.
446
+ */
447
+ const seeded: MonitorSteps = stepsSeededFor(MonitorType.Website);
448
+ seeded.data!.monitorStepsInstanceArray[0]!.setMonitorDestination(
449
+ URL.fromString("https://acme.example.com/health"),
450
+ );
451
+
452
+ const aligned: MonitorStepsAlignmentResult = alignTo(
453
+ seeded,
454
+ MonitorType.ExternalStatusPage,
455
+ );
456
+
457
+ expect(aligned.didChange).toBe(true);
458
+
459
+ expect(
460
+ aligned.monitorSteps.data!.monitorStepsInstanceArray[0]!.data!.monitorDestination?.toString(),
461
+ ).toBe("https://acme.example.com/health");
462
+
463
+ // The step's other settings are not collateral damage either.
464
+ expect(
465
+ aligned.monitorSteps.data!.monitorStepsInstanceArray[0]!.data!
466
+ .requestType,
467
+ ).toBe(seeded.data!.monitorStepsInstanceArray[0]!.data!.requestType);
468
+
469
+ expect(
470
+ aligned.monitorSteps.data!.defaultMonitorStatusId?.toString(),
471
+ ).toBe(SEED_OPTIONS.onlineMonitorStatusId.toString());
472
+ });
473
+ });
474
+
475
+ describe("criteria the user has edited are repaired, not replaced", () => {
476
+ function editedWebsiteSteps(): MonitorSteps {
477
+ const monitorSteps: MonitorSteps = stepsSeededFor(MonitorType.Website);
478
+
479
+ const offlineCriteria: MonitorCriteriaInstance =
480
+ criteriaOf(monitorSteps)[0]!;
481
+
482
+ offlineCriteria.setName("Acme is down and I want to know about it");
483
+ offlineCriteria.setFilters([
484
+ ...offlineCriteria.data!.filters,
485
+ {
486
+ checkOn: CheckOn.ResponseTime,
487
+ filterType: FilterType.GreaterThan,
488
+ value: 3000,
489
+ },
490
+ ]);
491
+
492
+ return monitorSteps;
493
+ }
494
+
495
+ test("the name the user wrote survives a switch to a completely different monitor type", () => {
496
+ const aligned: MonitorStepsAlignmentResult = alignTo(
497
+ editedWebsiteSteps(),
498
+ MonitorType.ExternalStatusPage,
499
+ );
500
+
501
+ expect(criteriaNamesOf(aligned.monitorSteps)).toContain(
502
+ "Acme is down and I want to know about it",
503
+ );
504
+ expect(
505
+ unrenderable(aligned.monitorSteps, MonitorType.ExternalStatusPage),
506
+ ).toEqual([]);
507
+ });
508
+
509
+ test("the incident the user's criteria opens survives too", () => {
510
+ const aligned: MonitorStepsAlignmentResult = alignTo(
511
+ editedWebsiteSteps(),
512
+ MonitorType.ExternalStatusPage,
513
+ );
514
+
515
+ const incidents: Array<CriteriaIncident> = criteriaOf(
516
+ aligned.monitorSteps,
517
+ ).flatMap((instance: MonitorCriteriaInstance) => {
518
+ return instance.data?.incidents || [];
519
+ });
520
+
521
+ expect(
522
+ incidents.map((incident: CriteriaIncident) => {
523
+ return incident.title;
524
+ }),
525
+ ).toContain("Acme is offline");
526
+ });
527
+
528
+ test("a threshold the user typed survives when the new monitor type still offers that check", () => {
529
+ /*
530
+ * Website and API both offer Response Time, so switching between
531
+ * them must not touch a threshold typed against it - the whole point
532
+ * of repairing rather than re-seeding.
533
+ */
534
+ const aligned: MonitorStepsAlignmentResult = alignTo(
535
+ editedWebsiteSteps(),
536
+ MonitorType.API,
537
+ );
538
+
539
+ expect(
540
+ filtersOf(aligned.monitorSteps).find((filter: CriteriaFilter) => {
541
+ return filter.checkOn === CheckOn.ResponseTime;
542
+ })?.value,
543
+ ).toBe(3000);
544
+ });
545
+
546
+ test("only the filters the new monitor type cannot render are dropped", () => {
547
+ /*
548
+ * The edited criteria carries three filters: "Is Online" is false,
549
+ * the status code is not 200, and the response took over three
550
+ * seconds. A Server monitor offers Is Online but neither of the
551
+ * other two checks, so the first filter should come through
552
+ * untouched and the other two should simply go.
553
+ */
554
+ const aligned: MonitorStepsAlignmentResult = alignTo(
555
+ editedWebsiteSteps(),
556
+ MonitorType.Server,
557
+ );
558
+
559
+ const repairedCriteria: MonitorCriteriaInstance = criteriaOf(
560
+ aligned.monitorSteps,
561
+ ).find((instance: MonitorCriteriaInstance) => {
562
+ return (
563
+ instance.data!.name === "Acme is down and I want to know about it"
564
+ );
565
+ })!;
566
+
567
+ expect(repairedCriteria.data!.filters).toEqual([
568
+ {
569
+ checkOn: CheckOn.IsOnline,
570
+ filterType: FilterType.False,
571
+ value: undefined,
572
+ },
573
+ ]);
574
+
575
+ expect(unrenderable(aligned.monitorSteps, MonitorType.Server)).toEqual(
576
+ [],
577
+ );
578
+ });
579
+
580
+ test("a filter the new monitor type cannot express is dropped, never swapped for one the user did not write", () => {
581
+ /*
582
+ * This is the regression that matters most in this file.
583
+ *
584
+ * The obvious repair - swap an unusable filter for this monitor
585
+ * type's default - quietly INVERTS the criteria. Every monitor
586
+ * type's default filter is a positive, immediately-matching rule
587
+ * ("is online / True"), and every seeded offline criteria runs
588
+ * under the "Any" filter condition, so one such filter is enough to
589
+ * fire the whole criteria. The criteria keeps its name, its
590
+ * "create incident" flag, its "Acme is offline" incident and its
591
+ * offline monitor status - and now fires while Acme is perfectly
592
+ * healthy.
593
+ *
594
+ * A stale filter at least failed safe by never matching. So: no
595
+ * fabricated filter may ever be added alongside the user's
596
+ * surviving ones.
597
+ */
598
+ const aligned: MonitorStepsAlignmentResult = alignTo(
599
+ editedWebsiteSteps(),
600
+ MonitorType.Server,
601
+ );
602
+
603
+ const repairedCriteria: MonitorCriteriaInstance = criteriaOf(
604
+ aligned.monitorSteps,
605
+ ).find((instance: MonitorCriteriaInstance) => {
606
+ return (
607
+ instance.data!.name === "Acme is down and I want to know about it"
608
+ );
609
+ })!;
610
+
611
+ // The criteria still opens an incident, under "Any".
612
+ expect(repairedCriteria.data!.createIncidents).toBe(true);
613
+ expect(repairedCriteria.data!.filterCondition).toBe(FilterCondition.Any);
614
+
615
+ const positiveFilters: Array<CriteriaFilter> =
616
+ repairedCriteria.data!.filters.filter((filter: CriteriaFilter) => {
617
+ return filter.filterType === FilterType.True;
618
+ });
619
+
620
+ expect(positiveFilters).toEqual([]);
621
+ expect(repairedCriteria.data!.filters).not.toContainEqual(
622
+ CriteriaFilterUtil.getDefaultCriteriaFilter(MonitorType.Server),
623
+ );
624
+ });
625
+
626
+ test("a criteria left with nothing it can express is refilled from the matching half of the new type's defaults", () => {
627
+ /*
628
+ * Website to External Status Page drops every filter of the offline
629
+ * criteria, and a criteria with no filters at all is not something
630
+ * the form can show. It is refilled - but from the type's OFFLINE
631
+ * defaults, because this criteria opens an incident. Refilling it
632
+ * from the online defaults would produce the inversion above by
633
+ * another route.
634
+ */
635
+ const aligned: MonitorStepsAlignmentResult = alignTo(
636
+ editedWebsiteSteps(),
637
+ MonitorType.ExternalStatusPage,
638
+ );
639
+
640
+ const repairedCriteria: MonitorCriteriaInstance = criteriaOf(
641
+ aligned.monitorSteps,
642
+ ).find((instance: MonitorCriteriaInstance) => {
643
+ return (
644
+ instance.data!.name === "Acme is down and I want to know about it"
645
+ );
646
+ })!;
647
+
648
+ const offlineDefaults: MonitorCriteriaInstance =
649
+ MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
650
+ monitorType: MonitorType.ExternalStatusPage,
651
+ monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
652
+ incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
653
+ alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
654
+ monitorName: SEED_OPTIONS.monitorName,
655
+ });
656
+
657
+ expect(repairedCriteria.data!.filters).toEqual(
658
+ offlineDefaults.data!.filters,
659
+ );
660
+
661
+ /*
662
+ * Concretely: it fires when the status page is NOT online, which is
663
+ * what a criteria named "Acme is down" and carrying an "Acme is
664
+ * offline" incident has to do.
665
+ */
666
+ expect(repairedCriteria.data!.filters[0]).toEqual({
667
+ checkOn: CheckOn.ExternalStatusPageIsOnline,
668
+ filterType: FilterType.False,
669
+ value: undefined,
670
+ });
671
+
672
+ // The user's own wording and incident are still theirs.
673
+ expect(repairedCriteria.data!.name).toBe(
674
+ "Acme is down and I want to know about it",
675
+ );
676
+ expect(repairedCriteria.data!.incidents[0]!.title).toBe(
677
+ "Acme is offline",
678
+ );
679
+ });
680
+
681
+ test("an up criteria left with nothing it can express is refilled from the online defaults", () => {
682
+ /*
683
+ * The other half of the same rule. The seeded "is online" criteria
684
+ * raises nothing and parks the monitor on the operational status,
685
+ * so it must come back matching when the target is UP.
686
+ */
687
+ const aligned: MonitorStepsAlignmentResult = alignTo(
688
+ editedWebsiteSteps(),
689
+ MonitorType.ExternalStatusPage,
690
+ );
691
+
692
+ const onlineCriteria: MonitorCriteriaInstance = criteriaOf(
693
+ aligned.monitorSteps,
694
+ ).find((instance: MonitorCriteriaInstance) => {
695
+ return (
696
+ instance.data!.monitorStatusId?.toString() ===
697
+ SEED_OPTIONS.onlineMonitorStatusId.toString()
698
+ );
699
+ })!;
700
+
701
+ const onlineDefaults: MonitorCriteriaInstance =
702
+ MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
703
+ monitorType: MonitorType.ExternalStatusPage,
704
+ monitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
705
+ monitorName: SEED_OPTIONS.monitorName,
706
+ })!;
707
+
708
+ expect(onlineCriteria.data!.filters).toEqual(
709
+ onlineDefaults.data!.filters,
710
+ );
711
+ expect(onlineCriteria.data!.filters[0]).toEqual({
712
+ checkOn: CheckOn.ExternalStatusPageIsOnline,
713
+ filterType: FilterType.True,
714
+ value: undefined,
715
+ });
716
+ });
717
+
718
+ test.each(MONITOR_TYPES_WITH_CRITERIA)(
719
+ "no criteria is ever left with no filters at all, aligning to %s",
720
+ (monitorType: MonitorType) => {
721
+ const empty: Array<string> = [];
722
+
723
+ for (const seededFor of sourceTypesFor(monitorType)) {
724
+ const edited: MonitorSteps = stepsSeededFor(seededFor);
725
+ criteriaOf(edited).forEach((instance: MonitorCriteriaInstance) => {
726
+ instance.setName("My own rule");
727
+ });
728
+
729
+ const aligned: MonitorStepsAlignmentResult = alignTo(
730
+ edited,
731
+ monitorType,
732
+ );
733
+
734
+ for (const instance of criteriaOf(aligned.monitorSteps)) {
735
+ if ((instance.data?.filters || []).length === 0) {
736
+ empty.push(
737
+ `seeded for ${seededFor}: "${instance.data!.name}" came back with no filters`,
738
+ );
739
+ }
740
+ }
741
+ }
742
+
743
+ expect(empty).toEqual([]);
744
+ },
745
+ );
746
+
747
+ test.each(MONITOR_TYPES_WITH_CRITERIA)(
748
+ "an incident-raising criteria is never refilled with the up-rules, aligning to %s",
749
+ (monitorType: MonitorType) => {
750
+ /*
751
+ * The inversion, swept over every pair. A criteria that opens an
752
+ * incident and had to be refilled must not come back carrying the
753
+ * rules this monitor type uses to decide it is UP - that is the
754
+ * shape that alerts when all is well.
755
+ *
756
+ * Stated against the online defaults rather than against the
757
+ * offline ones on purpose: "did not end up with the up-rules" is
758
+ * the property that matters and is not a restatement of how the
759
+ * replacement is chosen.
760
+ */
761
+ const onlineDefaults: string = JSON.stringify(
762
+ MonitorCriteriaAlignmentUtil.renderableFilters(
763
+ MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
764
+ monitorType: monitorType,
765
+ monitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
766
+ monitorName: SEED_OPTIONS.monitorName,
767
+ })?.data?.filters || [],
768
+ monitorType,
769
+ ),
770
+ );
771
+
772
+ const offlineDefaults: string = JSON.stringify(
773
+ MonitorCriteriaAlignmentUtil.renderableFilters(
774
+ MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
775
+ monitorType: monitorType,
776
+ monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
777
+ incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
778
+ alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
779
+ monitorName: SEED_OPTIONS.monitorName,
780
+ }).data?.filters || [],
781
+ monitorType,
782
+ ),
783
+ );
784
+
785
+ /*
786
+ * Nothing to get wrong on types whose two halves are the same
787
+ * rules (or which seed only one half).
788
+ */
789
+ if (onlineDefaults === offlineDefaults || onlineDefaults === "[]") {
790
+ return;
791
+ }
792
+
793
+ const inverted: Array<string> = [];
794
+
795
+ for (const seededFor of sourceTypesFor(monitorType)) {
796
+ const edited: MonitorSteps = stepsSeededFor(seededFor);
797
+ criteriaOf(edited).forEach((instance: MonitorCriteriaInstance) => {
798
+ instance.setName("My own rule");
799
+ });
800
+
801
+ const before: Map<string, string> = new Map<string, string>(
802
+ criteriaOf(edited).map((instance: MonitorCriteriaInstance) => {
803
+ return [
804
+ instance.data!.id,
805
+ JSON.stringify(instance.data!.filters),
806
+ ];
807
+ }),
808
+ );
809
+
810
+ const aligned: MonitorStepsAlignmentResult = alignTo(
811
+ edited,
812
+ monitorType,
813
+ );
814
+
815
+ for (const instance of criteriaOf(aligned.monitorSteps)) {
816
+ if (!instance.data?.createIncidents) {
817
+ continue;
818
+ }
819
+
820
+ const after: string = JSON.stringify(instance.data.filters);
821
+
822
+ // Untouched criteria are not refills; nothing was chosen.
823
+ if (before.get(instance.data.id) === after) {
824
+ continue;
825
+ }
826
+
827
+ if (after === onlineDefaults) {
828
+ inverted.push(
829
+ `seeded for ${seededFor}: incident-raising "${instance.data.name}" was refilled with ${monitorType}'s up-rules`,
830
+ );
831
+ }
832
+ }
833
+ }
834
+
835
+ expect(inverted).toEqual([]);
836
+ },
837
+ );
838
+ });
839
+
840
+ describe("the monitor types excluded from the sweeps above", () => {
841
+ /*
842
+ * The exclusion list is only honest while it is true. These two
843
+ * assertions fail if a type on it grows real seeded criteria (at which
844
+ * point it should come off, and the sweeps should cover it) or if a
845
+ * type not on it loses them.
846
+ */
847
+ test.each(MONITOR_TYPES_WITHOUT_SEEDED_CRITERIA)(
848
+ "%s really does seed nothing its own form can draw",
849
+ (monitorType: MonitorType) => {
850
+ expect(
851
+ MonitorCriteriaAlignmentUtil.renderableFilters(
852
+ MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
853
+ monitorType: monitorType,
854
+ monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
855
+ incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
856
+ alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
857
+ monitorName: SEED_OPTIONS.monitorName,
858
+ }).data?.filters || [],
859
+ monitorType,
860
+ ),
861
+ ).toEqual([]);
862
+ },
863
+ );
864
+
865
+ test.each(MONITOR_TYPES_WITH_SEEDED_CRITERIA)(
866
+ "%s really does seed something its own form can draw",
867
+ (monitorType: MonitorType) => {
868
+ expect(
869
+ MonitorCriteriaAlignmentUtil.renderableFilters(
870
+ MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
871
+ monitorType: monitorType,
872
+ monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
873
+ incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
874
+ alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
875
+ monitorName: SEED_OPTIONS.monitorName,
876
+ }).data?.filters || [],
877
+ monitorType,
878
+ ).length,
879
+ ).toBeGreaterThan(0);
880
+ },
881
+ );
882
+
883
+ test("aligning TO one of them still produces something drawable", () => {
884
+ /*
885
+ * The half that does work, and the half this change is responsible
886
+ * for: a Website's criteria arriving on a Docker monitor come back
887
+ * on the metric check Docker offers, not on the placeholder Docker
888
+ * would have seeded for itself.
889
+ */
890
+ const aligned: MonitorStepsAlignmentResult = alignTo(
891
+ stepsSeededFor(MonitorType.Website),
892
+ MonitorType.Docker,
893
+ );
894
+
895
+ expect(aligned.didChange).toBe(true);
896
+ expect(unrenderable(aligned.monitorSteps, MonitorType.Docker)).toEqual(
897
+ [],
898
+ );
899
+ expect(filtersOf(aligned.monitorSteps)[0]!.checkOn).toBe(
900
+ CheckOn.MetricValue,
901
+ );
902
+ });
903
+ });
904
+
905
+ describe("repairCriteriaFilterForMonitorType", () => {
906
+ test("a filter the monitor type can already draw comes back as the very same object", () => {
907
+ const criteriaFilter: CriteriaFilter = {
908
+ checkOn: CheckOn.ResponseTime,
909
+ filterType: FilterType.GreaterThan,
910
+ value: 5000,
911
+ };
912
+
913
+ expect(
914
+ CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
915
+ criteriaFilter: criteriaFilter,
916
+ monitorType: MonitorType.Website,
917
+ }),
918
+ ).toBe(criteriaFilter);
919
+ });
920
+
921
+ test("a check the monitor type still offers keeps its value, only the condition moves", () => {
922
+ /*
923
+ * "Is Online" has never accepted Equal To - criteria stored that way
924
+ * predate the seed being fixed. The check is fine on a Website, so
925
+ * only the condition is replaced.
926
+ */
927
+ const repaired: CriteriaFilter | null =
928
+ CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
929
+ criteriaFilter: {
930
+ checkOn: CheckOn.IsOnline,
931
+ filterType: FilterType.EqualTo,
932
+ value: "keep me",
933
+ },
934
+ monitorType: MonitorType.Website,
935
+ });
936
+
937
+ expect(repaired).not.toBeNull();
938
+ expect(repaired!.checkOn).toBe(CheckOn.IsOnline);
939
+ expect(repaired!.filterType).toBe(FilterType.True);
940
+ expect(repaired!.value).toBe("keep me");
941
+ });
942
+
943
+ test("a check the monitor type does not offer has no repair at all", () => {
944
+ /*
945
+ * Not this monitor type's default filter - null. The value went
946
+ * with the check (3000ms of response time means nothing as a status
947
+ * page component status), and substituting the type's default here
948
+ * would hand the caller a positive, immediately-matching rule the
949
+ * user never wrote. Whether that rule belongs in this criteria
950
+ * depends on the whole criteria, so the caller decides.
951
+ */
952
+ expect(
953
+ CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
954
+ criteriaFilter: {
955
+ checkOn: CheckOn.ResponseTime,
956
+ filterType: FilterType.GreaterThan,
957
+ value: 3000,
958
+ },
959
+ monitorType: MonitorType.ExternalStatusPage,
960
+ }),
961
+ ).toBeNull();
962
+ });
963
+
964
+ test.each(ALL_MONITOR_TYPES)(
965
+ "%s: whatever filter it is handed, what comes back has both dropdowns chosen",
966
+ (monitorType: MonitorType) => {
967
+ const handedIn: Array<CriteriaFilter> = [
968
+ ...ALL_MONITOR_TYPES.flatMap((seededFor: MonitorType) => {
969
+ return filtersOf(stepsSeededFor(seededFor));
970
+ }),
971
+ // Not filters any seed produces: mismatched, blank, and nonsense.
972
+ {
973
+ checkOn: CheckOn.IsOnline,
974
+ filterType: FilterType.EqualTo,
975
+ value: undefined,
976
+ },
977
+ {
978
+ checkOn: CheckOn.MetricValue,
979
+ filterType: undefined,
980
+ value: undefined,
981
+ },
982
+ {
983
+ checkOn: "Not A Real Check" as CheckOn,
984
+ filterType: "Not A Real Filter Type" as FilterType,
985
+ value: undefined,
986
+ },
987
+ ];
988
+
989
+ const offeredChecks: Array<string> = checkOnOptionsFor(monitorType);
990
+ const problems: Array<string> = [];
991
+
992
+ for (const criteriaFilter of handedIn) {
993
+ const repaired: CriteriaFilter | null =
994
+ CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
995
+ criteriaFilter: criteriaFilter,
996
+ monitorType: monitorType,
997
+ });
998
+
999
+ if (!repaired) {
1000
+ // "cannot be expressed" is a valid answer; the caller handles it.
1001
+ continue;
1002
+ }
1003
+
1004
+ if (!offeredChecks.includes(repaired.checkOn?.toString())) {
1005
+ problems.push(
1006
+ `"${criteriaFilter.checkOn}" was repaired to check "${repaired.checkOn}", which ${monitorType} does not offer`,
1007
+ );
1008
+ continue;
1009
+ }
1010
+
1011
+ if (
1012
+ !repaired.filterType ||
1013
+ !filterTypeOptionsFor(repaired.checkOn).includes(
1014
+ repaired.filterType.toString(),
1015
+ )
1016
+ ) {
1017
+ problems.push(
1018
+ `"${criteriaFilter.checkOn}" was repaired to check "${repaired.checkOn}" with condition "${repaired.filterType}", which that check does not offer`,
1019
+ );
1020
+ }
1021
+ }
1022
+
1023
+ expect(problems).toEqual([]);
1024
+ },
1025
+ );
1026
+ });
1027
+
1028
+ describe("isUntouchedDefaultFor", () => {
1029
+ test.each(ALL_MONITOR_TYPES)(
1030
+ "%s recognises its own freshly seeded criteria",
1031
+ (monitorType: MonitorType) => {
1032
+ expect(
1033
+ MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
1034
+ monitorCriteria:
1035
+ stepsSeededFor(monitorType).data!.monitorStepsInstanceArray[0]!
1036
+ .data!.monitorCriteria,
1037
+ monitorType: monitorType,
1038
+ seedOptions: SEED_OPTIONS,
1039
+ }),
1040
+ ).toBe(true);
1041
+ },
1042
+ );
1043
+
1044
+ test("the ids generated fresh for every seed are not mistaken for edits", () => {
1045
+ /*
1046
+ * Each seed generates new ObjectIDs for its criteria, incidents and
1047
+ * alerts, so two seeds of the same type are never byte-identical.
1048
+ * Comparing on those would report every criteria as edited and turn
1049
+ * the re-seed path off entirely.
1050
+ */
1051
+ const first: MonitorSteps = stepsSeededFor(MonitorType.Website);
1052
+ const second: MonitorSteps = stepsSeededFor(MonitorType.Website);
1053
+
1054
+ expect(criteriaOf(first)[0]!.data!.id).not.toBe(
1055
+ criteriaOf(second)[0]!.data!.id,
1056
+ );
1057
+
1058
+ expect(
1059
+ MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
1060
+ monitorCriteria:
1061
+ first.data!.monitorStepsInstanceArray[0]!.data!.monitorCriteria,
1062
+ monitorType: MonitorType.Website,
1063
+ seedOptions: SEED_OPTIONS,
1064
+ }),
1065
+ ).toBe(true);
1066
+ });
1067
+
1068
+ test("a criteria the user renamed is not an untouched default", () => {
1069
+ const monitorSteps: MonitorSteps = stepsSeededFor(MonitorType.Website);
1070
+ criteriaOf(monitorSteps)[0]!.setName("Mine now");
1071
+
1072
+ expect(
1073
+ MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
1074
+ monitorCriteria:
1075
+ monitorSteps.data!.monitorStepsInstanceArray[0]!.data!
1076
+ .monitorCriteria,
1077
+ monitorType: MonitorType.Website,
1078
+ seedOptions: SEED_OPTIONS,
1079
+ }),
1080
+ ).toBe(false);
1081
+ });
1082
+
1083
+ test("criteria that survived a round trip through the form's JSON are still untouched", () => {
1084
+ /*
1085
+ * The criteria the form hands back on mount have been serialized
1086
+ * into the form's value and read out again, which drops keys whose
1087
+ * value is undefined. That is not an edit, and treating it as one
1088
+ * would disable re-seeding on exactly the path the bug arrives by.
1089
+ */
1090
+ const roundTripped: MonitorSteps = MonitorSteps.fromJSON(
1091
+ stepsSeededFor(MonitorType.Website).toJSON(),
1092
+ );
1093
+
1094
+ expect(
1095
+ MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
1096
+ monitorCriteria:
1097
+ roundTripped.data!.monitorStepsInstanceArray[0]!.data!
1098
+ .monitorCriteria,
1099
+ monitorType: MonitorType.Website,
1100
+ seedOptions: SEED_OPTIONS,
1101
+ }),
1102
+ ).toBe(true);
1103
+ });
1104
+
1105
+ test("the monitor name is part of what makes criteria untouched", () => {
1106
+ /*
1107
+ * The seeded names and incident titles quote the monitor's name, so
1108
+ * criteria seeded for one name are not the defaults for another.
1109
+ * Being wrong in this direction only costs a re-seed - the criteria
1110
+ * are repaired instead - which is the safe way to be wrong.
1111
+ */
1112
+ expect(
1113
+ MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
1114
+ monitorCriteria: stepsSeededFor(MonitorType.Website).data!
1115
+ .monitorStepsInstanceArray[0]!.data!.monitorCriteria,
1116
+ monitorType: MonitorType.Website,
1117
+ seedOptions: { ...SEED_OPTIONS, monitorName: "Something else" },
1118
+ }),
1119
+ ).toBe(false);
1120
+ });
1121
+ });
1122
+ });