@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
@@ -303,6 +303,24 @@ export class Service extends DatabaseService<Model> {
303
303
  await this.applySiteAssignmentRulesToDevice(createdItem.id!);
304
304
  }
305
305
  })
306
+ .then(async () => {
307
+ /*
308
+ * A device created monitor-backed adopts its monitor's CURRENT
309
+ * status right away rather than waiting for that monitor's next
310
+ * status CHANGE. Skipped for SNMP devices, which are judged by
311
+ * their own walk.
312
+ */
313
+ if (
314
+ NetworkDeviceMonitoringMethodUtil.isMonitorBacked(
315
+ createdItem.monitoringMethod,
316
+ )
317
+ ) {
318
+ await this.refreshStampedMonitorStatus({
319
+ deviceId: createdItem.id!,
320
+ clearWhenNotMonitorBacked: false,
321
+ });
322
+ }
323
+ })
306
324
  .catch((error: Error) => {
307
325
  logger.error(
308
326
  `Error applying network device rules in NetworkDeviceService.onCreateSuccess: ${error}`,
@@ -453,6 +471,109 @@ export class Service extends DatabaseService<Model> {
453
471
  };
454
472
  }
455
473
 
474
+ /*
475
+ * Re-derives one device's stamped `currentMonitorStatusId` from its
476
+ * binding, and refreshes its site chain if the stamp moved.
477
+ *
478
+ * The stamp is what every monitor-backed surface reads — the device list
479
+ * pill, the site rollup, the topology node — and until this existed the
480
+ * ONLY thing that ever wrote it was
481
+ * `NetworkSiteService.onMonitorStatusChanged`, which fires on a monitor's
482
+ * next status CHANGE. Bind a device to a Ping monitor that is already Up
483
+ * and staying Up and nothing writes the stamp at all, so the device sits
484
+ * on "Pending" until the monitor happens to go down — which is
485
+ * OneUptime/oneuptime#3392. Binding is itself an event that decides the
486
+ * device's status, so it stamps here.
487
+ *
488
+ * `clearWhenNotMonitorBacked` is for the write that moves a device OFF
489
+ * monitor-backed: the ping monitor's verdict must not outlive the binding
490
+ * (DeviceHealthStateUtil lets a stamped status beat reachability, so a
491
+ * stale one would poison the site rollup of a device that is now walked).
492
+ * It is deliberately NOT set when a write only touches the monitor
493
+ * binding of an SNMP device, because an SNMP device's stamp comes from
494
+ * the Network Device monitor that watches it, and that binding lives in
495
+ * the monitor's step data rather than in this column.
496
+ */
497
+ @CaptureSpan()
498
+ public async refreshStampedMonitorStatus(data: {
499
+ deviceId: ObjectID;
500
+ clearWhenNotMonitorBacked: boolean;
501
+ }): Promise<void> {
502
+ const device: Model | null = await this.findOneById({
503
+ id: data.deviceId,
504
+ select: {
505
+ _id: true,
506
+ projectId: true,
507
+ siteId: true,
508
+ monitoringMethod: true,
509
+ monitorId: true,
510
+ currentMonitorStatusId: true,
511
+ },
512
+ props: {
513
+ isRoot: true,
514
+ },
515
+ });
516
+
517
+ if (!device || !device.projectId) {
518
+ return;
519
+ }
520
+
521
+ const isMonitorBacked: boolean =
522
+ NetworkDeviceMonitoringMethodUtil.isMonitorBacked(
523
+ device.monitoringMethod,
524
+ );
525
+
526
+ if (!isMonitorBacked && !data.clearWhenNotMonitorBacked) {
527
+ return;
528
+ }
529
+
530
+ let monitorStatusId: ObjectID | null = null;
531
+
532
+ if (isMonitorBacked && device.monitorId) {
533
+ /*
534
+ * Scoped to the device's project on the read as well as on the write
535
+ * path in onBeforeCreate/onBeforeUpdate: a monitor from another
536
+ * tenant must never be able to stamp a status here.
537
+ */
538
+ const monitor: Monitor | null = await MonitorService.findOneBy({
539
+ query: {
540
+ _id: device.monitorId,
541
+ projectId: device.projectId,
542
+ },
543
+ select: {
544
+ _id: true,
545
+ currentMonitorStatusId: true,
546
+ },
547
+ props: {
548
+ isRoot: true,
549
+ },
550
+ });
551
+
552
+ monitorStatusId = monitor?.currentMonitorStatusId || null;
553
+ }
554
+
555
+ const currentStampId: string | null =
556
+ device.currentMonitorStatusId?.toString() || null;
557
+ const nextStampId: string | null = monitorStatusId?.toString() || null;
558
+
559
+ if (currentStampId === nextStampId) {
560
+ return;
561
+ }
562
+
563
+ await this.updateColumnsByIdWithoutHooks({
564
+ id: data.deviceId,
565
+ data: {
566
+ currentMonitorStatusId: monitorStatusId,
567
+ },
568
+ });
569
+
570
+ if (device.siteId) {
571
+ await NetworkSiteService.recomputeRollupForSiteAndAncestors(
572
+ device.siteId,
573
+ );
574
+ }
575
+ }
576
+
456
577
  /*
457
578
  * Site maintenance after device updates. Resilient by design: a rollup
458
579
  * or rule-engine failure must never fail the device update itself.
@@ -462,6 +583,33 @@ export class Service extends DatabaseService<Model> {
462
583
  onUpdate: OnUpdate<Model>,
463
584
  updatedItemIds: Array<ObjectID>,
464
585
  ): Promise<OnUpdate<Model>> {
586
+ /*
587
+ * Re-stamp before the site maintenance below, and in its own try: the
588
+ * two are independent, and a rollup failure must not cost the device
589
+ * its status (nor the other way round).
590
+ */
591
+ try {
592
+ const dataKeys: Array<string> = Object.keys(onUpdate.updateBy.data || {});
593
+ const isMethodWrite: boolean = dataKeys.includes("monitoringMethod");
594
+ const isMonitorWrite: boolean = RelationIdUtil.isWritten(
595
+ dataKeys,
596
+ MONITOR_KEYS,
597
+ );
598
+
599
+ if (isMethodWrite || isMonitorWrite) {
600
+ for (const deviceId of updatedItemIds) {
601
+ await this.refreshStampedMonitorStatus({
602
+ deviceId: deviceId,
603
+ clearWhenNotMonitorBacked: isMethodWrite,
604
+ });
605
+ }
606
+ }
607
+ } catch (error) {
608
+ logger.error(
609
+ `Error in NetworkDeviceService.onUpdateSuccess monitor status refresh: ${error}`,
610
+ );
611
+ }
612
+
465
613
  try {
466
614
  const dataKeys: Array<string> = Object.keys(onUpdate.updateBy.data || {});
467
615
 
@@ -31,6 +31,48 @@ export class Service extends DatabaseService<Model> {
31
31
  super(Model);
32
32
  }
33
33
 
34
+ /**
35
+ * Re-resolve and persist the schedule's roster after it is attached to, or
36
+ * detached from, an on-call policy.
37
+ *
38
+ * The persisted roster columns are derived state, and one of their inputs is
39
+ * WHICH policies escalate to the schedule: a schedule attached to exactly one
40
+ * policy is resolved in that policy's context, so its policy-scoped user
41
+ * overrides count, and a schedule attached to none or to several is resolved
42
+ * policy-blind (OnCallDutyPolicyScheduleService.getSingleAttachedPolicyId).
43
+ * Attaching or detaching therefore changes the correct answer, and every other
44
+ * input — layers, layer users, overrides — already refreshes on change.
45
+ *
46
+ * Without this the roster stayed on the pre-attach answer until the next
47
+ * natural hand-off, so the schedule page's "X is currently on the roster"
48
+ * banner and the "On Call Now" column named the originally-scheduled user for
49
+ * the whole override window while alert routing paged the substitute.
50
+ * https://github.com/OneUptime/oneuptime/issues/3411
51
+ *
52
+ * Best-effort: an attach must not fail because a roster could not be
53
+ * recomputed, and the refresh is idempotent (an unaffected schedule simply
54
+ * resolves to the same roster and diffs to "no change", so no duplicate
55
+ * hand-off notification is sent).
56
+ */
57
+ private async refreshScheduleRoster(
58
+ onCallDutyPolicyScheduleId: ObjectID | null | undefined,
59
+ ): Promise<void> {
60
+ if (!onCallDutyPolicyScheduleId) {
61
+ return;
62
+ }
63
+
64
+ try {
65
+ await OnCallDutyPolicyScheduleService.refreshCurrentUserIdAndHandoffTimeInSchedule(
66
+ onCallDutyPolicyScheduleId,
67
+ );
68
+ } catch (err) {
69
+ logger.error(
70
+ "Error refreshing the roster after an on-call schedule was attached to or detached from a policy (best-effort).",
71
+ );
72
+ logger.error(err);
73
+ }
74
+ }
75
+
34
76
  protected override async onCreateSuccess(
35
77
  _onCreate: OnCreate<Model>,
36
78
  createdItem: Model,
@@ -75,6 +117,12 @@ export class Service extends DatabaseService<Model> {
75
117
  );
76
118
  }
77
119
 
120
+ /*
121
+ * Bring the persisted roster in line with the new policy attachment before
122
+ * anything below can return early. See refreshScheduleRoster.
123
+ */
124
+ await this.refreshScheduleRoster(createdModel.onCallDutyPolicyScheduleId);
125
+
78
126
  // send notification to the new current user.
79
127
 
80
128
  const userOnSchedule: ObjectID | null =
@@ -324,6 +372,23 @@ export class Service extends DatabaseService<Model> {
324
372
  ): Promise<OnDelete<Model>> {
325
373
  const deletedItems: Array<Model> = onDelete.carryForward.deletedItems;
326
374
 
375
+ /*
376
+ * Detaching changes the schedule's policy context just as attaching does, so
377
+ * its roster has to be re-resolved here too. Deduped by schedule: removing a
378
+ * schedule from several escalation rules of one policy is one context
379
+ * change, not several. See refreshScheduleRoster.
380
+ */
381
+ const refreshedScheduleIds: Set<string> = new Set<string>();
382
+ for (const deletedItem of deletedItems) {
383
+ const scheduleId: string | undefined =
384
+ deletedItem.onCallDutyPolicyScheduleId?.toString();
385
+ if (!scheduleId || refreshedScheduleIds.has(scheduleId)) {
386
+ continue;
387
+ }
388
+ refreshedScheduleIds.add(scheduleId);
389
+ await this.refreshScheduleRoster(deletedItem.onCallDutyPolicyScheduleId);
390
+ }
391
+
327
392
  for (const deletedItem of deletedItems) {
328
393
  const userOnSchedule: ObjectID | null =
329
394
  await OnCallDutyPolicyScheduleService.getCurrentUserIdInSchedule(
@@ -19,7 +19,7 @@ export class Service extends DatabaseService<OnCallDutyPolicyFeed> {
19
19
  super(OnCallDutyPolicyFeed);
20
20
 
21
21
  if (IsBillingEnabled) {
22
- this.hardDeleteItemsOlderThanInDays("createdAt", 90);
22
+ this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
23
23
  }
24
24
  }
25
25
 
@@ -16,8 +16,14 @@ import UpdateBy from "../Types/Database/UpdateBy";
16
16
  import logger, { LogAttributes } from "../Utils/Logger";
17
17
  import Errors from "../Utils/Errors";
18
18
  import ProductAnalytics from "../Utils/ProductAnalytics";
19
- import MarketingEventUtil from "../Utils/Marketing/MarketingEventUtil";
19
+ import MarketingEventUtil, {
20
+ utmAnalyticsProperties,
21
+ } from "../Utils/Marketing/MarketingEventUtil";
20
22
  import { MarketingEventType } from "../../Types/Marketing/MarketingEvent";
23
+ import {
24
+ UtmPropertyKeys,
25
+ UtmUrlPropertyKey,
26
+ } from "../../Types/Marketing/Attribution";
21
27
  import SessionReplayGateCacheStore from "../Utils/SessionReplay/SessionReplayGateCacheStore";
22
28
  import AccessTokenService from "./AccessTokenService";
23
29
  import BillingService from "./BillingService";
@@ -198,6 +204,10 @@ export class ProjectService extends DatabaseService<Model> {
198
204
  utmMedium: true,
199
205
  utmTerm: true,
200
206
  utmContent: true,
207
+ utmId: true,
208
+ utmSourcePlatform: true,
209
+ utmCreativeFormat: true,
210
+ utmMarketingTactic: true,
201
211
  utmUrl: true,
202
212
  clickIds: true,
203
213
  firstTouchAttribution: true,
@@ -348,13 +358,17 @@ export class ProjectService extends DatabaseService<Model> {
348
358
  data.data.createdOwnerPhone = user.companyPhoneNumber!;
349
359
  data.data.createdOwnerCompanyName = user.companyName!;
350
360
 
351
- // UTM info.
352
- data.data.utmCampaign = user.utmCampaign!;
353
- data.data.utmSource = user.utmSource!;
354
- data.data.utmMedium = user.utmMedium!;
355
- data.data.utmTerm = user.utmTerm!;
356
- data.data.utmContent = user.utmContent!;
357
- data.data.utmUrl = user.utmUrl!;
361
+ /*
362
+ * UTM info, copied field by field off the shared contract rather than
363
+ * hand-listed. Hand-listing is how Project ended up with four columns
364
+ * (utmId and its GA4 siblings) that the schema had but nothing ever wrote.
365
+ */
366
+ const projectRow: JSONObject = data.data as unknown as JSONObject;
367
+ const userRow: JSONObject = user as unknown as JSONObject;
368
+
369
+ for (const propertyKey of [...UtmPropertyKeys, UtmUrlPropertyKey]) {
370
+ projectRow[propertyKey] = userRow[propertyKey];
371
+ }
358
372
 
359
373
  // Ad attribution info (click IDs + first touch).
360
374
  data.data.clickIds = user.clickIds!;
@@ -572,6 +586,10 @@ export class ProjectService extends DatabaseService<Model> {
572
586
  utmCampaign: true,
573
587
  utmTerm: true,
574
588
  utmContent: true,
589
+ utmId: true,
590
+ utmSourcePlatform: true,
591
+ utmCreativeFormat: true,
592
+ utmMarketingTactic: true,
575
593
  utmUrl: true,
576
594
  clickIds: true,
577
595
  firstTouchAttribution: true,
@@ -950,11 +968,7 @@ These are no longer recorded against the project and have to be cancelled by han
950
968
  */
951
969
  is_paid_conversion: true,
952
970
  has_custom_pricing: data.plan.isCustomPricing(),
953
- utm_source: data.project.utmSource || "",
954
- utm_medium: data.project.utmMedium || "",
955
- utm_campaign: data.project.utmCampaign || "",
956
- utm_term: data.project.utmTerm || "",
957
- utm_content: data.project.utmContent || "",
971
+ ...utmAnalyticsProperties(data.project as unknown as JSONObject),
958
972
  click_ids: data.project.clickIds || {},
959
973
  };
960
974
 
@@ -1065,9 +1079,7 @@ These are no longer recorded against the project and have to be cancelled by han
1065
1079
  oldPlanOrder !== null && newPlanOrder === oldPlanOrder,
1066
1080
  is_paid_conversion: oldMonthlyAmountInUSD === 0 && newPlanIsPaid,
1067
1081
  has_custom_pricing: data.newPlan.isCustomPricing(),
1068
- utm_source: data.project.utmSource || "",
1069
- utm_medium: data.project.utmMedium || "",
1070
- utm_campaign: data.project.utmCampaign || "",
1082
+ ...utmAnalyticsProperties(data.project as unknown as JSONObject),
1071
1083
  click_ids: data.project.clickIds || {},
1072
1084
  };
1073
1085
 
@@ -1436,9 +1448,7 @@ These are no longer recorded against the project and have to be cancelled by han
1436
1448
  project_id: createdItem.id?.toString() || "",
1437
1449
  project_name: createdItem.name?.toString() || "",
1438
1450
  plan: createdItem.planName?.toString() || "",
1439
- utm_source: createdItem.utmSource || "",
1440
- utm_medium: createdItem.utmMedium || "",
1441
- utm_campaign: createdItem.utmCampaign || "",
1451
+ ...utmAnalyticsProperties(createdItem as unknown as JSONObject),
1442
1452
  click_ids: createdItem.clickIds || {},
1443
1453
  },
1444
1454
  });
@@ -13,10 +13,49 @@ import { getQuerySettings } from "../Utils/AnalyticsDatabase/QuerySettingsHelper
13
13
  export interface DetectionMatchGroup {
14
14
  groupValue: string;
15
15
  matchCount: number;
16
+ /*
17
+ * uniqExact of the rule's distinctCountField over the group's matches;
18
+ * 0 whenever the rule has no distinct-count field (the query emits a
19
+ * constant, not an aggregate, in that case).
20
+ */
21
+ distinctCount: number;
16
22
  sampleMessage: string;
17
23
  sampleObservables: Array<string>;
18
24
  }
19
25
 
26
+ export interface FindDetectionMatchesData {
27
+ projectId: ObjectID;
28
+ startTime: Date;
29
+ endTime: Date;
30
+ whereFragment: Statement;
31
+ groupByExpression: Statement | null;
32
+ distinctCountExpression: Statement | null;
33
+ minMatchCount: number;
34
+ maxGroups: number;
35
+ }
36
+
37
+ /*
38
+ * The in-process twin of the HAVING clause findDetectionMatches emits —
39
+ * exported so the evaluator's re-filter and the SQL builder live in one
40
+ * file, and a change to either side of the firing predicate is reviewed
41
+ * next to the other.
42
+ */
43
+ export function doesGroupMeetDetectionThreshold(data: {
44
+ group: DetectionMatchGroup;
45
+ usesDistinctCount: boolean;
46
+ minMatchCount: number;
47
+ }): boolean {
48
+ if (data.group.matchCount <= 0) {
49
+ return false;
50
+ }
51
+
52
+ const effectiveCount: number = data.usesDistinctCount
53
+ ? data.group.distinctCount
54
+ : data.group.matchCount;
55
+
56
+ return effectiveCount >= Math.max(1, data.minMatchCount);
57
+ }
58
+
20
59
  export class SecurityEventService extends AnalyticsDatabaseService<SecurityEvent> {
21
60
  public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) {
22
61
  super({ modelType: SecurityEvent, database: clickhouseDatabase });
@@ -24,18 +63,21 @@ export class SecurityEventService extends AnalyticsDatabaseService<SecurityEvent
24
63
 
25
64
  /*
26
65
  * Detection-engine query: count matching events in a window, grouped by
27
- * an optional field expression. `whereFragment` and
28
- * `groupByExpression` are Statements built by SigmaClickhouseCompiler —
29
- * every rule-controlled value inside them is a bound parameter.
66
+ * an optional field expression. `whereFragment`, `groupByExpression`
67
+ * and `distinctCountExpression` are Statements built by
68
+ * SigmaClickhouseCompiler — every rule-controlled value inside them is
69
+ * a bound parameter.
70
+ *
71
+ * When distinctCountExpression is set, the threshold applies to
72
+ * uniqExact of that field, not the raw match count — and empty values
73
+ * are excluded via nullIf, so "5 distinct usernames" never counts rows
74
+ * that carry no username. The threshold is enforced in HAVING so the
75
+ * maxGroups LIMIT keeps only qualifying groups instead of letting
76
+ * noisy-but-under-threshold groups crowd out real ones.
30
77
  */
31
- public async findDetectionMatches(data: {
32
- projectId: ObjectID;
33
- startTime: Date;
34
- endTime: Date;
35
- whereFragment: Statement;
36
- groupByExpression: Statement | null;
37
- maxGroups: number;
38
- }): Promise<Array<DetectionMatchGroup>> {
78
+ public async findDetectionMatches(
79
+ data: FindDetectionMatchesData,
80
+ ): Promise<Array<DetectionMatchGroup>> {
39
81
  if (!this.database) {
40
82
  this.useDefaultDatabase();
41
83
  }
@@ -51,8 +93,18 @@ export class SecurityEventService extends AnalyticsDatabaseService<SecurityEvent
51
93
  statement.append("''");
52
94
  }
53
95
 
96
+ statement.append(" AS groupValue, count() AS matchCount, ");
97
+
98
+ if (data.distinctCountExpression) {
99
+ statement.append("uniqExact(nullIf(");
100
+ statement.append(data.distinctCountExpression);
101
+ statement.append(", ''))");
102
+ } else {
103
+ statement.append("0");
104
+ }
105
+
54
106
  statement.append(
55
- ` AS groupValue, count() AS matchCount, any(message) AS sampleMessage, arrayDistinct(arrayFlatten(groupArray(20)(observables))) AS sampleObservables FROM ${databaseName}.${this.model.tableName} WHERE `,
107
+ ` AS distinctCount, any(message) AS sampleMessage, arrayDistinct(arrayFlatten(groupArray(20)(observables))) AS sampleObservables FROM ${databaseName}.${this.model.tableName} WHERE `,
56
108
  );
57
109
 
58
110
  statement.append(
@@ -69,7 +121,31 @@ export class SecurityEventService extends AnalyticsDatabaseService<SecurityEvent
69
121
  );
70
122
 
71
123
  statement.append(data.whereFragment);
72
- statement.append(") GROUP BY groupValue ORDER BY matchCount DESC LIMIT ");
124
+ statement.append(") GROUP BY groupValue");
125
+
126
+ /*
127
+ * The effective count — what the rule's threshold means — is the
128
+ * distinct count when a distinct expression was given, else the raw
129
+ * match count. A distinct-count rule always gets a HAVING clause,
130
+ * even at threshold 1: a group whose matches all lack the counted
131
+ * field has distinctCount 0 and must not fire.
132
+ */
133
+ const effectiveCountColumn: string = data.distinctCountExpression
134
+ ? "distinctCount"
135
+ : "matchCount";
136
+ const minMatchCount: number = Math.max(1, data.minMatchCount);
137
+
138
+ if (data.distinctCountExpression || minMatchCount > 1) {
139
+ statement.append(` HAVING ${effectiveCountColumn} >= `);
140
+ statement.append(
141
+ SQL`${{
142
+ type: TableColumnType.Number,
143
+ value: minMatchCount,
144
+ }}`,
145
+ );
146
+ }
147
+
148
+ statement.append(` ORDER BY ${effectiveCountColumn} DESC LIMIT `);
73
149
  statement.append(
74
150
  SQL`${{
75
151
  type: TableColumnType.Number,
@@ -88,6 +164,7 @@ export class SecurityEventService extends AnalyticsDatabaseService<SecurityEvent
88
164
  return {
89
165
  groupValue: String(row["groupValue"] ?? ""),
90
166
  matchCount: Number(row["matchCount"] || 0),
167
+ distinctCount: Number(row["distinctCount"] || 0),
91
168
  sampleMessage: String(row["sampleMessage"] ?? ""),
92
169
  sampleObservables: Array.isArray(row["sampleObservables"])
93
170
  ? (row["sampleObservables"] as Array<unknown>).map(