@oneuptime/common 12.0.23 → 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 (294) hide show
  1. package/Models/DatabaseModels/DetectionRule.ts +55 -0
  2. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
  3. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
  4. package/Models/DatabaseModels/Project.ts +68 -0
  5. package/Models/DatabaseModels/User.ts +68 -0
  6. package/Server/API/TelemetryAPI.ts +29 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  11. package/Server/Middleware/GzipRequestBody.ts +311 -0
  12. package/Server/Middleware/MultipartFormData.ts +87 -3
  13. package/Server/Services/DetectionRuleService.ts +73 -6
  14. package/Server/Services/Index.ts +205 -0
  15. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
  16. package/Server/Services/NetworkDeviceService.ts +148 -0
  17. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  18. package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
  19. package/Server/Services/ProjectService.ts +29 -19
  20. package/Server/Services/SecurityEventService.ts +90 -13
  21. package/Server/Services/StatusPageResourceService.ts +223 -0
  22. package/Server/Services/TelemetryAttributeService.ts +17 -0
  23. package/Server/Services/UserService.ts +4 -6
  24. package/Server/Types/Markdown.ts +161 -67
  25. package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
  26. package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
  27. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
  28. package/Server/Utils/Attribution.ts +4 -0
  29. package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
  30. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
  31. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
  32. package/Server/Utils/StartServer.ts +12 -32
  33. package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
  34. package/Server/Utils/Telemetry/LlmMetricSpend.ts +130 -0
  35. package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
  36. package/Server/Views/Partials/Head.ejs +2 -1
  37. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
  38. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
  39. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
  40. package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
  41. package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
  42. package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
  43. package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
  44. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
  45. package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
  46. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
  47. package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
  48. package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
  49. package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
  50. package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
  51. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
  52. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
  53. package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
  54. package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
  55. package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
  56. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
  57. package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
  58. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
  59. package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
  60. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
  61. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
  62. package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
  63. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
  64. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
  65. package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
  66. package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
  67. package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
  68. package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
  69. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
  70. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
  72. package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
  73. package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
  74. package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
  75. package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
  76. package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
  77. package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
  78. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
  79. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
  80. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
  81. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
  82. package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
  83. package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +368 -4
  84. package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +245 -0
  85. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
  86. package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
  87. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
  88. package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
  89. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
  90. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
  91. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
  92. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
  93. package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +193 -0
  94. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
  95. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
  96. package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
  97. package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
  98. package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
  99. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
  100. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
  101. package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
  102. package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
  103. package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
  104. package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
  105. package/Tests/UI/Components/SideOver.test.tsx +1 -1
  106. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
  107. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
  108. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
  109. package/Tests/UI/Components/Toggle.test.tsx +25 -0
  110. package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
  111. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
  112. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
  113. package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
  114. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
  115. package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +472 -0
  116. package/Types/Code/YamlSyntax.ts +396 -0
  117. package/Types/Exception/ExceptionCode.ts +1 -0
  118. package/Types/Exception/PayloadTooLargeException.ts +8 -0
  119. package/Types/Marketing/Attribution.ts +10 -0
  120. package/Types/Marketing/MarketingEvent.ts +4 -0
  121. package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
  122. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
  123. package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
  124. package/Types/Telemetry/LlmMetricConventions.ts +124 -0
  125. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
  126. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
  127. package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
  128. package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
  129. package/UI/Components/Detail/Detail.tsx +10 -0
  130. package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
  131. package/UI/Components/Forms/Fields/FormField.tsx +31 -0
  132. package/UI/Components/Forms/Types/Field.ts +8 -0
  133. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  134. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
  135. package/UI/Components/Forms/Validation.ts +46 -0
  136. package/UI/Components/Input/Input.tsx +3 -0
  137. package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
  138. package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
  139. package/UI/Components/ModelTable/Column.ts +8 -0
  140. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
  141. package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
  142. package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
  143. package/UI/Components/SideOver/SideOver.tsx +5 -5
  144. package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
  145. package/UI/Components/StatusPage/ResourceList.tsx +197 -38
  146. package/UI/Components/Toggle/Toggle.tsx +9 -1
  147. package/UI/Components/Types/FieldType.ts +1 -0
  148. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
  149. package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
  150. package/Utils/StatusPage/ResourceExplorer.ts +258 -0
  151. package/Utils/Telemetry/LlmMetricQuery.ts +204 -0
  152. package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
  153. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  154. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
  155. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  156. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
  157. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/Project.js +76 -0
  159. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/User.js +76 -0
  161. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  162. package/build/dist/Server/API/TelemetryAPI.js +17 -0
  163. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  164. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
  165. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
  166. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
  167. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
  168. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
  169. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  172. package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
  173. package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
  174. package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
  175. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  176. package/build/dist/Server/Services/DetectionRuleService.js +49 -4
  177. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  178. package/build/dist/Server/Services/Index.js +204 -0
  179. package/build/dist/Server/Services/Index.js.map +1 -1
  180. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
  181. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  182. package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
  183. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  184. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
  185. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  186. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
  187. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
  188. package/build/dist/Server/Services/ProjectService.js +25 -45
  189. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  190. package/build/dist/Server/Services/SecurityEventService.js +57 -5
  191. package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
  192. package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
  193. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  194. package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
  195. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  196. package/build/dist/Server/Services/UserService.js +2 -12
  197. package/build/dist/Server/Services/UserService.js.map +1 -1
  198. package/build/dist/Server/Types/Markdown.js +147 -61
  199. package/build/dist/Server/Types/Markdown.js.map +1 -1
  200. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
  201. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
  202. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
  203. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  204. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
  205. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  206. package/build/dist/Server/Utils/Attribution.js.map +1 -1
  207. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
  208. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
  210. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  211. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
  212. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  213. package/build/dist/Server/Utils/StartServer.js +12 -21
  214. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  215. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
  216. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
  217. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +102 -0
  218. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
  219. package/build/dist/Types/Code/YamlSyntax.js +212 -0
  220. package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
  221. package/build/dist/Types/Exception/ExceptionCode.js +1 -0
  222. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  223. package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
  224. package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
  225. package/build/dist/Types/Marketing/Attribution.js +10 -0
  226. package/build/dist/Types/Marketing/Attribution.js.map +1 -1
  227. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
  228. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  229. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
  230. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  231. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
  232. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  233. package/build/dist/Types/Telemetry/LlmMetricConventions.js +98 -0
  234. package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
  235. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  236. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  237. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
  238. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  239. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
  240. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  241. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
  242. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
  243. package/build/dist/UI/Components/Detail/Detail.js +9 -0
  244. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  245. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
  246. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  247. package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
  248. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  249. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  250. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  251. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  252. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
  253. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
  254. package/build/dist/UI/Components/Forms/Validation.js +27 -0
  255. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  256. package/build/dist/UI/Components/Input/Input.js +1 -1
  257. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  258. package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
  259. package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
  260. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
  261. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  262. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
  263. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  264. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
  265. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
  266. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
  267. package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
  268. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
  269. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  270. package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
  271. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  272. package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
  273. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  274. package/build/dist/UI/Components/Types/FieldType.js +1 -0
  275. package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
  276. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
  277. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
  278. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
  279. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
  280. package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
  281. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
  282. package/build/dist/Utils/Telemetry/LlmMetricQuery.js +119 -0
  283. package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
  284. package/jest.config.json +2 -0
  285. package/package.json +1 -1
  286. package/tsconfig.json +2 -1
  287. package/Tests/UI/Components/Toast.test.tsx +0 -197
  288. package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
  289. package/UI/Components/Toast/Toast.tsx +0 -189
  290. package/UI/Components/Toast/ToastInit.tsx +0 -110
  291. package/build/dist/UI/Components/Toast/Toast.js +0 -123
  292. package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
  293. package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
  294. package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
@@ -0,0 +1,598 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import FilterCondition from "../../../Types/Filter/FilterCondition";
3
+ import { CriteriaAlert } from "../../../Types/Monitor/CriteriaAlert";
4
+ import { CheckOn, FilterType } from "../../../Types/Monitor/CriteriaFilter";
5
+ import { CriteriaIncident } from "../../../Types/Monitor/CriteriaIncident";
6
+ import MonitorCriteria from "../../../Types/Monitor/MonitorCriteria";
7
+ import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
8
+ import MonitorStep from "../../../Types/Monitor/MonitorStep";
9
+ import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
10
+ import MonitorType from "../../../Types/Monitor/MonitorType";
11
+ import ObjectID from "../../../Types/ObjectID";
12
+ import { JSONObject } from "../../../Types/JSON";
13
+
14
+ /*
15
+ * Issues #3410 and #3413: "Alert title is required for criteria X" / "Incident
16
+ * title is required for criteria X" blocked Next on the monitor criteria step
17
+ * with the matching switch turned OFF, and there was no way out of it.
18
+ *
19
+ * The mechanism was a mismatch between three layers that all describe the same
20
+ * thing:
21
+ *
22
+ * - the form seeds a blank incident / alert row when you flip a switch on,
23
+ * and deliberately keeps that row when you flip it back off so your typing
24
+ * survives a mis-click;
25
+ * - the evaluator ignores those rows entirely while the flag is off
26
+ * (MonitorIncident / MonitorAlert both return early on a falsy flag);
27
+ * - getValidationError walked both arrays unconditionally.
28
+ *
29
+ * So the form demanded a title for a row that does nothing, and rendered the
30
+ * field to type it into only while the switch was on. Turning the switch back
31
+ * on to clear the error re-armed an action the user did not want.
32
+ *
33
+ * This file pins the fixed contract at every level of the chain, because the
34
+ * error the user sees is produced four levels above where the decision is made
35
+ * (MonitorSteps -> MonitorStep -> MonitorCriteria -> MonitorCriteriaInstance)
36
+ * and only the top level is what the form actually calls.
37
+ */
38
+
39
+ const MONITOR_STATUS_ID: ObjectID = new ObjectID(
40
+ "11111111-1111-4111-8111-111111111111",
41
+ );
42
+ const INCIDENT_SEVERITY_ID: ObjectID = new ObjectID(
43
+ "22222222-2222-4222-8222-222222222222",
44
+ );
45
+ const ALERT_SEVERITY_ID: ObjectID = new ObjectID(
46
+ "33333333-3333-4333-8333-333333333333",
47
+ );
48
+
49
+ // The row the criteria form seeds the moment a switch is flipped on.
50
+ function blankIncident(): CriteriaIncident {
51
+ return {
52
+ title: "",
53
+ description: "",
54
+ incidentSeverityId: undefined,
55
+ id: ObjectID.generate().toString(),
56
+ };
57
+ }
58
+
59
+ function blankAlert(): CriteriaAlert {
60
+ return {
61
+ title: "",
62
+ description: "",
63
+ alertSeverityId: undefined,
64
+ id: ObjectID.generate().toString(),
65
+ };
66
+ }
67
+
68
+ function filledIncident(): CriteriaIncident {
69
+ return {
70
+ title: "AWS Status Page has an active incident",
71
+ description: "",
72
+ incidentSeverityId: INCIDENT_SEVERITY_ID,
73
+ id: ObjectID.generate().toString(),
74
+ };
75
+ }
76
+
77
+ function filledAlert(): CriteriaAlert {
78
+ return {
79
+ title: "UND66DWANRTR01 is offline",
80
+ description: "",
81
+ alertSeverityId: ALERT_SEVERITY_ID,
82
+ id: ObjectID.generate().toString(),
83
+ };
84
+ }
85
+
86
+ /*
87
+ * A criteria in the state the reporter's screenshot shows: only "change
88
+ * monitor status" is on, and the two other actions are off.
89
+ */
90
+ function buildCriteria(
91
+ overrides?: Partial<MonitorCriteriaInstance["data"]>,
92
+ ): MonitorCriteriaInstance {
93
+ const instance: MonitorCriteriaInstance = new MonitorCriteriaInstance();
94
+ instance.data = {
95
+ id: ObjectID.generate().toString(),
96
+ monitorStatusId: MONITOR_STATUS_ID,
97
+ filterCondition: FilterCondition.All,
98
+ filters: [
99
+ {
100
+ checkOn: CheckOn.IsOnline,
101
+ filterType: FilterType.True,
102
+ value: undefined,
103
+ },
104
+ ],
105
+ incidents: [],
106
+ alerts: [],
107
+ changeMonitorStatus: true,
108
+ createIncidents: false,
109
+ createAlerts: false,
110
+ isEnabled: true,
111
+ name: "Check if AWS Status Page is operational",
112
+ description: "Checks whether the external status page is healthy",
113
+ ...(overrides || {}),
114
+ };
115
+ return instance;
116
+ }
117
+
118
+ function validate(instance: MonitorCriteriaInstance): string | null {
119
+ return MonitorCriteriaInstance.getValidationError(instance, MonitorType.Ping);
120
+ }
121
+
122
+ describe("MonitorCriteriaInstance.getValidationError - per-action gating", () => {
123
+ describe("declare an incident (createIncidents)", () => {
124
+ test("a blank incident row left behind by the switch does not block while the switch is off", () => {
125
+ const instance: MonitorCriteriaInstance = buildCriteria({
126
+ createIncidents: false,
127
+ incidents: [blankIncident()],
128
+ });
129
+
130
+ expect(validate(instance)).toBeNull();
131
+ });
132
+
133
+ test("the same blank row does block once the switch is on", () => {
134
+ const instance: MonitorCriteriaInstance = buildCriteria({
135
+ createIncidents: true,
136
+ incidents: [blankIncident()],
137
+ });
138
+
139
+ expect(validate(instance)).toBe(
140
+ 'Incident title is required for criteria "Check if AWS Status Page is operational"',
141
+ );
142
+ });
143
+
144
+ test("a missing severity blocks only while the switch is on", () => {
145
+ const row: CriteriaIncident = {
146
+ ...filledIncident(),
147
+ incidentSeverityId: undefined,
148
+ };
149
+
150
+ expect(
151
+ validate(buildCriteria({ createIncidents: false, incidents: [row] })),
152
+ ).toBeNull();
153
+ expect(
154
+ validate(buildCriteria({ createIncidents: true, incidents: [row] })),
155
+ ).toBe(
156
+ 'Incident severity is required for criteria "Check if AWS Status Page is operational"',
157
+ );
158
+ });
159
+
160
+ test("a fully filled incident passes with the switch on", () => {
161
+ expect(
162
+ validate(
163
+ buildCriteria({
164
+ createIncidents: true,
165
+ incidents: [filledIncident()],
166
+ }),
167
+ ),
168
+ ).toBeNull();
169
+ });
170
+ });
171
+
172
+ describe("the two actions are independent", () => {
173
+ /*
174
+ * The recommendation flow ships exactly this shape: both payloads
175
+ * authored, one flag on. Whichever side is off must not be able to block
176
+ * the other, in either direction.
177
+ */
178
+ test("incidents on and valid, alerts off and blank", () => {
179
+ expect(
180
+ validate(
181
+ buildCriteria({
182
+ createIncidents: true,
183
+ incidents: [filledIncident()],
184
+ createAlerts: false,
185
+ alerts: [blankAlert()],
186
+ }),
187
+ ),
188
+ ).toBeNull();
189
+ });
190
+
191
+ test("alerts on and valid, incidents off and blank", () => {
192
+ expect(
193
+ validate(
194
+ buildCriteria({
195
+ createAlerts: true,
196
+ alerts: [filledAlert()],
197
+ createIncidents: false,
198
+ incidents: [blankIncident()],
199
+ }),
200
+ ),
201
+ ).toBeNull();
202
+ });
203
+
204
+ test("both on and both blank reports the incident first", () => {
205
+ expect(
206
+ validate(
207
+ buildCriteria({
208
+ createIncidents: true,
209
+ incidents: [blankIncident()],
210
+ createAlerts: true,
211
+ alerts: [blankAlert()],
212
+ }),
213
+ ),
214
+ ).toBe(
215
+ 'Incident title is required for criteria "Check if AWS Status Page is operational"',
216
+ );
217
+ });
218
+ });
219
+
220
+ describe("create an alert (createAlerts)", () => {
221
+ test("a blank alert row left behind by the switch does not block while the switch is off", () => {
222
+ const instance: MonitorCriteriaInstance = buildCriteria({
223
+ createAlerts: false,
224
+ alerts: [blankAlert()],
225
+ });
226
+
227
+ expect(validate(instance)).toBeNull();
228
+ });
229
+
230
+ test("the same blank row does block once the switch is on", () => {
231
+ const instance: MonitorCriteriaInstance = buildCriteria({
232
+ createAlerts: true,
233
+ alerts: [blankAlert()],
234
+ });
235
+
236
+ expect(validate(instance)).toBe(
237
+ 'Alert title is required for criteria "Check if AWS Status Page is operational"',
238
+ );
239
+ });
240
+
241
+ test("typing the title clears the error - the whole of #3410 step 3", () => {
242
+ const instance: MonitorCriteriaInstance = buildCriteria({
243
+ createAlerts: true,
244
+ alerts: [blankAlert()],
245
+ });
246
+ expect(validate(instance)).toContain("Alert title is required");
247
+
248
+ instance.data!.alerts = [
249
+ { ...instance.data!.alerts[0]!, title: "UND66DWANRTR01 is offline" },
250
+ ];
251
+ expect(validate(instance)).toContain("Alert severity is required");
252
+
253
+ instance.data!.alerts = [
254
+ { ...instance.data!.alerts[0]!, alertSeverityId: ALERT_SEVERITY_ID },
255
+ ];
256
+ expect(validate(instance)).toBeNull();
257
+ });
258
+
259
+ test("a missing severity blocks only while the switch is on", () => {
260
+ const row: CriteriaAlert = {
261
+ ...filledAlert(),
262
+ alertSeverityId: undefined,
263
+ };
264
+
265
+ expect(
266
+ validate(buildCriteria({ createAlerts: false, alerts: [row] })),
267
+ ).toBeNull();
268
+ expect(
269
+ validate(buildCriteria({ createAlerts: true, alerts: [row] })),
270
+ ).toBe(
271
+ 'Alert severity is required for criteria "Check if AWS Status Page is operational"',
272
+ );
273
+ });
274
+ });
275
+
276
+ describe("description is optional", () => {
277
+ /*
278
+ * The criteria form marks Title and Severity `required`; the description
279
+ * sits in a collapsed section labelled "Optional incident description" /
280
+ * "Optional alert description", and Incident.description and
281
+ * Alert.description are both nullable columns. Requiring it here left the
282
+ * form stuck on a field the UI calls optional and folds out of sight,
283
+ * which is why #3410 reads "permanently stuck with no way to proceed".
284
+ */
285
+ test("an incident with a title and a severity passes with no description", () => {
286
+ expect(
287
+ validate(
288
+ buildCriteria({
289
+ createIncidents: true,
290
+ incidents: [{ ...filledIncident(), description: "" }],
291
+ }),
292
+ ),
293
+ ).toBeNull();
294
+ });
295
+
296
+ test("an alert with a title and a severity passes with no description", () => {
297
+ expect(
298
+ validate(
299
+ buildCriteria({
300
+ createAlerts: true,
301
+ alerts: [{ ...filledAlert(), description: "" }],
302
+ }),
303
+ ),
304
+ ).toBeNull();
305
+ });
306
+
307
+ test("an absent description is accepted too", () => {
308
+ /*
309
+ * CriteriaAlert types description as a string, but nothing enforces
310
+ * that on the way in from the REST API, so the absent case is reachable
311
+ * with a payload authored outside the form.
312
+ */
313
+ const row: CriteriaAlert = {
314
+ ...filledAlert(),
315
+ description: undefined as unknown as string,
316
+ };
317
+
318
+ expect(
319
+ validate(buildCriteria({ createAlerts: true, alerts: [row] })),
320
+ ).toBeNull();
321
+ });
322
+ });
323
+
324
+ describe("every monitor type", () => {
325
+ /*
326
+ * #3410 reports this "affects most/all Monitor types during creation".
327
+ * The gate lives above the per-type filter rules, so it should hold for
328
+ * every type in the enum - driven from Object.values so a new monitor
329
+ * type is covered the day it is added. IsOnline/True is the one filter
330
+ * shape no type rejects.
331
+ */
332
+ const ALL_MONITOR_TYPES: Array<MonitorType> = Object.values(MonitorType);
333
+
334
+ test("the enum is not empty, so the loops below mean something", () => {
335
+ expect(ALL_MONITOR_TYPES.length).toBeGreaterThan(10);
336
+ });
337
+
338
+ test("a switched-off action never blocks, whatever the monitor type", () => {
339
+ for (const monitorType of ALL_MONITOR_TYPES) {
340
+ const instance: MonitorCriteriaInstance = buildCriteria({
341
+ createIncidents: false,
342
+ incidents: [blankIncident()],
343
+ createAlerts: false,
344
+ alerts: [blankAlert()],
345
+ });
346
+
347
+ expect(
348
+ MonitorCriteriaInstance.getValidationError(instance, monitorType),
349
+ ).toBeNull();
350
+ }
351
+ });
352
+
353
+ test("a switched-on action still blocks, whatever the monitor type", () => {
354
+ for (const monitorType of ALL_MONITOR_TYPES) {
355
+ const instance: MonitorCriteriaInstance = buildCriteria({
356
+ createAlerts: true,
357
+ alerts: [blankAlert()],
358
+ });
359
+
360
+ expect(
361
+ MonitorCriteriaInstance.getValidationError(instance, monitorType),
362
+ ).toContain("Alert title is required");
363
+ }
364
+ });
365
+ });
366
+
367
+ describe("checks that are not action-gated still run", () => {
368
+ test("the criteria's own name and description are still required", () => {
369
+ expect(validate(buildCriteria({ name: "" }))).toContain(
370
+ "Name is required",
371
+ );
372
+ expect(validate(buildCriteria({ description: "" }))).toContain(
373
+ "Description is required",
374
+ );
375
+ });
376
+
377
+ test("filters are still required and still type-checked", () => {
378
+ expect(validate(buildCriteria({ filters: [] }))).toContain(
379
+ "Filter is required",
380
+ );
381
+ expect(
382
+ MonitorCriteriaInstance.getValidationError(
383
+ buildCriteria({
384
+ filters: [
385
+ {
386
+ checkOn: CheckOn.DiskUsagePercent,
387
+ filterType: FilterType.GreaterThan,
388
+ value: 90,
389
+ },
390
+ ],
391
+ }),
392
+ MonitorType.Ping,
393
+ ),
394
+ ).toContain("Ping Monitor cannot have filter type");
395
+ });
396
+
397
+ test("an empty array with the switch on is not an error", () => {
398
+ /*
399
+ * NetworkDeviceAlertPackUtil.buildCriteriaInstances ships exactly this:
400
+ * createIncidents / createAlerts on with empty arrays, for the user to
401
+ * fill in. Requiring a row here would break "Add Recommended Alerts".
402
+ */
403
+ expect(
404
+ validate(
405
+ buildCriteria({
406
+ createIncidents: true,
407
+ incidents: [],
408
+ createAlerts: true,
409
+ alerts: [],
410
+ }),
411
+ ),
412
+ ).toBeNull();
413
+ });
414
+
415
+ test("a null row inside a populated array is still skipped", () => {
416
+ const instance: MonitorCriteriaInstance = buildCriteria({
417
+ createAlerts: true,
418
+ alerts: [
419
+ undefined as unknown as CriteriaAlert,
420
+ filledAlert(),
421
+ ] as Array<CriteriaAlert>,
422
+ });
423
+
424
+ expect(validate(instance)).toBeNull();
425
+ });
426
+ });
427
+
428
+ describe("flag provenance", () => {
429
+ test("a criteria saved before the flags existed is treated as off, exactly as the evaluator treats it", () => {
430
+ /*
431
+ * The predicate is the falsy one (`!data.createAlerts`), matching
432
+ * MonitorAlert / MonitorIncident. A row written before these flags
433
+ * shipped deserialises with the flag absent -> false, so it creates
434
+ * nothing at runtime; validating it would block a save over a payload
435
+ * that does nothing.
436
+ */
437
+ const json: JSONObject = buildCriteria({
438
+ alerts: [blankAlert()],
439
+ }).toJSON();
440
+ delete (json["value"] as JSONObject)["createAlerts"];
441
+ delete (json["value"] as JSONObject)["createIncidents"];
442
+
443
+ const restored: MonitorCriteriaInstance =
444
+ MonitorCriteriaInstance.fromJSON(json);
445
+
446
+ expect(restored.data?.createAlerts).toBe(false);
447
+ expect(validate(restored)).toBeNull();
448
+ });
449
+
450
+ test("the gate survives a toJSON / fromJSON round-trip in both directions", () => {
451
+ const on: MonitorCriteriaInstance = MonitorCriteriaInstance.fromJSON(
452
+ buildCriteria({
453
+ createAlerts: true,
454
+ alerts: [blankAlert()],
455
+ }).toJSON(),
456
+ );
457
+ expect(validate(on)).toContain("Alert title is required");
458
+
459
+ const off: MonitorCriteriaInstance = MonitorCriteriaInstance.fromJSON(
460
+ buildCriteria({
461
+ createAlerts: false,
462
+ alerts: [blankAlert()],
463
+ }).toJSON(),
464
+ );
465
+ expect(validate(off)).toBeNull();
466
+ });
467
+
468
+ test("clone() carries the gate", () => {
469
+ const off: MonitorCriteriaInstance = MonitorCriteriaInstance.clone(
470
+ buildCriteria({ createIncidents: false, incidents: [blankIncident()] }),
471
+ );
472
+ expect(validate(off)).toBeNull();
473
+ });
474
+
475
+ test("the setters flip the gate", () => {
476
+ const instance: MonitorCriteriaInstance = buildCriteria({
477
+ alerts: [blankAlert()],
478
+ });
479
+
480
+ instance.setCreateAlerts(true);
481
+ expect(validate(instance)).toContain("Alert title is required");
482
+
483
+ instance.setCreateAlerts(false);
484
+ expect(validate(instance)).toBeNull();
485
+ });
486
+ });
487
+ });
488
+
489
+ /*
490
+ * The form never calls MonitorCriteriaInstance.getValidationError directly -
491
+ * it calls MonitorSteps.getValidationError, which walks down through
492
+ * MonitorStep and MonitorCriteria. A gate that worked only at the bottom
493
+ * level would still block Next, so each level is pinned here.
494
+ */
495
+ describe("the gate reaches the error the form actually shows", () => {
496
+ function stepsWith(instance: MonitorCriteriaInstance): MonitorSteps {
497
+ const criteria: MonitorCriteria = new MonitorCriteria();
498
+ criteria.data = { monitorCriteriaInstanceArray: [instance] };
499
+
500
+ const step: MonitorStep = new MonitorStep();
501
+ step.data = {
502
+ ...step.data!,
503
+ monitorDestination: undefined,
504
+ monitorCriteria: criteria,
505
+ };
506
+
507
+ const steps: MonitorSteps = new MonitorSteps();
508
+ steps.data = {
509
+ monitorStepsInstanceArray: [step],
510
+ defaultMonitorStatusId: MONITOR_STATUS_ID,
511
+ };
512
+ return steps;
513
+ }
514
+
515
+ test("MonitorCriteria.getValidationError respects the switch", () => {
516
+ const criteria: MonitorCriteria = new MonitorCriteria();
517
+ criteria.data = {
518
+ monitorCriteriaInstanceArray: [
519
+ buildCriteria({ createIncidents: false, incidents: [blankIncident()] }),
520
+ ],
521
+ };
522
+
523
+ expect(
524
+ MonitorCriteria.getValidationError(criteria, MonitorType.Manual),
525
+ ).toBeNull();
526
+
527
+ criteria.data.monitorCriteriaInstanceArray[0]!.setCreateIncidents(true);
528
+ expect(
529
+ MonitorCriteria.getValidationError(criteria, MonitorType.Manual),
530
+ ).toContain("Incident title is required");
531
+ });
532
+
533
+ test("MonitorStep.getValidationError respects the switch", () => {
534
+ const offInstance: MonitorCriteriaInstance = buildCriteria({
535
+ createAlerts: false,
536
+ alerts: [blankAlert()],
537
+ });
538
+ const step: MonitorStep =
539
+ stepsWith(offInstance).data!.monitorStepsInstanceArray[0]!;
540
+
541
+ expect(MonitorStep.getValidationError(step, MonitorType.Manual)).toBeNull();
542
+
543
+ offInstance.setCreateAlerts(true);
544
+ expect(MonitorStep.getValidationError(step, MonitorType.Manual)).toContain(
545
+ "Alert title is required",
546
+ );
547
+ });
548
+
549
+ test("MonitorSteps.getValidationError - the exact call the create form makes - respects the switch", () => {
550
+ const instance: MonitorCriteriaInstance = buildCriteria({
551
+ createIncidents: false,
552
+ incidents: [blankIncident()],
553
+ createAlerts: false,
554
+ alerts: [blankAlert()],
555
+ });
556
+
557
+ const steps: MonitorSteps = stepsWith(instance);
558
+
559
+ expect(
560
+ MonitorSteps.getValidationError(steps, MonitorType.Manual),
561
+ ).toBeNull();
562
+
563
+ /*
564
+ * And the other direction from the same object, so this pins the gate
565
+ * rather than just "these steps happen to be valid".
566
+ */
567
+ instance.setCreateIncidents(true);
568
+ expect(MonitorSteps.getValidationError(steps, MonitorType.Manual)).toBe(
569
+ 'Incident title is required for criteria "Check if AWS Status Page is operational"',
570
+ );
571
+
572
+ instance.setCreateIncidents(false);
573
+ instance.setCreateAlerts(true);
574
+ expect(MonitorSteps.getValidationError(steps, MonitorType.Manual)).toBe(
575
+ 'Alert title is required for criteria "Check if AWS Status Page is operational"',
576
+ );
577
+ });
578
+
579
+ test("one criteria with its switch on still blocks the whole form", () => {
580
+ const criteria: MonitorCriteria = new MonitorCriteria();
581
+ criteria.data = {
582
+ monitorCriteriaInstanceArray: [
583
+ buildCriteria({ createAlerts: false, alerts: [blankAlert()] }),
584
+ buildCriteria({
585
+ name: "Check if AWS Status Page is down",
586
+ createIncidents: true,
587
+ incidents: [blankIncident()],
588
+ }),
589
+ ],
590
+ };
591
+
592
+ expect(
593
+ MonitorCriteria.getValidationError(criteria, MonitorType.Manual),
594
+ ).toBe(
595
+ 'Incident title is required for criteria "Check if AWS Status Page is down"',
596
+ );
597
+ });
598
+ });
@@ -58,6 +58,14 @@ describe("MonitorCriteriaInstance", () => {
58
58
  expect(instance.data?.filterCondition).toBe(FilterCondition.All);
59
59
  expect(instance.data?.filters).toHaveLength(1);
60
60
  expect(instance.data?.filters[0]?.checkOn).toBe(CheckOn.IsOnline);
61
+ /*
62
+ * The condition is seeded too. A filter carrying only a check shows
63
+ * an empty "Filter Condition" dropdown in the criteria form, and is
64
+ * dead on the server - every comparator in CompareCriteria switches
65
+ * on the filter type and treats one it does not recognise as "no
66
+ * match", so the criteria never fires. See #3412.
67
+ */
68
+ expect(instance.data?.filters[0]?.filterType).toBe(FilterType.True);
61
69
  expect(instance.data?.isEnabled).toBe(true);
62
70
  // id should be a valid, non-empty ObjectID string.
63
71
  expect(typeof instance.data?.id).toBe("string");
@@ -268,6 +276,7 @@ describe("MonitorCriteriaInstance", () => {
268
276
 
269
277
  test("returns error when an incident is missing its severity", () => {
270
278
  const instance: MonitorCriteriaInstance = buildValidInstance();
279
+ instance.data!.createIncidents = true;
271
280
  instance.data!.incidents = [
272
281
  {
273
282
  title: "Down",
@@ -286,6 +295,7 @@ describe("MonitorCriteriaInstance", () => {
286
295
 
287
296
  test("returns error when an alert is missing its title", () => {
288
297
  const instance: MonitorCriteriaInstance = buildValidInstance();
298
+ instance.data!.createAlerts = true;
289
299
  instance.data!.alerts = [
290
300
  {
291
301
  title: "",
@@ -350,6 +360,45 @@ describe("MonitorCriteriaInstance", () => {
350
360
  expect(error).toContain("Value is required");
351
361
  });
352
362
 
363
+ test("returns error when a filter has no condition", () => {
364
+ /*
365
+ * The blank "Filter Condition" dropdown reported in #3412 sat next to
366
+ * fields that were all filled in, so it was easy to save past. A
367
+ * criteria with no condition can never match, so refuse it rather
368
+ * than accept a rule that silently does nothing.
369
+ */
370
+ const instance: MonitorCriteriaInstance = buildValidInstance();
371
+ instance.data!.filters = [
372
+ {
373
+ checkOn: CheckOn.ExternalStatusPageActiveIncidents,
374
+ filterType: undefined,
375
+ value: 0,
376
+ },
377
+ ];
378
+ const error: string | null = MonitorCriteriaInstance.getValidationError(
379
+ instance,
380
+ MonitorType.ExternalStatusPage,
381
+ );
382
+ expect(error).toContain("Filter Condition is required");
383
+ expect(error).toContain(CheckOn.ExternalStatusPageActiveIncidents);
384
+ });
385
+
386
+ test("accepts the criteria an External Status Page monitor is created with", () => {
387
+ const instance: MonitorCriteriaInstance =
388
+ MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
389
+ monitorType: MonitorType.ExternalStatusPage,
390
+ monitorStatusId: new ObjectID("aaaaaaaaaaaaaaaaaaaaaaaa"),
391
+ monitorName: "Acme Status",
392
+ })!;
393
+
394
+ expect(
395
+ MonitorCriteriaInstance.getValidationError(
396
+ instance,
397
+ MonitorType.ExternalStatusPage,
398
+ ),
399
+ ).toBeNull();
400
+ });
401
+
353
402
  test("returns null for a fully valid instance", () => {
354
403
  const instance: MonitorCriteriaInstance = buildValidInstance();
355
404
  expect(
@@ -540,12 +540,12 @@ describe("MonitorRecommendationNotificationMode", () => {
540
540
  it("still passes MonitorSteps.getValidationError under every mode, for every recommendation", () => {
541
541
  /*
542
542
  * `MonitorSteps.getValidationError` is the same client-side gate the
543
- * monitor create form runs before POSTing, and it validates the incident
544
- * and alert payloads regardless of whether their create flag is on. So a
545
- * mode implementation that "disabled" a side by blanking a title or a
546
- * severity id instead of clearing the flag would fail here — at submit
547
- * time in production, with a message about a field the user was never
548
- * shown, and only for the mode they happened to pick.
543
+ * monitor create form runs before POSTing. It validates the incident and
544
+ * alert payloads of whichever side the mode leaves switched on, so a
545
+ * mode implementation that blanked a title or a severity id on the side
546
+ * it kept enabled would fail here — at submit time in production, with a
547
+ * message about a field the user was never shown, and only for the mode
548
+ * they happened to pick.
549
549
  */
550
550
  for (const recommendation of ALL_RECOMMENDATIONS) {
551
551
  for (const notificationMode of ALL_MODES) {