@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,865 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ cleanup,
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ waitFor,
8
+ within,
9
+ } from "@testing-library/react";
10
+ import React from "react";
11
+ import {
12
+ afterEach,
13
+ beforeEach,
14
+ describe,
15
+ expect,
16
+ jest,
17
+ test,
18
+ } from "@jest/globals";
19
+ import getJestMockFunction, { MockFunction } from "../../MockType";
20
+
21
+ /*
22
+ * Security Events → Correlate (issue #3395): the quick single-observable
23
+ * search stays as shorthand, a condition builder adds AND/OR chains, the
24
+ * applied filter shows as removable chips and lives in the URL, AND chains
25
+ * compile into ONE query, OR chains fan out and union by event id, class
26
+ * nodes drill down into their events, and observable nodes offer
27
+ * focus/add/exclude pivots. Everything the component promises the issue is
28
+ * pinned here against a mocked AnalyticsModelAPI and a recording ReactFlow
29
+ * stand-in.
30
+ */
31
+
32
+ const getListMock: MockFunction = getJestMockFunction();
33
+ const mockQueryParams: Record<string, string | null> = {};
34
+ const setQueryStringMock: MockFunction = getJestMockFunction();
35
+ const navigateMock: MockFunction = getJestMockFunction();
36
+
37
+ const PROJECT_ID_STRING: string = "11111111-1111-4111-8111-111111111111";
38
+
39
+ /*
40
+ * The arrow wrappers are load bearing: jest.mock is hoisted above the
41
+ * compiled requires, so naming the mocks directly would capture them before
42
+ * their initializers have run.
43
+ */
44
+ jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
45
+ return {
46
+ __esModule: true,
47
+ default: {
48
+ getList: (...args: Array<any>) => {
49
+ return getListMock(...args);
50
+ },
51
+ },
52
+ };
53
+ });
54
+
55
+ jest.mock("../../../UI/Utils/Project", () => {
56
+ return {
57
+ __esModule: true,
58
+ default: {
59
+ getCurrentProjectId: () => {
60
+ return new ObjectID("11111111-1111-4111-8111-111111111111");
61
+ },
62
+ },
63
+ };
64
+ });
65
+
66
+ jest.mock("../../../UI/Utils/Navigation", () => {
67
+ return {
68
+ __esModule: true,
69
+ default: {
70
+ getQueryStringByName: (name: string) => {
71
+ return mockQueryParams[name] ?? null;
72
+ },
73
+ setQueryString: (...args: Array<any>) => {
74
+ return setQueryStringMock(...args);
75
+ },
76
+ navigate: (...args: Array<any>) => {
77
+ return navigateMock(...args);
78
+ },
79
+ },
80
+ };
81
+ });
82
+
83
+ jest.mock("react-i18next", () => {
84
+ return {
85
+ useTranslation: () => {
86
+ return {
87
+ t: (key: string): string => {
88
+ return key;
89
+ },
90
+ };
91
+ },
92
+ };
93
+ });
94
+
95
+ /*
96
+ * ReactFlow renders nothing measurable in jsdom — replace it with a stand-in
97
+ * that lists every node as a clickable button so node-click pivots are
98
+ * testable, and forwards onNodeClick exactly like the real component.
99
+ */
100
+ jest.mock("reactflow", () => {
101
+ return {
102
+ __esModule: true,
103
+ default: (props: any) => {
104
+ return React.createElement(
105
+ "div",
106
+ { "data-testid": "mock-react-flow" },
107
+ (props.nodes || []).map((node: any) => {
108
+ return React.createElement(
109
+ "button",
110
+ {
111
+ key: node.id,
112
+ type: "button",
113
+ "data-testid": `flow-node-${node.id}`,
114
+ onClick: (event: any) => {
115
+ if (props.onNodeClick) {
116
+ props.onNodeClick(event, node);
117
+ }
118
+ },
119
+ },
120
+ typeof node.data?.label === "string" ? node.data.label : node.id,
121
+ );
122
+ }),
123
+ );
124
+ },
125
+ Background: () => {
126
+ return null;
127
+ },
128
+ Controls: () => {
129
+ return null;
130
+ },
131
+ BackgroundVariant: { Dots: "dots" },
132
+ MarkerType: { ArrowClosed: "arrowclosed" },
133
+ };
134
+ });
135
+
136
+ const detailPivotObservable: string = "pivot-observable";
137
+
138
+ jest.mock(
139
+ "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/SecurityEventDetail",
140
+ () => {
141
+ return {
142
+ __esModule: true,
143
+ default: (props: any) => {
144
+ return React.createElement(
145
+ "div",
146
+ { "data-testid": "mock-event-detail" },
147
+ React.createElement(
148
+ "span",
149
+ { "data-testid": "mock-event-detail-message" },
150
+ props.securityEvent?.message || "",
151
+ ),
152
+ React.createElement(
153
+ "button",
154
+ {
155
+ type: "button",
156
+ "data-testid": "mock-event-detail-pivot",
157
+ onClick: () => {
158
+ if (props.onCorrelateObservable) {
159
+ props.onCorrelateObservable("pivot-observable");
160
+ }
161
+ },
162
+ },
163
+ "pivot",
164
+ ),
165
+ );
166
+ },
167
+ };
168
+ },
169
+ );
170
+
171
+ /*
172
+ * The component import comes LAST: requiring it is what instantiates the
173
+ * mock factories above, and those factories reference these imports (e.g.
174
+ * ObjectID) — so everything else must be initialized first.
175
+ */
176
+ import SecurityEvent from "../../../Models/AnalyticsModels/SecurityEvent";
177
+ import ObjectID from "../../../Types/ObjectID";
178
+ import OcsfSeverity from "../../../Types/SecurityEvent/OcsfSeverity";
179
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
180
+ import Includes from "../../../Types/BaseDatabase/Includes";
181
+ import IncludesNone from "../../../Types/BaseDatabase/IncludesNone";
182
+ import Search from "../../../Types/BaseDatabase/Search";
183
+ import {
184
+ CorrelationFilter,
185
+ serializeCorrelationFilter,
186
+ } from "../../../../App/FeatureSet/Dashboard/src/Utils/SecurityEventCorrelation";
187
+ import CorrelateGraph from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/CorrelateGraph";
188
+
189
+ interface EventInput {
190
+ id: string;
191
+ className?: string;
192
+ severityName?: OcsfSeverity;
193
+ message?: string;
194
+ observables?: Array<string>;
195
+ principalUser?: string;
196
+ }
197
+
198
+ function buildEvent(input: EventInput): SecurityEvent {
199
+ const event: SecurityEvent = new SecurityEvent();
200
+ event._id = new ObjectID(input.id);
201
+ event.time = new Date("2026-08-25T10:00:00.000Z");
202
+ if (input.className) {
203
+ event.className = input.className;
204
+ }
205
+ if (input.severityName) {
206
+ event.severityName = input.severityName;
207
+ }
208
+ if (input.message) {
209
+ event.message = input.message;
210
+ }
211
+ if (input.principalUser) {
212
+ event.principalUser = input.principalUser;
213
+ }
214
+ event.observables = input.observables || [];
215
+ return event;
216
+ }
217
+
218
+ function listResult(events: Array<SecurityEvent>): {
219
+ data: Array<SecurityEvent>;
220
+ count: number;
221
+ skip: number;
222
+ limit: number;
223
+ } {
224
+ return { data: events, count: events.length, skip: 0, limit: 200 };
225
+ }
226
+
227
+ type QueryRecord = Record<string, unknown>;
228
+
229
+ function queryOfCall(callIndex: number): QueryRecord {
230
+ const callArg: { query: QueryRecord } = getListMock.mock.calls[
231
+ callIndex
232
+ ]?.[0] as { query: QueryRecord };
233
+ return callArg.query;
234
+ }
235
+
236
+ function runQuickSearch(observable: string): void {
237
+ fireEvent.change(screen.getByTestId("security-events-correlate-observable"), {
238
+ target: { value: observable },
239
+ });
240
+ fireEvent.click(screen.getByTestId("security-events-correlate-button"));
241
+ }
242
+
243
+ beforeEach(() => {
244
+ getListMock.mockReset();
245
+ getListMock.mockResolvedValue(listResult([]));
246
+ setQueryStringMock.mockReset();
247
+ navigateMock.mockReset();
248
+ for (const key of Object.keys(mockQueryParams)) {
249
+ delete mockQueryParams[key];
250
+ }
251
+ });
252
+
253
+ afterEach(() => {
254
+ cleanup();
255
+ });
256
+
257
+ describe("CorrelateGraph", () => {
258
+ test("starts on the empty state without fetching", () => {
259
+ render(<CorrelateGraph />);
260
+ expect(screen.getByText("Correlate security events")).toBeInTheDocument();
261
+ expect(getListMock).not.toHaveBeenCalled();
262
+ });
263
+
264
+ test("quick search compiles to ONE hasAny query with project and time scope", async () => {
265
+ getListMock.mockResolvedValue(
266
+ listResult([
267
+ buildEvent({
268
+ id: "22222222-2222-4222-8222-222222222222",
269
+ className: "Authentication",
270
+ observables: ["wb-ubuntu-03", "alice"],
271
+ }),
272
+ buildEvent({
273
+ id: "33333333-3333-4333-8333-333333333333",
274
+ className: "Process Activity",
275
+ observables: ["wb-ubuntu-03"],
276
+ }),
277
+ ]),
278
+ );
279
+
280
+ render(<CorrelateGraph />);
281
+ runQuickSearch("wb-ubuntu-03");
282
+
283
+ await waitFor(() => {
284
+ expect(getListMock).toHaveBeenCalledTimes(1);
285
+ });
286
+
287
+ const query: QueryRecord = queryOfCall(0);
288
+ expect((query["projectId"] as ObjectID).toString()).toBe(PROJECT_ID_STRING);
289
+ expect(query["time"]).toBeInstanceOf(InBetween);
290
+ expect(query["observables"]).toBeInstanceOf(Includes);
291
+ expect((query["observables"] as Includes).values).toEqual(["wb-ubuntu-03"]);
292
+
293
+ const callArg: { limit: number; sort: Record<string, unknown> } =
294
+ getListMock.mock.calls[0]?.[0] as {
295
+ limit: number;
296
+ sort: Record<string, unknown>;
297
+ };
298
+ expect(callArg.limit).toBe(200);
299
+
300
+ await waitFor(() => {
301
+ expect(screen.getByText(/2 matching events\./)).toBeInTheDocument();
302
+ });
303
+
304
+ // The applied filter shows as a chip and lands in the URL.
305
+ expect(screen.getByTestId("correlate-filter-chip-0")).toHaveTextContent(
306
+ "wb-ubuntu-03",
307
+ );
308
+ expect(setQueryStringMock).toHaveBeenCalledWith(
309
+ expect.objectContaining({
310
+ q: expect.stringContaining("wb-ubuntu-03"),
311
+ observable: null,
312
+ }),
313
+ );
314
+
315
+ // Class nodes made it into the graph.
316
+ expect(
317
+ screen.getByTestId("flow-node-class:Authentication"),
318
+ ).toHaveTextContent("Authentication (1)");
319
+ });
320
+
321
+ test("a ?observable= deep link auto-correlates on mount", async () => {
322
+ mockQueryParams["observable"] = "wb-ubuntu-03";
323
+ render(<CorrelateGraph />);
324
+
325
+ await waitFor(() => {
326
+ expect(getListMock).toHaveBeenCalledTimes(1);
327
+ });
328
+ expect((queryOfCall(0)["observables"] as Includes).values).toEqual([
329
+ "wb-ubuntu-03",
330
+ ]);
331
+ });
332
+
333
+ test("a ?q= deep link restores a multi-condition AND filter into one query and opens the builder", async () => {
334
+ const filter: CorrelationFilter = {
335
+ conditions: [
336
+ { field: "principalIp", operator: "equals", value: "192.168.1.20" },
337
+ { field: "principalHost", operator: "equals", value: "wb-ubuntu-03" },
338
+ ],
339
+ connector: "and",
340
+ } as CorrelationFilter;
341
+ mockQueryParams["q"] = serializeCorrelationFilter(filter);
342
+ mockQueryParams["hours"] = "168";
343
+
344
+ render(<CorrelateGraph />);
345
+
346
+ await waitFor(() => {
347
+ expect(getListMock).toHaveBeenCalledTimes(1);
348
+ });
349
+ const query: QueryRecord = queryOfCall(0);
350
+ expect(query["principalIp"]).toBe("192.168.1.20");
351
+ expect(query["principalHost"]).toBe("wb-ubuntu-03");
352
+
353
+ // Both chips visible, builder auto-opened for the chain.
354
+ expect(screen.getByTestId("correlate-filter-chip-1")).toBeInTheDocument();
355
+ expect(screen.getByTestId("correlate-filter-builder")).toBeInTheDocument();
356
+
357
+ // The seeded time range makes it into the query window (168h ≈ 7 days).
358
+ const timeWindow: InBetween<Date> = query["time"] as InBetween<Date>;
359
+ const windowMs: number =
360
+ new Date(timeWindow.endValue).getTime() -
361
+ new Date(timeWindow.startValue).getTime();
362
+ expect(Math.round(windowMs / (1000 * 60 * 60))).toBe(168);
363
+ });
364
+
365
+ test("OR filters fan out into one query per condition and union by event id", async () => {
366
+ const filter: CorrelationFilter = {
367
+ conditions: [
368
+ { field: "observable", operator: "equals", value: "wb-ubuntu-03" },
369
+ { field: "message", operator: "contains", value: "failed" },
370
+ ],
371
+ connector: "or",
372
+ } as CorrelationFilter;
373
+ mockQueryParams["q"] = serializeCorrelationFilter(filter);
374
+
375
+ const sharedEvent: SecurityEvent = buildEvent({
376
+ id: "44444444-4444-4444-8444-444444444444",
377
+ className: "Authentication",
378
+ message: "failed password for alice",
379
+ observables: ["wb-ubuntu-03"],
380
+ });
381
+
382
+ getListMock.mockImplementation((args: any) => {
383
+ const query: QueryRecord = args.query as QueryRecord;
384
+ if (query["observables"]) {
385
+ return Promise.resolve(
386
+ listResult([
387
+ sharedEvent,
388
+ buildEvent({
389
+ id: "55555555-5555-4555-8555-555555555555",
390
+ className: "Authentication",
391
+ observables: ["wb-ubuntu-03"],
392
+ }),
393
+ ]),
394
+ );
395
+ }
396
+ return Promise.resolve(
397
+ listResult([
398
+ sharedEvent,
399
+ buildEvent({
400
+ id: "66666666-6666-4666-8666-666666666666",
401
+ className: "Detection Finding",
402
+ message: "failed logon burst",
403
+ observables: [],
404
+ }),
405
+ ]),
406
+ );
407
+ });
408
+
409
+ render(<CorrelateGraph />);
410
+
411
+ await waitFor(() => {
412
+ expect(getListMock).toHaveBeenCalledTimes(2);
413
+ });
414
+
415
+ // 2 + 2 rows with one shared id → 3 unique events.
416
+ await waitFor(() => {
417
+ expect(screen.getByText(/3 matching events\./)).toBeInTheDocument();
418
+ });
419
+
420
+ const queries: Array<QueryRecord> = [queryOfCall(0), queryOfCall(1)];
421
+ const observableQuery: QueryRecord | undefined = queries.find(
422
+ (query: QueryRecord) => {
423
+ return Boolean(query["observables"]);
424
+ },
425
+ );
426
+ const messageQuery: QueryRecord | undefined = queries.find(
427
+ (query: QueryRecord) => {
428
+ return Boolean(query["message"]);
429
+ },
430
+ );
431
+ expect(observableQuery).toBeTruthy();
432
+ expect(messageQuery?.["message"]).toBeInstanceOf(Search);
433
+ });
434
+
435
+ test("an impossible AND chain surfaces the friendly compiler error and fetches nothing", async () => {
436
+ const filter: CorrelationFilter = {
437
+ conditions: [
438
+ { field: "principalHost", operator: "equals", value: "host-a" },
439
+ { field: "principalHost", operator: "equals", value: "host-b" },
440
+ ],
441
+ connector: "and",
442
+ } as CorrelationFilter;
443
+ mockQueryParams["q"] = serializeCorrelationFilter(filter);
444
+
445
+ render(<CorrelateGraph />);
446
+
447
+ await waitFor(() => {
448
+ expect(screen.getByText(/can never match/)).toBeInTheDocument();
449
+ });
450
+ expect(getListMock).not.toHaveBeenCalled();
451
+ });
452
+
453
+ test("hitting the per-query cap marks counts as lower bounds", async () => {
454
+ const manyEvents: Array<SecurityEvent> = [];
455
+ for (let eventIndex: number = 0; eventIndex < 200; eventIndex++) {
456
+ manyEvents.push(
457
+ buildEvent({
458
+ id: `77777777-7777-4777-8777-${String(eventIndex).padStart(12, "0")}`,
459
+ className: "Authentication",
460
+ observables: ["wb-ubuntu-03"],
461
+ }),
462
+ );
463
+ }
464
+ getListMock.mockResolvedValue(listResult(manyEvents));
465
+
466
+ render(<CorrelateGraph />);
467
+ runQuickSearch("wb-ubuntu-03");
468
+
469
+ await waitFor(() => {
470
+ expect(
471
+ screen.getByText(/treat counts as lower bounds/),
472
+ ).toBeInTheDocument();
473
+ });
474
+ });
475
+
476
+ test("removing a chip re-correlates with the remaining conditions", async () => {
477
+ const filter: CorrelationFilter = {
478
+ conditions: [
479
+ { field: "principalIp", operator: "equals", value: "192.168.1.20" },
480
+ { field: "principalHost", operator: "equals", value: "wb-ubuntu-03" },
481
+ ],
482
+ connector: "and",
483
+ } as CorrelationFilter;
484
+ mockQueryParams["q"] = serializeCorrelationFilter(filter);
485
+
486
+ render(<CorrelateGraph />);
487
+ await waitFor(() => {
488
+ expect(getListMock).toHaveBeenCalledTimes(1);
489
+ });
490
+
491
+ fireEvent.click(screen.getByTestId("correlate-filter-chip-remove-0"));
492
+
493
+ await waitFor(() => {
494
+ expect(getListMock).toHaveBeenCalledTimes(2);
495
+ });
496
+ const query: QueryRecord = queryOfCall(1);
497
+ expect(query["principalIp"]).toBeUndefined();
498
+ expect(query["principalHost"]).toBe("wb-ubuntu-03");
499
+ expect(screen.queryByTestId("correlate-filter-chip-1")).toBeNull();
500
+ });
501
+
502
+ test("clearing every condition returns to the empty state and clears the URL", async () => {
503
+ const filter: CorrelationFilter = {
504
+ conditions: [
505
+ { field: "observable", operator: "equals", value: "wb-ubuntu-03" },
506
+ { field: "observable", operator: "equals", value: "192.168.1.20" },
507
+ ],
508
+ connector: "or",
509
+ } as CorrelationFilter;
510
+ mockQueryParams["q"] = serializeCorrelationFilter(filter);
511
+
512
+ render(<CorrelateGraph />);
513
+ await waitFor(() => {
514
+ expect(getListMock).toHaveBeenCalled();
515
+ });
516
+
517
+ fireEvent.click(screen.getByTestId("correlate-filter-clear-all"));
518
+
519
+ await waitFor(() => {
520
+ expect(screen.getByText("Correlate security events")).toBeInTheDocument();
521
+ });
522
+ expect(setQueryStringMock).toHaveBeenLastCalledWith(
523
+ expect.objectContaining({ q: null, hours: null }),
524
+ );
525
+ });
526
+
527
+ test("clicking a class node opens the drill-down and a row opens the event detail; the detail pivot re-correlates", async () => {
528
+ getListMock.mockResolvedValue(
529
+ listResult([
530
+ buildEvent({
531
+ id: "88888888-8888-4888-8888-888888888888",
532
+ className: "Authentication",
533
+ severityName: OcsfSeverity.High,
534
+ message: "failed password for alice",
535
+ principalUser: "alice",
536
+ observables: ["wb-ubuntu-03", "alice"],
537
+ }),
538
+ buildEvent({
539
+ id: "99999999-9999-4999-8999-999999999999",
540
+ className: "Process Activity",
541
+ message: "suspicious process",
542
+ observables: ["wb-ubuntu-03"],
543
+ }),
544
+ ]),
545
+ );
546
+
547
+ render(<CorrelateGraph />);
548
+ runQuickSearch("wb-ubuntu-03");
549
+
550
+ await waitFor(() => {
551
+ expect(
552
+ screen.getByTestId("flow-node-class:Authentication"),
553
+ ).toBeInTheDocument();
554
+ });
555
+
556
+ fireEvent.click(screen.getByTestId("flow-node-class:Authentication"));
557
+
558
+ const drillDown: HTMLElement = screen.getByTestId("correlate-drilldown");
559
+ expect(within(drillDown).getByText(/1 matching event/)).toBeInTheDocument();
560
+ expect(
561
+ within(drillDown).getByText("failed password for alice"),
562
+ ).toBeInTheDocument();
563
+ // Only Authentication events — not the Process Activity row.
564
+ expect(within(drillDown).queryByText("suspicious process")).toBeNull();
565
+
566
+ fireEvent.click(screen.getByTestId("correlate-drilldown-event-0"));
567
+ expect(screen.getByTestId("mock-event-detail")).toBeInTheDocument();
568
+ expect(screen.getByTestId("mock-event-detail-message")).toHaveTextContent(
569
+ "failed password for alice",
570
+ );
571
+
572
+ fireEvent.click(screen.getByTestId("mock-event-detail-pivot"));
573
+ await waitFor(() => {
574
+ expect(getListMock).toHaveBeenCalledTimes(2);
575
+ });
576
+ expect((queryOfCall(1)["observables"] as Includes).values).toEqual([
577
+ detailPivotObservable,
578
+ ]);
579
+ });
580
+
581
+ test("clicking an observable node offers Focus / Add / Exclude pivots", async () => {
582
+ getListMock.mockResolvedValue(
583
+ listResult([
584
+ buildEvent({
585
+ id: "22222222-2222-4222-8222-222222222222",
586
+ className: "Authentication",
587
+ observables: ["wb-ubuntu-03", "alice"],
588
+ }),
589
+ ]),
590
+ );
591
+
592
+ render(<CorrelateGraph />);
593
+ runQuickSearch("wb-ubuntu-03");
594
+
595
+ await waitFor(() => {
596
+ expect(
597
+ screen.getByTestId("flow-node-observable:alice"),
598
+ ).toBeInTheDocument();
599
+ });
600
+
601
+ fireEvent.click(screen.getByTestId("flow-node-observable:alice"));
602
+ const actionBar: HTMLElement = screen.getByTestId(
603
+ "correlate-observable-actions",
604
+ );
605
+ expect(within(actionBar).getByText("alice")).toBeInTheDocument();
606
+
607
+ // Exclude appends "Observable is not alice" alongside the equality.
608
+ fireEvent.click(screen.getByTestId("correlate-action-exclude"));
609
+ await waitFor(() => {
610
+ expect(getListMock).toHaveBeenCalledTimes(2);
611
+ });
612
+ const operators: Array<unknown> = queryOfCall(1)[
613
+ "observables"
614
+ ] as Array<unknown>;
615
+ expect(Array.isArray(operators)).toBe(true);
616
+ expect(operators[0]).toBeInstanceOf(Includes);
617
+ expect((operators[0] as Includes).values).toEqual(["wb-ubuntu-03"]);
618
+ expect(operators[1]).toBeInstanceOf(IncludesNone);
619
+ expect((operators[1] as IncludesNone).values).toEqual(["alice"]);
620
+ });
621
+
622
+ test("Focus replaces the whole filter with the selected observable", async () => {
623
+ getListMock.mockResolvedValue(
624
+ listResult([
625
+ buildEvent({
626
+ id: "22222222-2222-4222-8222-222222222222",
627
+ className: "Authentication",
628
+ observables: ["wb-ubuntu-03", "alice"],
629
+ }),
630
+ ]),
631
+ );
632
+
633
+ render(<CorrelateGraph />);
634
+ runQuickSearch("wb-ubuntu-03");
635
+
636
+ await waitFor(() => {
637
+ expect(
638
+ screen.getByTestId("flow-node-observable:alice"),
639
+ ).toBeInTheDocument();
640
+ });
641
+
642
+ fireEvent.click(screen.getByTestId("flow-node-observable:alice"));
643
+ fireEvent.click(screen.getByTestId("correlate-action-focus"));
644
+
645
+ await waitFor(() => {
646
+ expect(getListMock).toHaveBeenCalledTimes(2);
647
+ });
648
+ expect((queryOfCall(1)["observables"] as Includes).values).toEqual([
649
+ "alice",
650
+ ]);
651
+ // The quick input follows a single-observable filter.
652
+ expect(
653
+ screen.getByTestId("security-events-correlate-observable"),
654
+ ).toHaveValue("alice");
655
+ });
656
+
657
+ test("searched observables never appear as co-occurring nodes", async () => {
658
+ getListMock.mockResolvedValue(
659
+ listResult([
660
+ buildEvent({
661
+ id: "22222222-2222-4222-8222-222222222222",
662
+ className: "Authentication",
663
+ observables: ["wb-ubuntu-03", "alice"],
664
+ }),
665
+ ]),
666
+ );
667
+
668
+ render(<CorrelateGraph />);
669
+ runQuickSearch("wb-ubuntu-03");
670
+
671
+ await waitFor(() => {
672
+ expect(
673
+ screen.getByTestId("flow-node-observable:alice"),
674
+ ).toBeInTheDocument();
675
+ });
676
+ expect(
677
+ screen.queryByTestId("flow-node-observable:wb-ubuntu-03"),
678
+ ).toBeNull();
679
+ });
680
+
681
+ test("an API failure surfaces as an error message", async () => {
682
+ getListMock.mockRejectedValue(new Error("ClickHouse is down"));
683
+
684
+ render(<CorrelateGraph />);
685
+ runQuickSearch("wb-ubuntu-03");
686
+
687
+ await waitFor(() => {
688
+ expect(screen.getByText(/ClickHouse is down/)).toBeInTheDocument();
689
+ });
690
+ });
691
+
692
+ test("changing the time range refetches with the new window and syncs the URL", async () => {
693
+ getListMock.mockResolvedValue(
694
+ listResult([
695
+ buildEvent({
696
+ id: "22222222-2222-4222-8222-222222222222",
697
+ className: "Authentication",
698
+ observables: ["wb-ubuntu-03"],
699
+ }),
700
+ ]),
701
+ );
702
+
703
+ render(<CorrelateGraph />);
704
+ runQuickSearch("wb-ubuntu-03");
705
+ await waitFor(() => {
706
+ expect(getListMock).toHaveBeenCalledTimes(1);
707
+ });
708
+
709
+ const timeRangeCombobox: HTMLElement = within(
710
+ screen.getByTestId("security-events-correlate-time-range"),
711
+ ).getByRole("combobox");
712
+ fireEvent.keyDown(timeRangeCombobox, { key: "ArrowDown" });
713
+ const option: HTMLElement = screen.getByText("Last 7 days");
714
+ fireEvent.mouseDown(option);
715
+ fireEvent.click(option);
716
+
717
+ await waitFor(() => {
718
+ expect(getListMock).toHaveBeenCalledTimes(2);
719
+ });
720
+ const timeWindow: InBetween<Date> = queryOfCall(1)[
721
+ "time"
722
+ ] as InBetween<Date>;
723
+ const windowMs: number =
724
+ new Date(timeWindow.endValue).getTime() -
725
+ new Date(timeWindow.startValue).getTime();
726
+ expect(Math.round(windowMs / (1000 * 60 * 60))).toBe(168);
727
+ expect(setQueryStringMock).toHaveBeenLastCalledWith(
728
+ expect.objectContaining({ hours: "168" }),
729
+ );
730
+ });
731
+
732
+ test.each<[string]>([["999"], ["abc"], ["-5"]])(
733
+ "an invalid ?hours=%s falls back to the 24-hour default",
734
+ async (rawHours: string) => {
735
+ mockQueryParams["observable"] = "wb-ubuntu-03";
736
+ mockQueryParams["hours"] = rawHours;
737
+
738
+ render(<CorrelateGraph />);
739
+ await waitFor(() => {
740
+ expect(getListMock).toHaveBeenCalledTimes(1);
741
+ });
742
+
743
+ const timeWindow: InBetween<Date> = queryOfCall(0)[
744
+ "time"
745
+ ] as InBetween<Date>;
746
+ const windowMs: number =
747
+ new Date(timeWindow.endValue).getTime() -
748
+ new Date(timeWindow.startValue).getTime();
749
+ expect(Math.round(windowMs / (1000 * 60 * 60))).toBe(24);
750
+ },
751
+ );
752
+
753
+ test("OR mode hides the Exclude pivot and the drill-down class filter", async () => {
754
+ const filter: CorrelationFilter = {
755
+ conditions: [
756
+ { field: "observable", operator: "equals", value: "wb-ubuntu-03" },
757
+ { field: "observable", operator: "equals", value: "192.168.1.20" },
758
+ ],
759
+ connector: "or",
760
+ } as CorrelationFilter;
761
+ mockQueryParams["q"] = serializeCorrelationFilter(filter);
762
+
763
+ getListMock.mockResolvedValue(
764
+ listResult([
765
+ buildEvent({
766
+ id: "22222222-2222-4222-8222-222222222222",
767
+ className: "Authentication",
768
+ observables: ["wb-ubuntu-03", "alice"],
769
+ }),
770
+ ]),
771
+ );
772
+
773
+ render(<CorrelateGraph />);
774
+ await waitFor(() => {
775
+ expect(
776
+ screen.getByTestId("flow-node-observable:alice"),
777
+ ).toBeInTheDocument();
778
+ });
779
+
780
+ fireEvent.click(screen.getByTestId("flow-node-observable:alice"));
781
+ expect(
782
+ screen.getByTestId("correlate-observable-actions"),
783
+ ).toBeInTheDocument();
784
+ // "is not X" only narrows under AND — the pivot hides in OR mode.
785
+ expect(screen.queryByTestId("correlate-action-exclude")).toBeNull();
786
+
787
+ fireEvent.click(screen.getByTestId("flow-node-class:Authentication"));
788
+ expect(screen.getByTestId("correlate-drilldown")).toBeInTheDocument();
789
+ expect(screen.queryByTestId("correlate-drilldown-filter-class")).toBeNull();
790
+ });
791
+
792
+ test("the drill-down's Filter-to-this-class appends a className condition in AND mode", async () => {
793
+ getListMock.mockResolvedValue(
794
+ listResult([
795
+ buildEvent({
796
+ id: "22222222-2222-4222-8222-222222222222",
797
+ className: "Authentication",
798
+ observables: ["wb-ubuntu-03"],
799
+ }),
800
+ ]),
801
+ );
802
+
803
+ render(<CorrelateGraph />);
804
+ runQuickSearch("wb-ubuntu-03");
805
+ await waitFor(() => {
806
+ expect(
807
+ screen.getByTestId("flow-node-class:Authentication"),
808
+ ).toBeInTheDocument();
809
+ });
810
+
811
+ fireEvent.click(screen.getByTestId("flow-node-class:Authentication"));
812
+ fireEvent.click(screen.getByTestId("correlate-drilldown-filter-class"));
813
+
814
+ await waitFor(() => {
815
+ expect(getListMock).toHaveBeenCalledTimes(2);
816
+ });
817
+ const query: QueryRecord = queryOfCall(1);
818
+ expect((query["observables"] as Includes).values).toEqual(["wb-ubuntu-03"]);
819
+ expect(query["className"]).toBe("Authentication");
820
+ });
821
+
822
+ test("Correlate stays disabled while a builder row has no value", () => {
823
+ render(<CorrelateGraph />);
824
+
825
+ // Open the builder — it seeds one default (empty-valued) row.
826
+ fireEvent.click(
827
+ screen.getByTestId("security-events-correlate-toggle-builder"),
828
+ );
829
+ expect(screen.getByTestId("correlate-filter-builder")).toBeInTheDocument();
830
+ expect(
831
+ screen.getByTestId("security-events-correlate-button"),
832
+ ).toBeDisabled();
833
+
834
+ fireEvent.change(screen.getByTestId("correlate-condition-value-0"), {
835
+ target: { value: "wb-ubuntu-03" },
836
+ });
837
+ expect(
838
+ screen.getByTestId("security-events-correlate-button"),
839
+ ).not.toBeDisabled();
840
+ });
841
+
842
+ test("opening the builder seeds it from freshly typed (unapplied) quick text", async () => {
843
+ getListMock.mockResolvedValue(listResult([]));
844
+
845
+ render(<CorrelateGraph />);
846
+ // Apply one search, then type something NEW without applying it.
847
+ runQuickSearch("wb-ubuntu-03");
848
+ await waitFor(() => {
849
+ expect(getListMock).toHaveBeenCalledTimes(1);
850
+ });
851
+ fireEvent.change(
852
+ screen.getByTestId("security-events-correlate-observable"),
853
+ { target: { value: "freshly-typed" } },
854
+ );
855
+
856
+ fireEvent.click(
857
+ screen.getByTestId("security-events-correlate-toggle-builder"),
858
+ );
859
+
860
+ // The new text wins over the stale draft from the applied search.
861
+ expect(screen.getByTestId("correlate-condition-value-0")).toHaveValue(
862
+ "freshly-typed",
863
+ );
864
+ });
865
+ });