@oneuptime/common 12.0.23 → 12.0.25

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 (323) hide show
  1. package/Models/AnalyticsModels/Span.ts +101 -0
  2. package/Models/DatabaseModels/DetectionRule.ts +55 -0
  3. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
  4. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
  5. package/Models/DatabaseModels/Project.ts +68 -0
  6. package/Models/DatabaseModels/User.ts +68 -0
  7. package/Server/API/BaseAPI.ts +0 -24
  8. package/Server/API/SlackAPI.ts +0 -2
  9. package/Server/API/TelemetryAPI.ts +29 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  14. package/Server/Middleware/GzipRequestBody.ts +311 -0
  15. package/Server/Middleware/MultipartFormData.ts +87 -3
  16. package/Server/Middleware/SlackAuthorization.ts +96 -18
  17. package/Server/Services/DetectionRuleService.ts +73 -6
  18. package/Server/Services/Index.ts +205 -0
  19. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
  20. package/Server/Services/NetworkDeviceService.ts +148 -0
  21. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  22. package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
  23. package/Server/Services/ProjectService.ts +29 -19
  24. package/Server/Services/SecurityEventService.ts +90 -13
  25. package/Server/Services/StatusPageResourceService.ts +223 -0
  26. package/Server/Services/TelemetryAttributeService.ts +17 -0
  27. package/Server/Services/UserService.ts +4 -6
  28. package/Server/Types/Markdown.ts +161 -67
  29. package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
  30. package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
  31. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
  32. package/Server/Utils/Attribution.ts +4 -0
  33. package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
  34. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
  35. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
  36. package/Server/Utils/StartServer.ts +12 -32
  37. package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
  38. package/Server/Utils/Telemetry/LlmMetricSpend.ts +181 -0
  39. package/Server/Utils/Telemetry/LlmSpan.ts +46 -0
  40. package/Server/Utils/Workspace/Slack/Actions/Auth.ts +0 -12
  41. package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
  42. package/Server/Views/Partials/Head.ejs +2 -1
  43. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
  44. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
  45. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
  46. package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
  47. package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
  48. package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
  49. package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
  50. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
  51. package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
  52. package/Tests/App/Dashboard/LlmCallsTableIdentity.test.tsx +322 -0
  53. package/Tests/App/Dashboard/LlmOverview.test.tsx +335 -0
  54. package/Tests/App/Dashboard/LlmSpanDisplay.test.ts +391 -0
  55. package/Tests/App/Dashboard/LlmUsageBreakdown.test.tsx +1007 -0
  56. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
  57. package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
  58. package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
  59. package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
  60. package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
  61. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
  62. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
  63. package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
  64. package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
  65. package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
  66. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
  67. package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
  68. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
  69. package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
  70. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
  71. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
  72. package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
  73. package/Tests/Server/API/BaseAPI.test.ts +41 -0
  74. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +9 -16
  75. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
  76. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
  77. package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
  78. package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
  79. package/Tests/Server/Middleware/SlackAuthorization.test.ts +262 -5
  80. package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
  81. package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
  82. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
  83. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
  84. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
  85. package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
  86. package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
  87. package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
  88. package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
  89. package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
  90. package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
  91. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
  92. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
  93. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
  94. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
  95. package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
  96. package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +387 -4
  97. package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +386 -0
  98. package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +804 -0
  99. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
  100. package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
  101. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
  102. package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
  103. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
  104. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
  105. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
  106. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
  107. package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +584 -0
  108. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
  109. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
  110. package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
  111. package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
  112. package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
  113. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
  114. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
  115. package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
  116. package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
  117. package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
  118. package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
  119. package/Tests/UI/Components/SideOver.test.tsx +1 -1
  120. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
  121. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
  122. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
  123. package/Tests/UI/Components/Toggle.test.tsx +25 -0
  124. package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
  125. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
  126. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
  127. package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
  128. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
  129. package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +770 -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/SecurityEvent/DetectionFindingConstants.ts +11 -0
  138. package/Types/Telemetry/LlmConventions.ts +255 -0
  139. package/Types/Telemetry/LlmMetricConventions.ts +329 -0
  140. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
  141. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
  142. package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
  143. package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
  144. package/UI/Components/Detail/Detail.tsx +10 -0
  145. package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
  146. package/UI/Components/Forms/Fields/FormField.tsx +31 -0
  147. package/UI/Components/Forms/Types/Field.ts +8 -0
  148. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  149. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
  150. package/UI/Components/Forms/Validation.ts +46 -0
  151. package/UI/Components/Input/Input.tsx +3 -0
  152. package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
  153. package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
  154. package/UI/Components/ModelTable/Column.ts +8 -0
  155. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
  156. package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
  157. package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
  158. package/UI/Components/SideOver/SideOver.tsx +5 -5
  159. package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
  160. package/UI/Components/StatusPage/ResourceList.tsx +197 -38
  161. package/UI/Components/Toggle/Toggle.tsx +9 -1
  162. package/UI/Components/Types/FieldType.ts +1 -0
  163. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
  164. package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
  165. package/Utils/StatusPage/ResourceExplorer.ts +258 -0
  166. package/Utils/Telemetry/LlmMetricQuery.ts +287 -0
  167. package/build/dist/Models/AnalyticsModels/Span.js +89 -0
  168. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  169. package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
  170. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  171. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
  172. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  173. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
  174. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  175. package/build/dist/Models/DatabaseModels/Project.js +76 -0
  176. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  177. package/build/dist/Models/DatabaseModels/User.js +76 -0
  178. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  179. package/build/dist/Server/API/BaseAPI.js +3 -19
  180. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  181. package/build/dist/Server/API/SlackAPI.js +0 -2
  182. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  183. package/build/dist/Server/API/TelemetryAPI.js +17 -0
  184. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  185. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
  190. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
  191. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  192. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  193. package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
  194. package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
  195. package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
  196. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  197. package/build/dist/Server/Middleware/SlackAuthorization.js +58 -7
  198. package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
  199. package/build/dist/Server/Services/DetectionRuleService.js +49 -4
  200. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  201. package/build/dist/Server/Services/Index.js +204 -0
  202. package/build/dist/Server/Services/Index.js.map +1 -1
  203. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
  204. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  205. package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
  206. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  207. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
  208. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  209. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
  210. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
  211. package/build/dist/Server/Services/ProjectService.js +25 -45
  212. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  213. package/build/dist/Server/Services/SecurityEventService.js +57 -5
  214. package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
  215. package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
  216. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  217. package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
  218. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  219. package/build/dist/Server/Services/UserService.js +2 -12
  220. package/build/dist/Server/Services/UserService.js.map +1 -1
  221. package/build/dist/Server/Types/Markdown.js +147 -61
  222. package/build/dist/Server/Types/Markdown.js.map +1 -1
  223. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
  224. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
  225. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
  226. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  227. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
  228. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  229. package/build/dist/Server/Utils/Attribution.js.map +1 -1
  230. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
  231. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
  232. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
  233. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  234. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
  235. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  236. package/build/dist/Server/Utils/StartServer.js +12 -21
  237. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  238. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
  239. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
  240. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +151 -0
  241. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
  242. package/build/dist/Server/Utils/Telemetry/LlmSpan.js +24 -1
  243. package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
  244. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +0 -10
  245. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
  246. package/build/dist/Types/Code/YamlSyntax.js +212 -0
  247. package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
  248. package/build/dist/Types/Exception/ExceptionCode.js +1 -0
  249. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  250. package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
  251. package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
  252. package/build/dist/Types/Marketing/Attribution.js +10 -0
  253. package/build/dist/Types/Marketing/Attribution.js.map +1 -1
  254. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
  255. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  256. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
  257. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  258. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
  259. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  260. package/build/dist/Types/Telemetry/LlmConventions.js +236 -0
  261. package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
  262. package/build/dist/Types/Telemetry/LlmMetricConventions.js +290 -0
  263. package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
  264. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  265. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  266. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
  267. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  268. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
  269. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  270. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
  271. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
  272. package/build/dist/UI/Components/Detail/Detail.js +9 -0
  273. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  274. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
  275. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  276. package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
  277. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  278. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  279. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  280. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  281. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
  282. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
  283. package/build/dist/UI/Components/Forms/Validation.js +27 -0
  284. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  285. package/build/dist/UI/Components/Input/Input.js +1 -1
  286. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  287. package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
  288. package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
  289. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
  290. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  291. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
  292. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  293. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
  294. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
  295. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
  296. package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
  297. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
  298. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  299. package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
  300. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  301. package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
  302. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  303. package/build/dist/UI/Components/Types/FieldType.js +1 -0
  304. package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
  305. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
  306. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
  307. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
  308. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
  309. package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
  310. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
  311. package/build/dist/Utils/Telemetry/LlmMetricQuery.js +175 -0
  312. package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
  313. package/jest.config.json +2 -0
  314. package/package.json +1 -1
  315. package/tsconfig.json +2 -1
  316. package/Tests/UI/Components/Toast.test.tsx +0 -197
  317. package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
  318. package/UI/Components/Toast/Toast.tsx +0 -189
  319. package/UI/Components/Toast/ToastInit.tsx +0 -110
  320. package/build/dist/UI/Components/Toast/Toast.js +0 -123
  321. package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
  322. package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
  323. package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
@@ -0,0 +1,824 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ act,
4
+ cleanup,
5
+ configure,
6
+ fireEvent,
7
+ render,
8
+ screen,
9
+ waitFor,
10
+ } from "@testing-library/react";
11
+ import React from "react";
12
+
13
+ /*
14
+ * A real BaseModelTable mounts a card, a header, a full table and (in the
15
+ * picker tests) a modal on top of that. That comfortably outruns the 1s
16
+ * default when the whole Common suite is competing for the same box.
17
+ */
18
+ configure({ asyncUtilTimeout: 15000 });
19
+
20
+ import {
21
+ afterEach,
22
+ beforeEach,
23
+ describe,
24
+ expect,
25
+ jest,
26
+ test,
27
+ } from "@jest/globals";
28
+
29
+ /*
30
+ * BaseModelTable pulls in permissions, the current project and the i18n
31
+ * provider. None of those are what this suite is about.
32
+ */
33
+ jest.mock("../../../../UI/Utils/Permission", () => {
34
+ return {
35
+ __esModule: true,
36
+ default: {
37
+ getAllPermissions: () => {
38
+ return [];
39
+ },
40
+ getProjectPermissions: () => {
41
+ return [];
42
+ },
43
+ getGlobalPermissions: () => {
44
+ return [];
45
+ },
46
+ },
47
+ };
48
+ });
49
+
50
+ jest.mock("../../../../UI/Utils/User", () => {
51
+ return {
52
+ __esModule: true,
53
+ default: {
54
+ isMasterAdmin: () => {
55
+ return true;
56
+ },
57
+ getUserId: () => {
58
+ return null;
59
+ },
60
+ },
61
+ };
62
+ });
63
+
64
+ jest.mock("../../../../UI/Utils/Translation", () => {
65
+ return {
66
+ __esModule: true,
67
+ default: () => {
68
+ return {
69
+ translateString: (value: string | undefined) => {
70
+ return value;
71
+ },
72
+ translateValue: (value: unknown) => {
73
+ return value;
74
+ },
75
+ };
76
+ },
77
+ };
78
+ });
79
+
80
+ import BaseModelTable, {
81
+ BaseTableCallbacks,
82
+ ComponentProps as BaseModelTableProps,
83
+ } from "../../../../UI/Components/ModelTable/BaseModelTable";
84
+ import Columns from "../../../../UI/Components/ModelTable/Columns";
85
+ import TableFilterUrlState from "../../../../UI/Utils/TableFilterUrlState";
86
+ import FieldType from "../../../../UI/Components/Types/FieldType";
87
+ import SecurityEvent from "../../../../Models/AnalyticsModels/SecurityEvent";
88
+ import Query from "../../../../Types/BaseDatabase/Query";
89
+ import ListResult from "../../../../Types/BaseDatabase/ListResult";
90
+ import UserPreferences, {
91
+ UserPreferenceType,
92
+ } from "../../../../Utils/UserPreferences";
93
+ import { JSONObject } from "../../../../Types/JSON";
94
+
95
+ /*
96
+ * ---------------------------------------------------------------------------
97
+ * Attribute columns, end to end through a real table
98
+ * ---------------------------------------------------------------------------
99
+ *
100
+ * A security event's typed OCSF columns are only half of what it carries; the
101
+ * rest of the source payload is flattened into an `attributes` map whose keys
102
+ * differ per event class. Those cannot ship as columns, so the picker offers
103
+ * them through a search box and whatever the viewer adds becomes an ordinary
104
+ * column.
105
+ *
106
+ * What is pinned here is everything that only shows up once the pieces are
107
+ * wired together:
108
+ *
109
+ * - a column added from the picker actually renders a <th> and a cell;
110
+ * - it survives a reload, because the layout it lives in is the same layout
111
+ * every other column choice lives in;
112
+ * - removing it, or resetting the layout, takes it away;
113
+ * - the map column is on the wire from the FIRST request, since the table
114
+ * does not refetch when its column set changes - get that wrong and a
115
+ * freshly added column renders blank until something else reloads the page;
116
+ * - the key pool is fetched lazily, when the picker opens, not on mount.
117
+ */
118
+
119
+ const PREFS_KEY: string = "security-events-table";
120
+
121
+ const ATTRIBUTE_KEYS: Array<string> = [
122
+ "activity_name",
123
+ "class_uid",
124
+ "device.hostname",
125
+ "finding_info.title",
126
+ "metadata.product.name",
127
+ ];
128
+
129
+ type GetListCall = {
130
+ query: Query<SecurityEvent>;
131
+ select: JSONObject;
132
+ };
133
+
134
+ type MakeColumnsFunction = () => Columns<SecurityEvent>;
135
+
136
+ const makeColumns: MakeColumnsFunction = (): Columns<SecurityEvent> => {
137
+ return [
138
+ { field: { message: true }, title: "Message", type: FieldType.Text },
139
+ { field: { severityName: true }, title: "Severity", type: FieldType.Text },
140
+ {
141
+ field: { eventUid: true },
142
+ title: "Event UID",
143
+ type: FieldType.Text,
144
+ isHiddenByDefault: true,
145
+ },
146
+ ] as unknown as Columns<SecurityEvent>;
147
+ };
148
+
149
+ type SeedPreferenceFunction = (data: {
150
+ order: Array<string>;
151
+ hidden: Array<string>;
152
+ }) => void;
153
+
154
+ const seedPreference: SeedPreferenceFunction = (data: {
155
+ order: Array<string>;
156
+ hidden: Array<string>;
157
+ }): void => {
158
+ UserPreferences.saveUserPreferenceByTypeAsJSON({
159
+ key: PREFS_KEY,
160
+ userPreferenceType: UserPreferenceType.BaseModelTableColumns,
161
+ value: { order: data.order, hidden: data.hidden },
162
+ });
163
+ };
164
+
165
+ type ReadPreferenceFunction = () => JSONObject | null;
166
+
167
+ const readPreference: ReadPreferenceFunction = (): JSONObject | null => {
168
+ return UserPreferences.getUserPreferenceByTypeAsJSON({
169
+ key: PREFS_KEY,
170
+ userPreferenceType: UserPreferenceType.BaseModelTableColumns,
171
+ });
172
+ };
173
+
174
+ type GetHeadersFunction = () => Array<string>;
175
+
176
+ const getHeaders: GetHeadersFunction = (): Array<string> => {
177
+ return screen.getAllByRole("columnheader").map((header: HTMLElement) => {
178
+ return (header.textContent || "").trim();
179
+ });
180
+ };
181
+
182
+ describe("BaseModelTable attribute columns", () => {
183
+ let calls: Array<GetListCall> = [];
184
+ let rows: Array<JSONObject> = [];
185
+ let fetchCallCount: number = 0;
186
+ let fetchResult: () => Promise<Array<string>> = async () => {
187
+ return ATTRIBUTE_KEYS;
188
+ };
189
+
190
+ type MakeCallbacksFunction = () => BaseTableCallbacks<SecurityEvent>;
191
+
192
+ const makeCallbacks: MakeCallbacksFunction =
193
+ (): BaseTableCallbacks<SecurityEvent> => {
194
+ return {
195
+ deleteItem: async () => {
196
+ return undefined;
197
+ },
198
+ getModelFromJSON: (item: JSONObject) => {
199
+ return item as unknown as SecurityEvent;
200
+ },
201
+ getJSONFromModel: (item: SecurityEvent) => {
202
+ return item as unknown as JSONObject;
203
+ },
204
+ addSlugToSelect: (select: unknown) => {
205
+ return select;
206
+ },
207
+ getList: async (data: {
208
+ query: Query<SecurityEvent>;
209
+ limit: number;
210
+ select: JSONObject;
211
+ }): Promise<ListResult<SecurityEvent>> => {
212
+ calls.push({
213
+ query: data.query,
214
+ select: (data.select || {}) as unknown as JSONObject,
215
+ });
216
+ return {
217
+ data: rows as unknown as Array<SecurityEvent>,
218
+ count: rows.length,
219
+ skip: 0,
220
+ limit: data.limit,
221
+ };
222
+ },
223
+ toJSONArray: () => {
224
+ return rows;
225
+ },
226
+ updateById: async () => {
227
+ return undefined;
228
+ },
229
+ showCreateEditModal: () => {
230
+ return <></>;
231
+ },
232
+ } as unknown as BaseTableCallbacks<SecurityEvent>;
233
+ };
234
+
235
+ type RenderTableOptions = {
236
+ columns?: Columns<SecurityEvent> | undefined;
237
+ attributeColumnKey?: string | undefined;
238
+ withoutAttributeColumns?: boolean | undefined;
239
+ };
240
+
241
+ type RenderTableFunction = (
242
+ options?: RenderTableOptions,
243
+ ) => ReturnType<typeof render>;
244
+
245
+ const renderTable: RenderTableFunction = (
246
+ options: RenderTableOptions = {},
247
+ ): ReturnType<typeof render> => {
248
+ const props: BaseModelTableProps<SecurityEvent> = {
249
+ modelType: SecurityEvent,
250
+ id: "security-events-table",
251
+ name: "Security Events",
252
+ userPreferencesKey: PREFS_KEY,
253
+ cardProps: { title: "Security Events" },
254
+ columns: options.columns || makeColumns(),
255
+ filters: [],
256
+ isDeleteable: false,
257
+ isCreateable: false,
258
+ isViewable: false,
259
+ isEditable: false,
260
+ disableUrlState: true,
261
+ callbacks: makeCallbacks(),
262
+ ...(options.withoutAttributeColumns
263
+ ? {}
264
+ : {
265
+ attributeColumnsProps: {
266
+ columnKey: options.attributeColumnKey || "attributes",
267
+ title: "Add Attribute Column",
268
+ emptyMessage: "No attributes seen on recent events.",
269
+ fetchAttributeKeys: (): Promise<Array<string>> => {
270
+ fetchCallCount++;
271
+ return fetchResult();
272
+ },
273
+ },
274
+ }),
275
+ } as unknown as BaseModelTableProps<SecurityEvent>;
276
+
277
+ return render(<BaseModelTable<SecurityEvent> {...props} />);
278
+ };
279
+
280
+ type WaitForTableFunction = () => Promise<void>;
281
+
282
+ const waitForTable: WaitForTableFunction = async (): Promise<void> => {
283
+ await waitFor(() => {
284
+ expect(calls.length).toBeGreaterThan(0);
285
+ });
286
+
287
+ await waitFor(() => {
288
+ expect(screen.getAllByRole("columnheader").length).toBeGreaterThan(0);
289
+ });
290
+ };
291
+
292
+ type ClickFunction = (element: HTMLElement) => Promise<void>;
293
+
294
+ const click: ClickFunction = async (element: HTMLElement): Promise<void> => {
295
+ await act(async () => {
296
+ fireEvent.click(element);
297
+ });
298
+ };
299
+
300
+ type OpenPickerFunction = () => Promise<void>;
301
+
302
+ const openPicker: OpenPickerFunction = async (): Promise<void> => {
303
+ await click(screen.getByRole("button", { name: "More options" }));
304
+ await click(screen.getByText("Columns"));
305
+
306
+ await waitFor(() => {
307
+ expect(screen.queryByTestId("column-customization-modal")).not.toBeNull();
308
+ });
309
+ };
310
+
311
+ type SavePickerFunction = () => Promise<void>;
312
+
313
+ const savePicker: SavePickerFunction = async (): Promise<void> => {
314
+ await click(screen.getByTestId("modal-footer-submit-button"));
315
+
316
+ await waitFor(() => {
317
+ expect(screen.queryByTestId("column-customization-modal")).toBeNull();
318
+ });
319
+ };
320
+
321
+ type WaitForPoolFunction = () => Promise<void>;
322
+
323
+ const waitForPool: WaitForPoolFunction = async (): Promise<void> => {
324
+ await waitFor(() => {
325
+ expect(
326
+ screen.queryByTestId("add-column-attributes.device.hostname"),
327
+ ).not.toBeNull();
328
+ });
329
+ };
330
+
331
+ beforeEach(() => {
332
+ calls = [];
333
+ rows = [];
334
+ fetchCallCount = 0;
335
+ fetchResult = async () => {
336
+ return ATTRIBUTE_KEYS;
337
+ };
338
+ window.history.replaceState(
339
+ window.history.state,
340
+ "",
341
+ "/dashboard/security-events",
342
+ );
343
+ TableFilterUrlState.resetClaimedKeys();
344
+ window.localStorage.clear();
345
+ });
346
+
347
+ afterEach(() => {
348
+ cleanup();
349
+ jest.restoreAllMocks();
350
+ });
351
+
352
+ describe("the map column is always requested", () => {
353
+ /*
354
+ * The table does not refetch when its column set changes, so a column the
355
+ * viewer adds from the picker appears against rows that were fetched
356
+ * before it existed. If the map were only selected because a column reads
357
+ * it, every freshly added attribute column would render blank until
358
+ * something else happened to reload the page.
359
+ */
360
+ test("the very first request selects the map column, with no attribute columns yet", async () => {
361
+ renderTable();
362
+
363
+ await waitForTable();
364
+
365
+ expect(calls[0]!.select["attributes"]).toBe(true);
366
+ });
367
+
368
+ test("a table that did not opt in does not select the map column", async () => {
369
+ renderTable({ withoutAttributeColumns: true });
370
+
371
+ await waitForTable();
372
+
373
+ expect(calls[0]!.select["attributes"]).toBeUndefined();
374
+ });
375
+
376
+ /*
377
+ * A misconfigured columnKey must cost the feature, not the page:
378
+ * getSelectFromColumns throws a BadDataException for a field the model
379
+ * does not have, which would blank the whole table.
380
+ */
381
+ test("a column key that is not on the model is ignored rather than fatal", async () => {
382
+ renderTable({ attributeColumnKey: "notAColumn" });
383
+
384
+ await waitForTable();
385
+
386
+ expect(getHeaders()).toContain("Message");
387
+ expect(calls[0]!.select["notAColumn"]).toBeUndefined();
388
+ });
389
+ });
390
+
391
+ describe("the key pool", () => {
392
+ test("is not fetched on mount", async () => {
393
+ renderTable();
394
+
395
+ await waitForTable();
396
+
397
+ expect(fetchCallCount).toBe(0);
398
+ });
399
+
400
+ test("is fetched when the picker opens, and offered as searchable rows", async () => {
401
+ renderTable();
402
+
403
+ await waitForTable();
404
+ await openPicker();
405
+ await waitForPool();
406
+
407
+ expect(fetchCallCount).toBe(1);
408
+ expect(screen.getAllByTestId(/^add-column-attributes\./)).toHaveLength(
409
+ ATTRIBUTE_KEYS.length,
410
+ );
411
+ });
412
+
413
+ test("is fetched once, not again on every open", async () => {
414
+ renderTable();
415
+
416
+ await waitForTable();
417
+ await openPicker();
418
+ await waitForPool();
419
+ await click(screen.getByTestId("modal-footer-close-button"));
420
+ await openPicker();
421
+ await waitForPool();
422
+
423
+ expect(fetchCallCount).toBe(1);
424
+ });
425
+
426
+ test("is normalized: blanks dropped, duplicates collapsed, sorted", async () => {
427
+ fetchResult = async () => {
428
+ return ["zeta", " alpha ", "", "alpha"] as Array<string>;
429
+ };
430
+
431
+ renderTable();
432
+
433
+ await waitForTable();
434
+ await openPicker();
435
+
436
+ await waitFor(() => {
437
+ expect(
438
+ screen.queryByTestId("add-column-attributes.alpha"),
439
+ ).not.toBeNull();
440
+ });
441
+
442
+ const offered: Array<string> = screen
443
+ .getAllByTestId(/^add-column-attributes\./)
444
+ .map((element: HTMLElement) => {
445
+ return (element.getAttribute("data-testid") || "").replace(
446
+ "add-column-attributes.",
447
+ "",
448
+ );
449
+ });
450
+
451
+ expect(offered).toEqual(["alpha", "zeta"]);
452
+ });
453
+
454
+ /*
455
+ * A failed pool costs the search box and nothing else. It must not take
456
+ * the picker, or the columns already added, down with it.
457
+ */
458
+ test("a failure is surfaced without breaking the rest of the picker", async () => {
459
+ fetchResult = async () => {
460
+ throw new Error("clickhouse said no");
461
+ };
462
+
463
+ renderTable();
464
+
465
+ await waitForTable();
466
+ await openPicker();
467
+
468
+ await waitFor(() => {
469
+ expect(screen.queryByTestId("add-column-error")).not.toBeNull();
470
+ });
471
+
472
+ expect(screen.getByTestId("add-column-error")).toHaveTextContent(
473
+ "clickhouse said no",
474
+ );
475
+ // The ordinary checklist is untouched.
476
+ expect(screen.getByTestId("column-toggle-message")).toBeInTheDocument();
477
+ });
478
+
479
+ test("a failed fetch is retried the next time the picker opens", async () => {
480
+ fetchResult = async () => {
481
+ throw new Error("clickhouse said no");
482
+ };
483
+
484
+ renderTable();
485
+
486
+ await waitForTable();
487
+ await openPicker();
488
+
489
+ await waitFor(() => {
490
+ expect(screen.queryByTestId("add-column-error")).not.toBeNull();
491
+ });
492
+
493
+ fetchResult = async () => {
494
+ return ATTRIBUTE_KEYS;
495
+ };
496
+
497
+ await click(screen.getByTestId("modal-footer-close-button"));
498
+ await openPicker();
499
+ await waitForPool();
500
+
501
+ expect(fetchCallCount).toBe(2);
502
+ });
503
+
504
+ test("an empty pool says so in the caller's words", async () => {
505
+ fetchResult = async () => {
506
+ return [];
507
+ };
508
+
509
+ renderTable();
510
+
511
+ await waitForTable();
512
+ await openPicker();
513
+
514
+ await waitFor(() => {
515
+ expect(screen.queryByTestId("add-column-empty")).not.toBeNull();
516
+ });
517
+
518
+ expect(screen.getByTestId("add-column-empty")).toHaveTextContent(
519
+ "No attributes seen on recent events.",
520
+ );
521
+ });
522
+ });
523
+
524
+ describe("adding a column from the picker", () => {
525
+ test("renders it as a new header once saved", async () => {
526
+ renderTable();
527
+
528
+ await waitForTable();
529
+
530
+ expect(getHeaders()).toEqual(["Message", "Severity"]);
531
+
532
+ await openPicker();
533
+ await waitForPool();
534
+ await click(screen.getByTestId("add-column-attributes.device.hostname"));
535
+ await savePicker();
536
+
537
+ await waitFor(() => {
538
+ expect(getHeaders()).toContain("device.hostname");
539
+ });
540
+
541
+ expect(getHeaders()).toEqual(["Message", "Severity", "device.hostname"]);
542
+ });
543
+
544
+ test("renders the value out of the row's map", async () => {
545
+ rows = [
546
+ {
547
+ _id: "1",
548
+ message: "Suspicious logon",
549
+ severityName: "High",
550
+ attributes: { "device.hostname": "web-1", class_uid: "3002" },
551
+ },
552
+ ];
553
+
554
+ renderTable();
555
+
556
+ await waitForTable();
557
+ await openPicker();
558
+ await waitForPool();
559
+ await click(screen.getByTestId("add-column-attributes.device.hostname"));
560
+ await savePicker();
561
+
562
+ await waitFor(() => {
563
+ expect(screen.queryByText("web-1")).not.toBeNull();
564
+ });
565
+ });
566
+
567
+ test("stores the choice so it survives a reload", async () => {
568
+ renderTable();
569
+
570
+ await waitForTable();
571
+ await openPicker();
572
+ await waitForPool();
573
+ await click(screen.getByTestId("add-column-attributes.device.hostname"));
574
+ await savePicker();
575
+
576
+ const stored: JSONObject | null = readPreference();
577
+
578
+ expect(stored).not.toBeNull();
579
+ expect(stored!["order"]).toContain("attributes.device.hostname");
580
+ });
581
+
582
+ test("a stored attribute column is rebuilt on the next mount", async () => {
583
+ seedPreference({
584
+ order: ["message", "severityName", "attributes.device.hostname"],
585
+ hidden: [],
586
+ });
587
+
588
+ rows = [
589
+ {
590
+ _id: "1",
591
+ message: "Suspicious logon",
592
+ severityName: "High",
593
+ attributes: { "device.hostname": "web-1" },
594
+ },
595
+ ];
596
+
597
+ renderTable();
598
+
599
+ await waitForTable();
600
+
601
+ /*
602
+ * Rebuilt during render off the stored layout - not after an effect, and
603
+ * without waiting on the key pool, which is not even fetched yet.
604
+ */
605
+ expect(getHeaders()).toEqual(["Message", "Severity", "device.hostname"]);
606
+ expect(fetchCallCount).toBe(0);
607
+
608
+ // Headers render off the column set; the cells wait on the first page.
609
+ await waitFor(() => {
610
+ expect(screen.queryByText("web-1")).not.toBeNull();
611
+ });
612
+ });
613
+
614
+ test("a stored attribute column that is also hidden stays a column", async () => {
615
+ seedPreference({
616
+ order: ["message", "severityName", "attributes.device.hostname"],
617
+ hidden: ["attributes.device.hostname"],
618
+ });
619
+
620
+ renderTable();
621
+
622
+ await waitForTable();
623
+
624
+ expect(getHeaders()).toEqual(["Message", "Severity"]);
625
+
626
+ await openPicker();
627
+
628
+ // Still in the picker, so switching it back on is one click.
629
+ expect(
630
+ screen.getByTestId("column-toggle-attributes.device.hostname"),
631
+ ).not.toBeChecked();
632
+ });
633
+
634
+ test("adding several keeps them in the order they were added", async () => {
635
+ renderTable();
636
+
637
+ await waitForTable();
638
+ await openPicker();
639
+ await waitForPool();
640
+ await click(screen.getByTestId("add-column-attributes.device.hostname"));
641
+ await click(screen.getByTestId("add-column-attributes.class_uid"));
642
+ await savePicker();
643
+
644
+ await waitFor(() => {
645
+ expect(getHeaders()).toContain("class_uid");
646
+ });
647
+
648
+ expect(getHeaders()).toEqual([
649
+ "Message",
650
+ "Severity",
651
+ "device.hostname",
652
+ "class_uid",
653
+ ]);
654
+ });
655
+
656
+ test("an added column can be reordered like any other", async () => {
657
+ renderTable();
658
+
659
+ await waitForTable();
660
+ await openPicker();
661
+ await waitForPool();
662
+ await click(screen.getByTestId("add-column-attributes.device.hostname"));
663
+ await click(
664
+ screen.getByTestId("column-move-up-attributes.device.hostname"),
665
+ );
666
+ await savePicker();
667
+
668
+ await waitFor(() => {
669
+ expect(getHeaders()).toContain("device.hostname");
670
+ });
671
+
672
+ /*
673
+ * "Event UID" is hidden by default, so it sits between Severity and the
674
+ * added column in the picker but never reaches the header row.
675
+ */
676
+ expect(getHeaders()).toEqual(["Message", "Severity", "device.hostname"]);
677
+
678
+ expect(readPreference()!["order"]).toEqual([
679
+ "message",
680
+ "severityName",
681
+ "attributes.device.hostname",
682
+ "eventUid",
683
+ ]);
684
+ });
685
+ });
686
+
687
+ describe("removing an attribute column", () => {
688
+ type SeedAndRenderFunction = () => Promise<void>;
689
+
690
+ const seedAndRender: SeedAndRenderFunction = async (): Promise<void> => {
691
+ seedPreference({
692
+ order: ["message", "severityName", "attributes.device.hostname"],
693
+ hidden: [],
694
+ });
695
+
696
+ renderTable();
697
+
698
+ await waitForTable();
699
+ };
700
+
701
+ test("takes the header away", async () => {
702
+ await seedAndRender();
703
+
704
+ expect(getHeaders()).toContain("device.hostname");
705
+
706
+ await openPicker();
707
+ await click(
708
+ screen.getByTestId("column-remove-attributes.device.hostname"),
709
+ );
710
+ await savePicker();
711
+
712
+ await waitFor(() => {
713
+ expect(getHeaders()).not.toContain("device.hostname");
714
+ });
715
+ });
716
+
717
+ test("takes it out of the stored layout, not just out of view", async () => {
718
+ await seedAndRender();
719
+
720
+ await openPicker();
721
+ await click(
722
+ screen.getByTestId("column-remove-attributes.device.hostname"),
723
+ );
724
+ await savePicker();
725
+
726
+ /*
727
+ * With nothing left that differs from the table's own layout, the stored
728
+ * preference is cleared entirely rather than pinning this release's
729
+ * column set forever.
730
+ */
731
+ expect(readPreference()).toBeNull();
732
+ });
733
+
734
+ test("a removed column goes back into the pool it came from", async () => {
735
+ await seedAndRender();
736
+
737
+ await openPicker();
738
+
739
+ /*
740
+ * Waits on a DIFFERENT key: device.hostname is already a column, so it
741
+ * is precisely the one the pool must not be offering yet.
742
+ */
743
+ await waitFor(() => {
744
+ expect(
745
+ screen.queryByTestId("add-column-attributes.class_uid"),
746
+ ).not.toBeNull();
747
+ });
748
+
749
+ expect(
750
+ screen.queryByTestId("add-column-attributes.device.hostname"),
751
+ ).toBeNull();
752
+
753
+ await click(
754
+ screen.getByTestId("column-remove-attributes.device.hostname"),
755
+ );
756
+
757
+ expect(
758
+ screen.getByTestId("add-column-attributes.device.hostname"),
759
+ ).toBeInTheDocument();
760
+ });
761
+
762
+ test("declared columns have no remove button at all", async () => {
763
+ await seedAndRender();
764
+
765
+ await openPicker();
766
+
767
+ expect(screen.queryByTestId("column-remove-message")).toBeNull();
768
+ expect(screen.queryByTestId("column-remove-severityName")).toBeNull();
769
+ });
770
+
771
+ test("Reset to default drops every attribute column", async () => {
772
+ await seedAndRender();
773
+
774
+ await openPicker();
775
+ await click(screen.getByTestId("column-customization-reset"));
776
+
777
+ await waitFor(() => {
778
+ expect(getHeaders()).not.toContain("device.hostname");
779
+ });
780
+
781
+ expect(readPreference()).toBeNull();
782
+ expect(getHeaders()).toEqual(["Message", "Severity"]);
783
+ });
784
+ });
785
+
786
+ describe("stored layouts that name things this table does not have", () => {
787
+ /*
788
+ * Attribute keys are recovered from the RAW stored layout rather than the
789
+ * sanitized one, which is the only way a column can exist because its id
790
+ * is in there. The flip side is that a key nobody has ingested for a while
791
+ * still rebuilds - deliberately: it renders empty cells the viewer can see
792
+ * and remove, rather than silently discarding a column they chose.
793
+ */
794
+ test("a key the pool no longer knows about still rebuilds its column", async () => {
795
+ fetchResult = async () => {
796
+ return ["class_uid"];
797
+ };
798
+
799
+ seedPreference({
800
+ order: ["message", "severityName", "attributes.long.gone"],
801
+ hidden: [],
802
+ });
803
+
804
+ renderTable();
805
+
806
+ await waitForTable();
807
+
808
+ expect(getHeaders()).toContain("long.gone");
809
+ });
810
+
811
+ test("an unknown non-attribute id is still dropped", async () => {
812
+ seedPreference({
813
+ order: ["message", "severityName", "someColumnWeRemoved"],
814
+ hidden: ["someColumnWeRemoved"],
815
+ });
816
+
817
+ renderTable();
818
+
819
+ await waitForTable();
820
+
821
+ expect(getHeaders()).toEqual(["Message", "Severity"]);
822
+ });
823
+ });
824
+ });