@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,346 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import SecurityEvent from "../../../Models/AnalyticsModels/SecurityEvent";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+ import OcsfSeverity from "../../../Types/SecurityEvent/OcsfSeverity";
5
+ import {
6
+ CENTER_NODE_ID,
7
+ CLASS_NODE_PREFIX,
8
+ CorrelationGraphData,
9
+ CorrelationGraphEdge,
10
+ CorrelationGraphNode,
11
+ OBSERVABLE_NODE_PREFIX,
12
+ buildCorrelationGraph,
13
+ dedupeSecurityEvents,
14
+ severityRank,
15
+ } from "../../../../App/FeatureSet/Dashboard/src/Utils/CorrelationGraph";
16
+
17
+ /*
18
+ * The correlate graph builder aggregates fetched events into the
19
+ * center/class/observable neighborhood: event counts per class, worst
20
+ * severity per class (for node tinting), co-observable counting with
21
+ * per-event dedupe, exclusion of the searched values, the top-N cap with
22
+ * honest drop accounting, and the union-dedupe used when OR mode runs one
23
+ * query per condition. All pinned here as pure data — no React, no
24
+ * ReactFlow.
25
+ */
26
+
27
+ interface EventInput {
28
+ id?: string;
29
+ eventUid?: string;
30
+ className?: string;
31
+ severityName?: OcsfSeverity;
32
+ observables?: Array<string>;
33
+ time?: Date;
34
+ }
35
+
36
+ function buildEvent(input: EventInput): SecurityEvent {
37
+ const event: SecurityEvent = new SecurityEvent();
38
+ if (input.id) {
39
+ event._id = new ObjectID(input.id);
40
+ }
41
+ if (input.eventUid) {
42
+ event.eventUid = input.eventUid;
43
+ }
44
+ if (input.className) {
45
+ event.className = input.className;
46
+ }
47
+ if (input.severityName) {
48
+ event.severityName = input.severityName;
49
+ }
50
+ event.observables = input.observables || [];
51
+ if (input.time) {
52
+ event.time = input.time;
53
+ }
54
+ return event;
55
+ }
56
+
57
+ function nodeById(
58
+ graph: CorrelationGraphData,
59
+ id: string,
60
+ ): CorrelationGraphNode | undefined {
61
+ return graph.nodes.find((node: CorrelationGraphNode) => {
62
+ return node.id === id;
63
+ });
64
+ }
65
+
66
+ function edgeById(
67
+ graph: CorrelationGraphData,
68
+ id: string,
69
+ ): CorrelationGraphEdge | undefined {
70
+ return graph.edges.find((edge: CorrelationGraphEdge) => {
71
+ return edge.id === id;
72
+ });
73
+ }
74
+
75
+ describe("severityRank", () => {
76
+ test("orders real severities and puts Unknown/Other/blank at the bottom", () => {
77
+ expect(severityRank(OcsfSeverity.Fatal)).toBeGreaterThan(
78
+ severityRank(OcsfSeverity.Critical),
79
+ );
80
+ expect(severityRank(OcsfSeverity.Critical)).toBeGreaterThan(
81
+ severityRank(OcsfSeverity.High),
82
+ );
83
+ expect(severityRank(OcsfSeverity.High)).toBeGreaterThan(
84
+ severityRank(OcsfSeverity.Medium),
85
+ );
86
+ expect(severityRank(OcsfSeverity.Medium)).toBeGreaterThan(
87
+ severityRank(OcsfSeverity.Low),
88
+ );
89
+ expect(severityRank(OcsfSeverity.Low)).toBeGreaterThan(
90
+ severityRank(OcsfSeverity.Informational),
91
+ );
92
+ expect(severityRank(OcsfSeverity.Unknown)).toBe(0);
93
+ expect(severityRank(OcsfSeverity.Other)).toBe(0);
94
+ expect(severityRank(undefined)).toBe(0);
95
+ expect(severityRank("")).toBe(0);
96
+ });
97
+ });
98
+
99
+ describe("buildCorrelationGraph", () => {
100
+ test("no events → empty graph", () => {
101
+ const graph: CorrelationGraphData = buildCorrelationGraph({
102
+ events: [],
103
+ centerLabel: 'Observable is "x"',
104
+ excludedObservables: ["x"],
105
+ });
106
+ expect(graph.nodes).toHaveLength(0);
107
+ expect(graph.edges).toHaveLength(0);
108
+ expect(graph.droppedCoObservableCount).toBe(0);
109
+ });
110
+
111
+ test("one node per class with event counts on the center edges", () => {
112
+ const graph: CorrelationGraphData = buildCorrelationGraph({
113
+ events: [
114
+ buildEvent({ className: "Authentication", observables: ["host-1"] }),
115
+ buildEvent({ className: "Authentication", observables: ["host-1"] }),
116
+ buildEvent({ className: "Process Activity", observables: ["host-1"] }),
117
+ buildEvent({ observables: ["host-1"] }), // no class → Unclassified
118
+ ],
119
+ centerLabel: "center-label",
120
+ excludedObservables: ["host-1"],
121
+ });
122
+
123
+ const center: CorrelationGraphNode | undefined = nodeById(
124
+ graph,
125
+ CENTER_NODE_ID,
126
+ );
127
+ expect(center).toBeTruthy();
128
+ expect(center?.label).toBe("center-label");
129
+ expect(center?.kind).toBe("center");
130
+
131
+ const authClass: CorrelationGraphNode | undefined = nodeById(
132
+ graph,
133
+ `${CLASS_NODE_PREFIX}Authentication`,
134
+ );
135
+ expect(authClass?.count).toBe(2);
136
+ expect(
137
+ edgeById(graph, `${CENTER_NODE_ID}->${CLASS_NODE_PREFIX}Authentication`)
138
+ ?.count,
139
+ ).toBe(2);
140
+
141
+ expect(nodeById(graph, `${CLASS_NODE_PREFIX}Unclassified`)?.count).toBe(1);
142
+ expect(nodeById(graph, `${CLASS_NODE_PREFIX}Process Activity`)?.count).toBe(
143
+ 1,
144
+ );
145
+
146
+ // Excluded observable never becomes a node.
147
+ expect(nodeById(graph, `${OBSERVABLE_NODE_PREFIX}host-1`)).toBeUndefined();
148
+ });
149
+
150
+ test("class nodes carry the WORST severity among their events", () => {
151
+ const graph: CorrelationGraphData = buildCorrelationGraph({
152
+ events: [
153
+ buildEvent({
154
+ className: "Authentication",
155
+ severityName: OcsfSeverity.Low,
156
+ }),
157
+ buildEvent({
158
+ className: "Authentication",
159
+ severityName: OcsfSeverity.Critical,
160
+ }),
161
+ buildEvent({
162
+ className: "Authentication",
163
+ severityName: OcsfSeverity.Medium,
164
+ }),
165
+ buildEvent({
166
+ className: "Compliance Finding",
167
+ severityName: OcsfSeverity.Unknown,
168
+ }),
169
+ ],
170
+ centerLabel: "c",
171
+ excludedObservables: [],
172
+ });
173
+
174
+ expect(
175
+ nodeById(graph, `${CLASS_NODE_PREFIX}Authentication`)?.worstSeverity,
176
+ ).toBe(OcsfSeverity.Critical);
177
+ /*
178
+ * Unknown ranks zero — the node gets NO severity rather than
179
+ * pretending "Unknown" is a color-worthy signal.
180
+ */
181
+ expect(
182
+ nodeById(graph, `${CLASS_NODE_PREFIX}Compliance Finding`)?.worstSeverity,
183
+ ).toBeUndefined();
184
+ });
185
+
186
+ test("co-observables count once per event even when repeated in one row", () => {
187
+ const graph: CorrelationGraphData = buildCorrelationGraph({
188
+ events: [
189
+ buildEvent({
190
+ className: "Authentication",
191
+ observables: ["searched", "alice", "alice", "alice"],
192
+ }),
193
+ buildEvent({
194
+ className: "Authentication",
195
+ observables: ["searched", "alice"],
196
+ }),
197
+ ],
198
+ centerLabel: "c",
199
+ excludedObservables: ["searched"],
200
+ });
201
+
202
+ expect(
203
+ edgeById(
204
+ graph,
205
+ `${CLASS_NODE_PREFIX}Authentication->${OBSERVABLE_NODE_PREFIX}alice`,
206
+ )?.count,
207
+ ).toBe(2);
208
+ });
209
+
210
+ test("empty observable strings are ignored", () => {
211
+ const graph: CorrelationGraphData = buildCorrelationGraph({
212
+ events: [
213
+ buildEvent({ className: "Authentication", observables: ["", "alice"] }),
214
+ ],
215
+ centerLabel: "c",
216
+ excludedObservables: [],
217
+ });
218
+ expect(nodeById(graph, `${OBSERVABLE_NODE_PREFIX}alice`)).toBeTruthy();
219
+ expect(nodeById(graph, `${OBSERVABLE_NODE_PREFIX}`)).toBeUndefined();
220
+ });
221
+
222
+ test("an observable shared by two classes gets ONE node and two edges", () => {
223
+ const graph: CorrelationGraphData = buildCorrelationGraph({
224
+ events: [
225
+ buildEvent({ className: "Authentication", observables: ["alice"] }),
226
+ buildEvent({ className: "Process Activity", observables: ["alice"] }),
227
+ ],
228
+ centerLabel: "c",
229
+ excludedObservables: [],
230
+ });
231
+
232
+ const observableNodes: Array<CorrelationGraphNode> = graph.nodes.filter(
233
+ (node: CorrelationGraphNode) => {
234
+ return node.kind === "observable";
235
+ },
236
+ );
237
+ expect(observableNodes).toHaveLength(1);
238
+ expect(
239
+ edgeById(
240
+ graph,
241
+ `${CLASS_NODE_PREFIX}Authentication->${OBSERVABLE_NODE_PREFIX}alice`,
242
+ ),
243
+ ).toBeTruthy();
244
+ expect(
245
+ edgeById(
246
+ graph,
247
+ `${CLASS_NODE_PREFIX}Process Activity->${OBSERVABLE_NODE_PREFIX}alice`,
248
+ ),
249
+ ).toBeTruthy();
250
+ });
251
+
252
+ test("caps co-observables at the most frequent N and reports the drop count", () => {
253
+ const events: Array<SecurityEvent> = [];
254
+ // "popular" appears in 3 events, every "rare-N" in exactly one.
255
+ for (let eventIndex: number = 0; eventIndex < 3; eventIndex++) {
256
+ events.push(
257
+ buildEvent({
258
+ className: "Authentication",
259
+ observables: ["popular", `rare-${eventIndex}`],
260
+ }),
261
+ );
262
+ }
263
+ events.push(
264
+ buildEvent({ className: "Authentication", observables: ["rare-3"] }),
265
+ );
266
+
267
+ const graph: CorrelationGraphData = buildCorrelationGraph({
268
+ events: events,
269
+ centerLabel: "c",
270
+ excludedObservables: [],
271
+ maxCoObservables: 2,
272
+ });
273
+
274
+ const observableNodes: Array<CorrelationGraphNode> = graph.nodes.filter(
275
+ (node: CorrelationGraphNode) => {
276
+ return node.kind === "observable";
277
+ },
278
+ );
279
+ expect(observableNodes).toHaveLength(2);
280
+ // Highest count first, then alphabetical among the tied rares.
281
+ expect(nodeById(graph, `${OBSERVABLE_NODE_PREFIX}popular`)).toBeTruthy();
282
+ expect(nodeById(graph, `${OBSERVABLE_NODE_PREFIX}rare-0`)).toBeTruthy();
283
+ expect(graph.droppedCoObservableCount).toBe(3);
284
+ });
285
+
286
+ test("cap exactly met → nothing reported dropped", () => {
287
+ const graph: CorrelationGraphData = buildCorrelationGraph({
288
+ events: [
289
+ buildEvent({ className: "Authentication", observables: ["a", "b"] }),
290
+ ],
291
+ centerLabel: "c",
292
+ excludedObservables: [],
293
+ maxCoObservables: 2,
294
+ });
295
+ expect(graph.droppedCoObservableCount).toBe(0);
296
+ });
297
+ });
298
+
299
+ describe("dedupeSecurityEvents", () => {
300
+ test("unions result sets by row id", () => {
301
+ const shared: SecurityEvent = buildEvent({
302
+ id: "11111111-1111-4111-8111-111111111111",
303
+ className: "Authentication",
304
+ });
305
+ const onlyFirst: SecurityEvent = buildEvent({
306
+ id: "22222222-2222-4222-8222-222222222222",
307
+ });
308
+ const onlySecond: SecurityEvent = buildEvent({
309
+ id: "33333333-3333-4333-8333-333333333333",
310
+ });
311
+
312
+ const deduped: Array<SecurityEvent> = dedupeSecurityEvents([
313
+ [onlyFirst, shared],
314
+ [shared, onlySecond],
315
+ ]);
316
+
317
+ expect(deduped).toHaveLength(3);
318
+ expect(deduped[0]).toBe(onlyFirst);
319
+ expect(deduped[1]).toBe(shared);
320
+ expect(deduped[2]).toBe(onlySecond);
321
+ });
322
+
323
+ test("two model instances with the SAME id count once", () => {
324
+ const first: SecurityEvent = buildEvent({
325
+ id: "11111111-1111-4111-8111-111111111111",
326
+ });
327
+ const second: SecurityEvent = buildEvent({
328
+ id: "11111111-1111-4111-8111-111111111111",
329
+ });
330
+ expect(dedupeSecurityEvents([[first], [second]])).toHaveLength(1);
331
+ });
332
+
333
+ test("falls back to eventUid + time when _id was not selected", () => {
334
+ const time: Date = new Date("2026-08-25T10:00:00.000Z");
335
+ const first: SecurityEvent = buildEvent({ eventUid: "uid-1", time });
336
+ const duplicate: SecurityEvent = buildEvent({ eventUid: "uid-1", time });
337
+ const differentTime: SecurityEvent = buildEvent({
338
+ eventUid: "uid-1",
339
+ time: new Date("2026-08-25T11:00:00.000Z"),
340
+ });
341
+
342
+ expect(
343
+ dedupeSecurityEvents([[first], [duplicate], [differentTime]]),
344
+ ).toHaveLength(2);
345
+ });
346
+ });
@@ -0,0 +1,256 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import * as React from "react";
4
+ import { describe, expect, test } from "@jest/globals";
5
+ import CriteriaFilterElement from "../../../../App/FeatureSet/Dashboard/src/Components/Form/Monitor/CriteriaFilter";
6
+ import CriteriaFiltersElement from "../../../../App/FeatureSet/Dashboard/src/Components/Form/Monitor/CriteriaFilters";
7
+ import {
8
+ CheckOn,
9
+ CriteriaFilter,
10
+ FilterType,
11
+ } from "../../../Types/Monitor/CriteriaFilter";
12
+ import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
13
+ import MonitorStep from "../../../Types/Monitor/MonitorStep";
14
+ import MonitorType from "../../../Types/Monitor/MonitorType";
15
+ import ObjectID from "../../../Types/ObjectID";
16
+ import getJestMockFunction, { MockFunction } from "../../MockType";
17
+
18
+ /*
19
+ * #3412 as the user met it: the "Filter Condition" dropdown on the
20
+ * "External Status Page Active Incidents" filter showed an empty "Select...",
21
+ * while the sibling filter's condition read "True" and this filter's own
22
+ * value box already showed 0.
23
+ *
24
+ * The option-list tests in ExternalStatusPageCriteriaFilter.test.ts and
25
+ * CriteriaFilterDefaults.test.ts pin the data the dropdown is built from.
26
+ * This file drives the real component instead, because "Select..." is
27
+ * something react-select decides to render when it cannot find an option
28
+ * matching the value it was handed — a rule about lists cannot say whether
29
+ * that happens on screen, only the rendered row can.
30
+ *
31
+ * Assertions stay on plain DOM text: jest-dom's matchers do not typecheck
32
+ * repo-wide.
33
+ */
34
+
35
+ // react-select's placeholder, i.e. what "unset" looks like to the user.
36
+ const UNSET: string = "Select...";
37
+
38
+ function monitorStep(): MonitorStep {
39
+ return new MonitorStep();
40
+ }
41
+
42
+ function seededExternalStatusPageFilters(): Array<CriteriaFilter> {
43
+ return MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
44
+ monitorType: MonitorType.ExternalStatusPage,
45
+ monitorStatusId: new ObjectID("aaaaaaaaaaaaaaaaaaaaaaaa"),
46
+ monitorName: "Acme Status",
47
+ })!.data!.filters;
48
+ }
49
+
50
+ function renderFilter(
51
+ criteriaFilter: CriteriaFilter,
52
+ monitorType: MonitorType,
53
+ ): {
54
+ onChange: MockFunction;
55
+ } {
56
+ const onChange: MockFunction = getJestMockFunction();
57
+
58
+ render(
59
+ <CriteriaFilterElement
60
+ monitorType={monitorType}
61
+ monitorStep={monitorStep()}
62
+ value={criteriaFilter}
63
+ onChange={onChange as unknown as (value: CriteriaFilter) => void}
64
+ />,
65
+ );
66
+
67
+ return { onChange: onChange };
68
+ }
69
+
70
+ /*
71
+ * The last filter each onChange handed back. The component replaces the whole
72
+ * filter object on every edit, so the newest call is the current state.
73
+ */
74
+ function lastFilterFrom(onChange: MockFunction): CriteriaFilter {
75
+ expect(onChange.mock.calls.length).toBeGreaterThan(0);
76
+
77
+ return onChange.mock.calls[
78
+ onChange.mock.calls.length - 1
79
+ ]![0] as CriteriaFilter;
80
+ }
81
+
82
+ // The filter list the last onChange handed back, after "Add Filter" ran.
83
+ function lastFilterListFrom(onChange: MockFunction): Array<CriteriaFilter> {
84
+ expect(onChange.mock.calls.length).toBeGreaterThan(0);
85
+
86
+ return onChange.mock.calls[
87
+ onChange.mock.calls.length - 1
88
+ ]![0] as Array<CriteriaFilter>;
89
+ }
90
+
91
+ async function pickFromDropdown(input: {
92
+ labelledBy: string;
93
+ optionLabel: string;
94
+ }): Promise<void> {
95
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
96
+
97
+ /*
98
+ * react-select renders its own text input per dropdown. Ordering follows
99
+ * the form: "Filter Type" first, then "Filter Condition".
100
+ */
101
+ const comboboxes: Array<HTMLElement> = screen.getAllByRole("combobox");
102
+ const index: number = input.labelledBy === "Filter Type" ? 0 : 1;
103
+
104
+ await user.click(comboboxes[index]!);
105
+ await user.click(await screen.findByText(input.optionLabel));
106
+ }
107
+
108
+ describe("The criteria Filter Condition dropdown", () => {
109
+ describe("the row reported in #3412", () => {
110
+ test("the seeded active-incidents filter shows its condition, not an empty Select...", () => {
111
+ const criteriaFilter: CriteriaFilter =
112
+ seededExternalStatusPageFilters()[1]!;
113
+
114
+ expect(criteriaFilter.checkOn).toBe(
115
+ CheckOn.ExternalStatusPageActiveIncidents,
116
+ );
117
+
118
+ renderFilter(criteriaFilter, MonitorType.ExternalStatusPage);
119
+
120
+ // The Filter Type dropdown, which was never the problem.
121
+ expect(
122
+ screen.getAllByText(CheckOn.ExternalStatusPageActiveIncidents).length,
123
+ ).toBeGreaterThan(0);
124
+
125
+ // The Filter Condition dropdown. This is what showed "Select...".
126
+ expect(screen.getAllByText(FilterType.EqualTo).length).toBeGreaterThan(0);
127
+ expect(screen.queryByText(UNSET)).toBeNull();
128
+ });
129
+
130
+ test("the sibling is-online filter still shows True", () => {
131
+ const criteriaFilter: CriteriaFilter =
132
+ seededExternalStatusPageFilters()[0]!;
133
+
134
+ expect(criteriaFilter.checkOn).toBe(CheckOn.ExternalStatusPageIsOnline);
135
+
136
+ renderFilter(criteriaFilter, MonitorType.ExternalStatusPage);
137
+
138
+ expect(screen.getAllByText(FilterType.True).length).toBeGreaterThan(0);
139
+ expect(screen.queryByText(UNSET)).toBeNull();
140
+ });
141
+
142
+ test("both seeded filters render with nothing left unset", () => {
143
+ for (const criteriaFilter of seededExternalStatusPageFilters()) {
144
+ const { unmount }: { unmount: () => void } = render(
145
+ <CriteriaFilterElement
146
+ monitorType={MonitorType.ExternalStatusPage}
147
+ monitorStep={monitorStep()}
148
+ value={criteriaFilter}
149
+ onChange={() => {}}
150
+ />,
151
+ );
152
+
153
+ expect(screen.queryByText(UNSET)).toBeNull();
154
+ unmount();
155
+ }
156
+ });
157
+ });
158
+
159
+ describe("changing the Filter Type", () => {
160
+ test("moves the condition to the new check's default instead of clearing it", async () => {
161
+ const { onChange }: { onChange: MockFunction } = renderFilter(
162
+ seededExternalStatusPageFilters()[0]!,
163
+ MonitorType.ExternalStatusPage,
164
+ );
165
+
166
+ await pickFromDropdown({
167
+ labelledBy: "Filter Type",
168
+ optionLabel: CheckOn.ExternalStatusPageActiveIncidents,
169
+ });
170
+
171
+ const emitted: CriteriaFilter = lastFilterFrom(onChange);
172
+
173
+ expect(emitted.checkOn).toBe(CheckOn.ExternalStatusPageActiveIncidents);
174
+ // Was undefined, which is what left the next dropdown blank.
175
+ expect(emitted.filterType).toBe(FilterType.EqualTo);
176
+ });
177
+
178
+ test("a check with exactly one condition no longer asks the user to re-pick it", async () => {
179
+ const { onChange }: { onChange: MockFunction } = renderFilter(
180
+ {
181
+ checkOn: CheckOn.ResponseBody,
182
+ filterType: FilterType.Contains,
183
+ value: "ok",
184
+ },
185
+ MonitorType.API,
186
+ );
187
+
188
+ await pickFromDropdown({
189
+ labelledBy: "Filter Type",
190
+ optionLabel: CheckOn.JavaScriptExpression,
191
+ });
192
+
193
+ const emitted: CriteriaFilter = lastFilterFrom(onChange);
194
+
195
+ expect(emitted.checkOn).toBe(CheckOn.JavaScriptExpression);
196
+ expect(emitted.filterType).toBe(FilterType.EvaluatesToTrue);
197
+ });
198
+ });
199
+
200
+ describe("adding a filter", () => {
201
+ test("an External Status Page monitor gets a filter it can actually render", () => {
202
+ const onChange: MockFunction = getJestMockFunction();
203
+
204
+ render(
205
+ <CriteriaFiltersElement
206
+ monitorType={MonitorType.ExternalStatusPage}
207
+ monitorStep={monitorStep()}
208
+ value={[seededExternalStatusPageFilters()[0]!]}
209
+ onChange={
210
+ onChange as unknown as (value: Array<CriteriaFilter>) => void
211
+ }
212
+ />,
213
+ );
214
+
215
+ screen.getByText("Add Filter").click();
216
+
217
+ const added: CriteriaFilter = lastFilterListFrom(onChange)[1]!;
218
+
219
+ /*
220
+ * Was { checkOn: IsOnline, filterType: EqualTo } — a check this monitor
221
+ * type does not offer, paired with a condition that check has never
222
+ * accepted, so both dropdowns opened blank.
223
+ */
224
+ expect(added.checkOn).toBe(CheckOn.ExternalStatusPageIsOnline);
225
+ expect(added.filterType).toBe(FilterType.True);
226
+ });
227
+
228
+ test("a website monitor keeps its up/down check and gains a condition", () => {
229
+ const onChange: MockFunction = getJestMockFunction();
230
+
231
+ render(
232
+ <CriteriaFiltersElement
233
+ monitorType={MonitorType.Website}
234
+ monitorStep={monitorStep()}
235
+ value={[
236
+ {
237
+ checkOn: CheckOn.IsOnline,
238
+ filterType: FilterType.True,
239
+ value: undefined,
240
+ },
241
+ ]}
242
+ onChange={
243
+ onChange as unknown as (value: Array<CriteriaFilter>) => void
244
+ }
245
+ />,
246
+ );
247
+
248
+ screen.getByText("Add Filter").click();
249
+
250
+ const added: CriteriaFilter = lastFilterListFrom(onChange)[1]!;
251
+
252
+ expect(added.checkOn).toBe(CheckOn.IsOnline);
253
+ expect(added.filterType).toBe(FilterType.True);
254
+ });
255
+ });
256
+ });