@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
@@ -1,6 +1,7 @@
1
+ import "@testing-library/jest-dom";
1
2
  import React from "react";
2
3
  import { fireEvent, render, screen } from "@testing-library/react";
3
- import { describe, expect, test } from "@jest/globals";
4
+ import { beforeEach, describe, expect, test } from "@jest/globals";
4
5
  import CodeType from "../../../Types/Code/CodeType";
5
6
 
6
7
  /*
@@ -13,10 +14,28 @@ import CodeType from "../../../Types/Code/CodeType";
13
14
  interface RecordedEditorProps {
14
15
  value?: string | undefined;
15
16
  defaultValue?: string | undefined;
17
+ /*
18
+ * The grammar Monaco would tokenise with, and the option bag it would be
19
+ * configured from. Both are recorded because for YAML they are the feature:
20
+ * the wrong grammar highlights a Sigma rule as HTML, and the wrong
21
+ * indentation options emit tabs the parser rejects.
22
+ */
23
+ defaultLanguage?: string | undefined;
24
+ language?: string | undefined;
25
+ height?: string | undefined;
26
+ options?: Record<string, unknown> | undefined;
16
27
  }
17
28
 
18
29
  const mockEditorRenders: Array<RecordedEditorProps> = [];
19
30
 
31
+ /*
32
+ * Everything the component pushes onto the MODEL rather than into the options
33
+ * bag. tabSize/insertSpaces live here on purpose - they are global config in
34
+ * monaco's standalone build, so setting them per-editor would have the last
35
+ * editor to mount dictate indentation for every other one on the page.
36
+ */
37
+ const mockModelOptionUpdates: Array<Record<string, unknown>> = [];
38
+
20
39
  jest.mock("@monaco-editor/react", () => {
21
40
  return {
22
41
  __esModule: true,
@@ -26,23 +45,65 @@ jest.mock("@monaco-editor/react", () => {
26
45
  default: (editorProps: {
27
46
  value?: string | undefined;
28
47
  defaultValue?: string | undefined;
48
+ defaultLanguage?: string | undefined;
49
+ language?: string | undefined;
50
+ height?: string | undefined;
51
+ options?: Record<string, unknown> | undefined;
29
52
  onChange?: ((value: string | undefined) => void) | undefined;
53
+ onMount?: ((editor: unknown, monaco: unknown) => void) | undefined;
30
54
  }) => {
31
55
  mockEditorRenders.push({
32
56
  value: editorProps.value,
33
57
  defaultValue: editorProps.defaultValue,
58
+ defaultLanguage: editorProps.defaultLanguage,
59
+ language: editorProps.language,
60
+ height: editorProps.height,
61
+ options: editorProps.options,
34
62
  });
35
63
 
64
+ const hostRef: React.MutableRefObject<HTMLDivElement | null> =
65
+ React.useRef<HTMLDivElement | null>(null);
66
+
67
+ /*
68
+ * Monaco calls onMount once the editor and its model exist. Child
69
+ * effects run before the parent's, which is the same order the real
70
+ * editor mounts in.
71
+ */
72
+ React.useEffect(() => {
73
+ if (!editorProps.onMount) {
74
+ return;
75
+ }
76
+
77
+ editorProps.onMount(
78
+ {
79
+ getDomNode: () => {
80
+ return hostRef.current;
81
+ },
82
+ getModel: () => {
83
+ return {
84
+ updateOptions: (options: Record<string, unknown>) => {
85
+ mockModelOptionUpdates.push(options);
86
+ },
87
+ };
88
+ },
89
+ },
90
+ {},
91
+ );
92
+ // eslint-disable-next-line
93
+ }, []);
94
+
36
95
  return (
37
- <textarea
38
- data-testid="monaco"
39
- value={editorProps.value || ""}
40
- onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
41
- if (editorProps.onChange) {
42
- editorProps.onChange(event.target.value);
43
- }
44
- }}
45
- />
96
+ <div ref={hostRef}>
97
+ <textarea
98
+ data-testid="monaco"
99
+ value={editorProps.value || ""}
100
+ onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
101
+ if (editorProps.onChange) {
102
+ editorProps.onChange(event.target.value);
103
+ }
104
+ }}
105
+ />
106
+ </div>
46
107
  );
47
108
  },
48
109
  };
@@ -172,3 +233,406 @@ describe("CodeEditor", () => {
172
233
  expect(getEditor().value).toBe(JSON.stringify({ hello: "world" }, null, 4));
173
234
  });
174
235
  });
236
+
237
+ type LastRenderFunction = () => RecordedEditorProps;
238
+
239
+ const lastRender: LastRenderFunction = (): RecordedEditorProps => {
240
+ expect(mockEditorRenders.length).toBeGreaterThan(0);
241
+
242
+ return mockEditorRenders[mockEditorRenders.length - 1] as RecordedEditorProps;
243
+ };
244
+
245
+ describe("CodeEditor — the grammar Monaco is given", () => {
246
+ beforeEach(() => {
247
+ mockEditorRenders.length = 0;
248
+ });
249
+
250
+ test.each([
251
+ [CodeType.YAML, "yaml"],
252
+ [CodeType.JSON, "json"],
253
+ [CodeType.JavaScript, "javascript"],
254
+ [CodeType.CSS, "css"],
255
+ [CodeType.HTML, "html"],
256
+ [CodeType.Markdown, "markdown"],
257
+ ])("%s is handed to Monaco as %s", (type: CodeType, expected: string) => {
258
+ render(<CodeEditor type={type} value="" />);
259
+
260
+ expect(lastRender().defaultLanguage).toBe(expected);
261
+ });
262
+
263
+ /*
264
+ * `defaultLanguage` is only read when the model is created, so a caller that
265
+ * swaps CodeType after mount keeps the old grammar unless `language` is also
266
+ * passed - the prop that drives setModelLanguage.
267
+ */
268
+ test("the language prop is passed too, so a type change after mount lands", () => {
269
+ const { rerender } = render(<CodeEditor type={CodeType.Text} value="" />);
270
+
271
+ expect(lastRender().language).toBe("text");
272
+
273
+ rerender(<CodeEditor type={CodeType.YAML} value="" />);
274
+
275
+ expect(lastRender().language).toBe("yaml");
276
+ });
277
+ });
278
+
279
+ describe("CodeEditor — YAML gets YAML-safe editing options", () => {
280
+ beforeEach(() => {
281
+ mockEditorRenders.length = 0;
282
+ });
283
+
284
+ type YamlOptionsFunction = () => Record<string, unknown>;
285
+
286
+ const yamlOptions: YamlOptionsFunction = (): Record<string, unknown> => {
287
+ render(<CodeEditor type={CodeType.YAML} value="title: x" />);
288
+
289
+ return (lastRender().options || {}) as Record<string, unknown>;
290
+ };
291
+
292
+ /*
293
+ * A literal tab is illegal as YAML indentation, and Monaco's model defaults
294
+ * are four spaces with the indentation detected from the initial content -
295
+ * so a rule pasted in tab-indented would keep emitting tabs the parser
296
+ * rejects.
297
+ */
298
+ test("two spaces, always spaces - set on this editor's own model", () => {
299
+ mockModelOptionUpdates.length = 0;
300
+
301
+ render(<CodeEditor type={CodeType.YAML} value={"a:\n b: 1"} />);
302
+
303
+ expect(mockModelOptionUpdates).toContainEqual({
304
+ tabSize: 2,
305
+ insertSpaces: true,
306
+ });
307
+ });
308
+
309
+ /*
310
+ * The regression guard for the reason those three live on the model.
311
+ * tabSize, insertSpaces and detectIndentation are IGlobalEditorOptions:
312
+ * monaco's standalone editor writes every registered `editor.*` key it is
313
+ * constructed with into a PROCESS-WIDE configuration service, and those
314
+ * three are exactly the ones ModelService reads back and pushes onto every
315
+ * model on the page. Put them in the options bag and the last editor to
316
+ * mount silently re-indents all the others.
317
+ */
318
+ test.each(["tabSize", "insertSpaces", "detectIndentation"])(
319
+ "%s is never passed as an editor option - it is global config",
320
+ (key: string) => {
321
+ for (const type of [
322
+ CodeType.YAML,
323
+ CodeType.JSON,
324
+ CodeType.JavaScript,
325
+ CodeType.Markdown,
326
+ CodeType.Text,
327
+ ]) {
328
+ mockEditorRenders.length = 0;
329
+
330
+ render(<CodeEditor type={type} value="x" />);
331
+
332
+ expect(
333
+ Object.keys((lastRender().options || {}) as Record<string, unknown>),
334
+ ).not.toContain(key);
335
+ }
336
+ },
337
+ );
338
+
339
+ test("a non-YAML editor touches no model options at all", () => {
340
+ mockModelOptionUpdates.length = 0;
341
+
342
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
343
+
344
+ expect(mockModelOptionUpdates).toHaveLength(0);
345
+ });
346
+
347
+ test("the YAML chrome's padding is supplied by the editor", () => {
348
+ expect(yamlOptions()["padding"]).toEqual({ top: 10, bottom: 10 });
349
+ });
350
+
351
+ test("the gutter is on, because every parse error names a line", () => {
352
+ expect(yamlOptions()["lineNumbers"]).toBe("on");
353
+ });
354
+
355
+ /*
356
+ * Wrapping YAML is not an option - the indentation IS the syntax and a
357
+ * wrapped line reads as a deeper one - so the horizontal scrollbar is the
358
+ * only way to reach a long scalar.
359
+ */
360
+ test("long lines stay reachable: no wrapping, but a real scrollbar", () => {
361
+ const options: Record<string, unknown> = yamlOptions();
362
+
363
+ expect(options["wordWrap"]).toBe("off");
364
+ expect(options["scrollbar"]).toEqual({ horizontal: "auto" });
365
+ });
366
+
367
+ test("whitespace is rendered, because in YAML it is the syntax", () => {
368
+ expect(yamlOptions()["renderWhitespace"]).toBe("boundary");
369
+ });
370
+
371
+ test("no blank runway below the last line in a short form field", () => {
372
+ expect(yamlOptions()["scrollBeyondLastLine"]).toBe(false);
373
+ });
374
+
375
+ test("folding is on, which YAML's offside rule makes work", () => {
376
+ expect(yamlOptions()["folding"]).toBe(true);
377
+ });
378
+ });
379
+
380
+ describe("CodeEditor — the other languages are left exactly as they were", () => {
381
+ beforeEach(() => {
382
+ mockEditorRenders.length = 0;
383
+ });
384
+
385
+ type OptionsForFunction = (type: CodeType) => Record<string, unknown>;
386
+
387
+ const optionsFor: OptionsForFunction = (
388
+ type: CodeType,
389
+ ): Record<string, unknown> => {
390
+ render(<CodeEditor type={type} value="x" />);
391
+
392
+ return (lastRender().options || {}) as Record<string, unknown>;
393
+ };
394
+
395
+ test.each([
396
+ CodeType.JSON,
397
+ CodeType.JavaScript,
398
+ CodeType.CSS,
399
+ CodeType.HTML,
400
+ CodeType.Markdown,
401
+ CodeType.Text,
402
+ ])("%s keeps Monaco's own indentation behaviour", (type: CodeType) => {
403
+ const options: Record<string, unknown> = optionsFor(type);
404
+
405
+ expect(options["tabSize"]).toBeUndefined();
406
+ expect(options["insertSpaces"]).toBeUndefined();
407
+ expect(options["detectIndentation"]).toBeUndefined();
408
+ });
409
+
410
+ test.each([
411
+ CodeType.JSON,
412
+ CodeType.JavaScript,
413
+ CodeType.CSS,
414
+ CodeType.HTML,
415
+ CodeType.Markdown,
416
+ CodeType.Text,
417
+ ])(
418
+ "%s keeps its scrolling and padding exactly as before",
419
+ (type: CodeType) => {
420
+ const options: Record<string, unknown> = optionsFor(type);
421
+
422
+ expect(options["scrollbar"]).toEqual({ horizontal: "hidden" });
423
+ expect(options["scrollBeyondLastLine"]).toBe(true);
424
+ expect(options["padding"]).toBeUndefined();
425
+ },
426
+ );
427
+
428
+ test.each([
429
+ CodeType.JSON,
430
+ CodeType.JavaScript,
431
+ CodeType.CSS,
432
+ CodeType.HTML,
433
+ CodeType.Text,
434
+ ])("%s keeps its gutter off unless asked", (type: CodeType) => {
435
+ expect(optionsFor(type)["lineNumbers"]).toBe("off");
436
+ });
437
+
438
+ test("showLineNumbers still turns the gutter on for any type", () => {
439
+ render(
440
+ <CodeEditor type={CodeType.JSON} value="{}" showLineNumbers={true} />,
441
+ );
442
+
443
+ expect(
444
+ ((lastRender().options || {}) as Record<string, unknown>)["lineNumbers"],
445
+ ).toBe("on");
446
+ });
447
+
448
+ test.each([
449
+ CodeType.JSON,
450
+ CodeType.JavaScript,
451
+ CodeType.CSS,
452
+ CodeType.HTML,
453
+ CodeType.Markdown,
454
+ ])("%s keeps rendering no whitespace", (type: CodeType) => {
455
+ expect(optionsFor(type)["renderWhitespace"]).toBe("none");
456
+ });
457
+
458
+ test("Markdown still wraps", () => {
459
+ expect(optionsFor(CodeType.Markdown)["wordWrap"]).toBe("on");
460
+ });
461
+ });
462
+
463
+ describe("CodeEditor — height", () => {
464
+ beforeEach(() => {
465
+ mockEditorRenders.length = 0;
466
+ });
467
+
468
+ test("defaults to 30vh, as every existing caller expects", () => {
469
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
470
+
471
+ expect(lastRender().height).toBe("30vh");
472
+ });
473
+
474
+ test("a caller can ask for its own", () => {
475
+ render(<CodeEditor type={CodeType.YAML} value="a: 1" height="22rem" />);
476
+
477
+ expect(lastRender().height).toBe("22rem");
478
+ });
479
+ });
480
+
481
+ describe("CodeEditor — a YAML placeholder never becomes document text", () => {
482
+ beforeEach(() => {
483
+ mockEditorRenders.length = 0;
484
+ });
485
+
486
+ /*
487
+ * `defaultValue` seeds the Monaco model, so anything routed there is real
488
+ * content the user can accidentally save. The JS and CSS arms get away with
489
+ * it by wrapping the hint in a comment; YAML has no such wrapper, so its
490
+ * hint has to go to the help text instead.
491
+ */
492
+ test("the hint is shown as help text, not seeded into the editor", () => {
493
+ render(
494
+ <CodeEditor
495
+ type={CodeType.YAML}
496
+ value=""
497
+ placeholder="Sigma rule YAML — title, logsource, detection and condition."
498
+ />,
499
+ );
500
+
501
+ expect(lastRender().defaultValue).toBe("");
502
+ expect(
503
+ screen.getByText(/Sigma rule YAML/, { exact: false }),
504
+ ).toBeInTheDocument();
505
+ });
506
+
507
+ test("an empty YAML editor still starts empty when no hint is given", () => {
508
+ render(<CodeEditor type={CodeType.YAML} value="" />);
509
+
510
+ expect(lastRender().defaultValue).toBe("");
511
+ });
512
+ });
513
+ describe("CodeEditor — accessibility lands on Monaco's input, not the wrapper", () => {
514
+ beforeEach(() => {
515
+ mockEditorRenders.length = 0;
516
+ });
517
+
518
+ type InputAreaFunction = () => HTMLTextAreaElement;
519
+
520
+ const inputArea: InputAreaFunction = (): HTMLTextAreaElement => {
521
+ return screen.getByTestId("monaco") as HTMLTextAreaElement;
522
+ };
523
+
524
+ /*
525
+ * The wrapper this component returns is a bare <div>, i.e. role=generic,
526
+ * and ARIA forbids naming one - browsers compute no name for it and
527
+ * assistive technology ignores it. The element that actually takes focus is
528
+ * Monaco's own textarea, whose default name is the string "Editor content".
529
+ */
530
+ test("the field's label names the editor's textarea", () => {
531
+ render(
532
+ <CodeEditor
533
+ type={CodeType.YAML}
534
+ value="a: 1"
535
+ ariaLabelledby="the-field-label"
536
+ />,
537
+ );
538
+
539
+ expect(inputArea().getAttribute("aria-labelledby")).toBe("the-field-label");
540
+ });
541
+
542
+ test("descriptions are associated with the textarea", () => {
543
+ render(
544
+ <CodeEditor
545
+ type={CodeType.YAML}
546
+ value="a: 1"
547
+ ariaDescribedby="hint-id status-id"
548
+ />,
549
+ );
550
+
551
+ expect(inputArea().getAttribute("aria-describedby")).toBe(
552
+ "hint-id status-id",
553
+ );
554
+ });
555
+
556
+ test("an invalid document is announced as invalid", () => {
557
+ render(<CodeEditor type={CodeType.YAML} value="a: [" ariaInvalid={true} />);
558
+
559
+ expect(inputArea().getAttribute("aria-invalid")).toBe("true");
560
+ });
561
+
562
+ test("a valid document carries no aria-invalid at all", () => {
563
+ render(<CodeEditor type={CodeType.YAML} value="a: 1" />);
564
+
565
+ expect(inputArea().hasAttribute("aria-invalid")).toBe(false);
566
+ });
567
+
568
+ test("the attributes follow their props after mount", () => {
569
+ const { rerender } = render(
570
+ <CodeEditor type={CodeType.YAML} value="a: 1" ariaInvalid={false} />,
571
+ );
572
+
573
+ expect(inputArea().hasAttribute("aria-invalid")).toBe(false);
574
+
575
+ rerender(
576
+ <CodeEditor type={CodeType.YAML} value="a: [" ariaInvalid={true} />,
577
+ );
578
+
579
+ expect(inputArea().getAttribute("aria-invalid")).toBe("true");
580
+ });
581
+
582
+ test("no aria attributes are invented when the caller passes none", () => {
583
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
584
+
585
+ expect(inputArea().hasAttribute("aria-labelledby")).toBe(false);
586
+ expect(inputArea().hasAttribute("aria-describedby")).toBe(false);
587
+ });
588
+ });
589
+
590
+ describe("CodeEditor — spell check", () => {
591
+ beforeEach(() => {
592
+ mockEditorRenders.length = 0;
593
+ });
594
+
595
+ type SpellCheckFunction = () => boolean;
596
+
597
+ const spellCheck: SpellCheckFunction = (): boolean => {
598
+ return (screen.getByTestId("monaco") as HTMLTextAreaElement).spellcheck;
599
+ };
600
+
601
+ // YAML is code: squiggles under every Sigma key name are pure noise.
602
+ test("is always off for YAML, whatever the caller asked for", () => {
603
+ render(<CodeEditor type={CodeType.YAML} value="a: 1" />);
604
+
605
+ expect(spellCheck()).toBe(false);
606
+ });
607
+
608
+ test("is still off for YAML when the caller explicitly enables it", () => {
609
+ render(
610
+ <CodeEditor
611
+ type={CodeType.YAML}
612
+ value="a: 1"
613
+ disableSpellCheck={false}
614
+ />,
615
+ );
616
+
617
+ expect(spellCheck()).toBe(false);
618
+ });
619
+
620
+ // Markdown is prose, and keeps following the caller exactly as it did.
621
+ test("follows the caller for Markdown", () => {
622
+ render(<CodeEditor type={CodeType.Markdown} value="# hi" />);
623
+
624
+ expect(spellCheck()).toBe(true);
625
+ });
626
+
627
+ test("is off for Markdown when the caller disables it", () => {
628
+ render(
629
+ <CodeEditor
630
+ type={CodeType.Markdown}
631
+ value="# hi"
632
+ disableSpellCheck={true}
633
+ />,
634
+ );
635
+
636
+ expect(spellCheck()).toBe(false);
637
+ });
638
+ });
@@ -0,0 +1,105 @@
1
+ import "@testing-library/jest-dom";
2
+ import { afterEach, describe, expect, test } from "@jest/globals";
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+ import React from "react";
5
+
6
+ import Detail from "../../../../UI/Components/Detail/Detail";
7
+ import Field from "../../../../UI/Components/Detail/Field";
8
+ import FieldType from "../../../../UI/Components/Types/FieldType";
9
+
10
+ /*
11
+ * The read-only half of the YAML field type. A YAML value must render as a
12
+ * highlighted code block - never as prose, and never re-emitted through a
13
+ * parser: dumping YAML back out drops the comments, anchors and key ordering
14
+ * a hand-written document (a Sigma rule, a collector config) is written with.
15
+ */
16
+
17
+ interface TestItem {
18
+ sigmaRuleYaml: string;
19
+ description: string;
20
+ }
21
+
22
+ const SIGMA_RULE_WITH_COMMENT: string = `# catches password spraying
23
+ title: Failed logon burst
24
+ logsource:
25
+ category: authentication
26
+ detection:
27
+ selection:
28
+ className: Authentication
29
+ condition: selection
30
+ `;
31
+
32
+ type RenderDetailFunction = (fieldType: FieldType, value: string) => void;
33
+
34
+ const renderDetail: RenderDetailFunction = (
35
+ fieldType: FieldType,
36
+ value: string,
37
+ ): void => {
38
+ const fields: Array<Field<TestItem>> = [
39
+ {
40
+ key: "sigmaRuleYaml",
41
+ title: "Sigma Rule (YAML)",
42
+ fieldType,
43
+ },
44
+ ];
45
+
46
+ render(
47
+ <Detail<TestItem>
48
+ item={{ sigmaRuleYaml: value, description: "unused" }}
49
+ fields={fields}
50
+ />,
51
+ );
52
+ };
53
+
54
+ afterEach(() => {
55
+ cleanup();
56
+ });
57
+
58
+ describe("Detail — a YAML field renders as highlighted YAML", () => {
59
+ test("the value reaches the page", () => {
60
+ renderDetail(FieldType.YAML, SIGMA_RULE_WITH_COMMENT);
61
+
62
+ expect(document.body.textContent).toContain("title: Failed logon burst");
63
+ });
64
+
65
+ test("it renders inside a code block, not as prose", () => {
66
+ renderDetail(FieldType.YAML, SIGMA_RULE_WITH_COMMENT);
67
+
68
+ expect(document.querySelector("pre code")).not.toBeNull();
69
+ });
70
+
71
+ test("it is tagged as the yaml language for the highlighter", () => {
72
+ renderDetail(FieldType.YAML, SIGMA_RULE_WITH_COMMENT);
73
+
74
+ expect(document.querySelector("code.language-yaml")).not.toBeNull();
75
+ });
76
+
77
+ /*
78
+ * The JSON arm of the same branch pretty-prints by round-tripping through
79
+ * JSON.parse/stringify. Doing that to YAML would silently rewrite the user's
80
+ * document; comments are the most visible casualty.
81
+ */
82
+ test("comments survive - the document is rendered verbatim", () => {
83
+ renderDetail(FieldType.YAML, SIGMA_RULE_WITH_COMMENT);
84
+
85
+ expect(document.body.textContent).toContain("# catches password spraying");
86
+ });
87
+
88
+ test("indentation survives verbatim", () => {
89
+ renderDetail(FieldType.YAML, SIGMA_RULE_WITH_COMMENT);
90
+
91
+ expect(document.body.textContent).toContain(" condition: selection");
92
+ });
93
+
94
+ test("the field title is still rendered", () => {
95
+ renderDetail(FieldType.YAML, SIGMA_RULE_WITH_COMMENT);
96
+
97
+ expect(screen.getByText("Sigma Rule (YAML)")).toBeInTheDocument();
98
+ });
99
+
100
+ test("a syntactically broken document is still displayed, not swallowed", () => {
101
+ renderDetail(FieldType.YAML, "title: [unclosed");
102
+
103
+ expect(document.body.textContent).toContain("title: [unclosed");
104
+ });
105
+ });