@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
@@ -154,6 +154,25 @@ export interface StatusPageResourceGridModel {
154
154
  orphanResources: Array<StatusPageResource>;
155
155
  }
156
156
 
157
+ /*
158
+ * How a group's checkbox column stands at the moment: whether the box at the
159
+ * top of the list should be ticked, half ticked, or empty.
160
+ *
161
+ * Computed over whichever resources the list is actually drawing - a filtered
162
+ * list's "select all" means "everything the search matched", not "everything in
163
+ * the group", because those are the only rows the operator can see.
164
+ */
165
+ export interface StatusPageResourceSelectionState {
166
+ /* Every resource on offer is selected, and there is at least one. */
167
+ isAllSelected: boolean;
168
+ /* Some but not all - the half-filled box. */
169
+ isIndeterminate: boolean;
170
+ /* How many of the resources on offer are selected. */
171
+ selectedCount: number;
172
+ /* How many resources were on offer at all. */
173
+ selectableCount: number;
174
+ }
175
+
157
176
  export default class StatusPageResourceExplorerUtil {
158
177
  /*
159
178
  * How many navigator rows are drawn before the list stops and offers to draw
@@ -622,6 +641,245 @@ export default class StatusPageResourceExplorerUtil {
622
641
  );
623
642
  }
624
643
 
644
+ /* ------------------------------------------------------------------ */
645
+ /* Selecting resources */
646
+ /* ------------------------------------------------------------------ */
647
+
648
+ /*
649
+ * Removing monitors from a status page used to be a row at a time: open the
650
+ * row's menu, confirm, wait for the refetch, and do it again for the next of
651
+ * twenty seven (issue #3419). The list carries a checkbox column instead, and
652
+ * everything below is the model behind it.
653
+ *
654
+ * A selection is a set of id STRINGS rather than of resources. Resources are
655
+ * re-fetched constantly here - every create, delete, reorder and refresh
656
+ * replaces the whole array - so holding the objects would mean holding rows
657
+ * that no longer exist, compared by identity against their own replacements.
658
+ */
659
+
660
+ /*
661
+ * A resource's id as a selection holds it.
662
+ *
663
+ * Null when there is nothing to hold on to. The server never returns such a
664
+ * row, but a list drawn from local state briefly can, and a set containing
665
+ * `""` would silently make every id-less row the same row.
666
+ */
667
+ public static getResourceId(
668
+ statusPageResource: StatusPageResource,
669
+ ): string | null {
670
+ const id: string = statusPageResource._id?.toString() || "";
671
+
672
+ return id.length > 0 ? id : null;
673
+ }
674
+
675
+ /*
676
+ * The ids of everything in the list that can be selected at all, in the order
677
+ * the list draws them and without repeats.
678
+ */
679
+ public static getResourceIds(
680
+ statusPageResources: Array<StatusPageResource>,
681
+ ): Array<string> {
682
+ const ids: Array<string> = [];
683
+ const seen: Set<string> = new Set<string>();
684
+
685
+ for (const statusPageResource of statusPageResources) {
686
+ const id: string | null =
687
+ StatusPageResourceExplorerUtil.getResourceId(statusPageResource);
688
+
689
+ if (!id || seen.has(id)) {
690
+ continue;
691
+ }
692
+
693
+ seen.add(id);
694
+ ids.push(id);
695
+ }
696
+
697
+ return ids;
698
+ }
699
+
700
+ /*
701
+ * One row's box, ticked or unticked. Always a new set, because it always
702
+ * changes something.
703
+ */
704
+ public static toggleSelectedResourceId(data: {
705
+ selectedResourceIds: Set<string>;
706
+ resourceId: string;
707
+ }): Set<string> {
708
+ const next: Set<string> = new Set<string>(data.selectedResourceIds);
709
+
710
+ if (next.has(data.resourceId)) {
711
+ next.delete(data.resourceId);
712
+ } else {
713
+ next.add(data.resourceId);
714
+ }
715
+
716
+ return next;
717
+ }
718
+
719
+ /*
720
+ * The box at the top of the list: every resource handed in is selected, or
721
+ * none of them is.
722
+ *
723
+ * Only the resources handed in are touched. A search is on while this is
724
+ * pressed as often as not, and un-ticking "select all" over four matching
725
+ * rows must not quietly drop the twenty the operator selected before they
726
+ * started typing.
727
+ *
728
+ * Returns the set it was given when nothing would change, so a component
729
+ * holding it in state does not re-render itself for a no-op.
730
+ */
731
+ public static setResourcesSelected(data: {
732
+ selectedResourceIds: Set<string>;
733
+ statusPageResources: Array<StatusPageResource>;
734
+ isSelected: boolean;
735
+ }): Set<string> {
736
+ const ids: Array<string> = StatusPageResourceExplorerUtil.getResourceIds(
737
+ data.statusPageResources,
738
+ );
739
+
740
+ const next: Set<string> = new Set<string>(data.selectedResourceIds);
741
+
742
+ let hasChanged: boolean = false;
743
+
744
+ for (const id of ids) {
745
+ if (data.isSelected) {
746
+ if (!next.has(id)) {
747
+ next.add(id);
748
+ hasChanged = true;
749
+ }
750
+
751
+ continue;
752
+ }
753
+
754
+ if (next.delete(id)) {
755
+ hasChanged = true;
756
+ }
757
+ }
758
+
759
+ return hasChanged ? next : data.selectedResourceIds;
760
+ }
761
+
762
+ /*
763
+ * What the box at the top of the list should look like, over whichever
764
+ * resources are being drawn.
765
+ *
766
+ * An empty list is never "all selected": a ticked box over nothing to tick
767
+ * reads as a selection that was lost.
768
+ */
769
+ public static getSelectionState(data: {
770
+ statusPageResources: Array<StatusPageResource>;
771
+ selectedResourceIds: Set<string>;
772
+ }): StatusPageResourceSelectionState {
773
+ const ids: Array<string> = StatusPageResourceExplorerUtil.getResourceIds(
774
+ data.statusPageResources,
775
+ );
776
+
777
+ let selectedCount: number = 0;
778
+
779
+ for (const id of ids) {
780
+ if (data.selectedResourceIds.has(id)) {
781
+ selectedCount++;
782
+ }
783
+ }
784
+
785
+ const isAllSelected: boolean =
786
+ ids.length > 0 && selectedCount === ids.length;
787
+
788
+ return {
789
+ isAllSelected: isAllSelected,
790
+ isIndeterminate: selectedCount > 0 && !isAllSelected,
791
+ selectedCount: selectedCount,
792
+ selectableCount: ids.length,
793
+ };
794
+ }
795
+
796
+ /*
797
+ * The selected resources themselves, in the order the list holds them, so a
798
+ * bulk action reports its progress in the order the operator is looking at.
799
+ *
800
+ * Ids in the selection that match nothing in the list are dropped rather than
801
+ * guessed at - see retainSelectedResourceIds for where they come from.
802
+ */
803
+ public static getSelectedResources(data: {
804
+ statusPageResources: Array<StatusPageResource>;
805
+ selectedResourceIds: Set<string>;
806
+ }): Array<StatusPageResource> {
807
+ const seen: Set<string> = new Set<string>();
808
+
809
+ return data.statusPageResources.filter(
810
+ (statusPageResource: StatusPageResource): boolean => {
811
+ const id: string | null =
812
+ StatusPageResourceExplorerUtil.getResourceId(statusPageResource);
813
+
814
+ if (!id || seen.has(id) || !data.selectedResourceIds.has(id)) {
815
+ return false;
816
+ }
817
+
818
+ seen.add(id);
819
+
820
+ return true;
821
+ },
822
+ );
823
+ }
824
+
825
+ /*
826
+ * The selection, minus anything the list no longer holds.
827
+ *
828
+ * Every write on this pane ends in a refetch, and a resource that was removed
829
+ * - by this operator's own bulk action, or by somebody else's - must not stay
830
+ * in the selection: the bulk bar would go on counting it, and the next action
831
+ * would try to delete a row that is already gone.
832
+ *
833
+ * Returns the set it was given when nothing was dropped, for the same reason
834
+ * setResourcesSelected does.
835
+ */
836
+ public static retainSelectedResourceIds(data: {
837
+ selectedResourceIds: Set<string>;
838
+ statusPageResources: Array<StatusPageResource>;
839
+ }): Set<string> {
840
+ if (data.selectedResourceIds.size === 0) {
841
+ return data.selectedResourceIds;
842
+ }
843
+
844
+ const present: Set<string> = new Set<string>(
845
+ StatusPageResourceExplorerUtil.getResourceIds(data.statusPageResources),
846
+ );
847
+
848
+ const next: Set<string> = new Set<string>();
849
+
850
+ for (const id of data.selectedResourceIds) {
851
+ if (present.has(id)) {
852
+ next.add(id);
853
+ }
854
+ }
855
+
856
+ return next.size === data.selectedResourceIds.size
857
+ ? data.selectedResourceIds
858
+ : next;
859
+ }
860
+
861
+ /*
862
+ * What the confirmation for a bulk removal is called, and what it says.
863
+ *
864
+ * It says the monitors survive, for the same reason the single-row confirm
865
+ * does: "remove" on a status page means "stop publishing this", and an
866
+ * operator clearing twenty seven rows deserves to be told that once, loudly,
867
+ * rather than to wonder afterwards.
868
+ */
869
+ public static getBulkRemoveConfirmTitle(data: { count: number }): string {
870
+ return `Remove ${data.count.toLocaleString()} ${
871
+ data.count === 1 ? "resource" : "resources"
872
+ }`;
873
+ }
874
+
875
+ public static getBulkRemoveConfirmMessage(data: { count: number }): string {
876
+ if (data.count === 1) {
877
+ return "Are you sure you want to remove 1 resource from this status page? The monitor itself is not deleted.";
878
+ }
879
+
880
+ return `Are you sure you want to remove ${data.count.toLocaleString()} resources from this status page? The monitors themselves are not deleted.`;
881
+ }
882
+
625
883
  /*
626
884
  * The `order` to write when a resource is dragged from one position to
627
885
  * another.
@@ -0,0 +1,287 @@
1
+ import Metric from "../../Models/AnalyticsModels/Metric";
2
+ import AggregatedModel from "../../Types/BaseDatabase/AggregatedModel";
3
+ import InBetween from "../../Types/BaseDatabase/InBetween";
4
+ import Includes from "../../Types/BaseDatabase/Includes";
5
+ import Query from "../../Types/BaseDatabase/Query";
6
+ import { JSONObject } from "../../Types/JSON";
7
+ import ObjectID from "../../Types/ObjectID";
8
+ import {
9
+ LlmRequestModelAttributeKeys,
10
+ LlmSystemAttributeKeys,
11
+ } from "../../Types/Telemetry/LlmConventions";
12
+ import {
13
+ LlmCostMetricNames,
14
+ LlmMetricTeamAttributeKeys,
15
+ LlmMetricUserAttributeKeys,
16
+ LlmMicroUsdCostMetricNames,
17
+ LlmTokenDirection,
18
+ LlmTokenTypeAttributeKeys,
19
+ LlmTokenUsageMetricNames,
20
+ MICRO_USD_TO_USD,
21
+ getLlmTokenDirection,
22
+ } from "../../Types/Telemetry/LlmMetricConventions";
23
+
24
+ /*
25
+ * Pure query construction and result folding for metric-sourced LLM token and
26
+ * cost totals. Isomorphic on purpose: the budget evaluator runs these on the
27
+ * server with root props, and the AI / LLM overview runs the same queries in
28
+ * the browser under the signed-in user's permissions. Keeping the shapes here
29
+ * means the two cannot drift into disagreeing about what "LLM cost" means.
30
+ *
31
+ * The server wrapper (Common/Server/Utils/Telemetry/LlmMetricSpend.ts) adds
32
+ * the AggregateBy envelope and executes them.
33
+ *
34
+ * Scoping caveat, deliberate: provider/model narrowing matches the PRIMARY
35
+ * semantic-convention attribute key only (gen_ai.system, gen_ai.request.model).
36
+ * The span extractor accepts several fallback spellings per field, but a
37
+ * ClickHouse map filter can only test one key at a time, and issuing one query
38
+ * per candidate key and summing would double-count any datapoint carrying two
39
+ * of them. Matching the semconv key alone can only ever UNDER-count a scoped
40
+ * query, never over-count it. Unscoped project-wide queries — the common case,
41
+ * and the one a metrics-only emitter usually needs — apply no attribute filter
42
+ * and see every datapoint.
43
+ */
44
+
45
+ export interface LlmMetricScope {
46
+ projectId: ObjectID;
47
+ startTime: Date;
48
+ endTime: Date;
49
+ // Telemetry service to narrow to, matching Span.primaryEntityId scoping.
50
+ serviceId?: ObjectID | undefined;
51
+ // gen_ai.system value, e.g. "openai".
52
+ llmSystem?: string | undefined;
53
+ // gen_ai.request.model value.
54
+ llmModel?: string | undefined;
55
+ /*
56
+ * Employee / team narrowing, for "what did this person spend" and
57
+ * per-cost-centre rollups.
58
+ *
59
+ * Same one-key-only caveat as llmSystem/llmModel above, and for the same
60
+ * reason: several spellings are recognized, a ClickHouse map filter can
61
+ * test one key at a time, and issuing one query per candidate key and
62
+ * summing would double-count any datapoint carrying two of them. So each
63
+ * filter matches the PRIMARY key of its list only (user.email, team.id),
64
+ * which can under-count a scoped query but can never over-count it —
65
+ * exactly the direction a chargeback figure should err in.
66
+ */
67
+ llmUserId?: string | undefined;
68
+ llmUserEmail?: string | undefined;
69
+ llmTeam?: string | undefined;
70
+ }
71
+
72
+ export interface LlmMetricTokenTotals {
73
+ inputTokens: number;
74
+ outputTokens: number;
75
+ }
76
+
77
+ /*
78
+ * Both convention lists are ordered preferred-first, so element zero is the
79
+ * semantic-convention key.
80
+ */
81
+ export const METRIC_SYSTEM_ATTRIBUTE_KEY: string = LlmSystemAttributeKeys[0]!;
82
+ export const METRIC_MODEL_ATTRIBUTE_KEY: string =
83
+ LlmRequestModelAttributeKeys[0]!;
84
+
85
+ /*
86
+ * The identity keys, likewise element zero of their lists. On the metric side
87
+ * that is "user.email" — the spelling the coding-agent CLIs emit natively and
88
+ * the one a manager can read without a lookup table.
89
+ */
90
+ export const METRIC_USER_ATTRIBUTE_KEY: string = LlmMetricUserAttributeKeys[0]!;
91
+ export const METRIC_TEAM_ATTRIBUTE_KEY: string = LlmMetricTeamAttributeKeys[0]!;
92
+
93
+ export default class LlmMetricQuery {
94
+ /**
95
+ * Shared filter for both the cost and token queries: the project, the
96
+ * window, the candidate metric names, and the caller's optional scoping.
97
+ *
98
+ * The `time` predicate has to live in the query itself. AggregateBy's
99
+ * startTimestamp/endTimestamp only choose the bucket grid — without this the
100
+ * aggregate scans the metric's whole retention (the same trap the span query
101
+ * documents).
102
+ */
103
+ public static buildBaseQuery(data: {
104
+ scope: LlmMetricScope;
105
+ metricNames: Array<string>;
106
+ }): Query<Metric> {
107
+ const query: Record<string, unknown> = {
108
+ projectId: data.scope.projectId,
109
+ name: new Includes(data.metricNames),
110
+ time: new InBetween(data.scope.startTime, data.scope.endTime),
111
+ };
112
+
113
+ if (data.scope.serviceId) {
114
+ query["primaryEntityId"] = data.scope.serviceId;
115
+ }
116
+
117
+ const attributes: Record<string, string> = {};
118
+
119
+ if (data.scope.llmSystem) {
120
+ attributes[METRIC_SYSTEM_ATTRIBUTE_KEY] = data.scope.llmSystem;
121
+ }
122
+
123
+ if (data.scope.llmModel) {
124
+ attributes[METRIC_MODEL_ATTRIBUTE_KEY] = data.scope.llmModel;
125
+ }
126
+
127
+ /*
128
+ * llmUserId and llmUserEmail both narrow on METRIC_USER_ATTRIBUTE_KEY —
129
+ * the metric stream has ONE identity key ("user.email"), unlike the span
130
+ * side where the id and the email are separate columns. Email is the
131
+ * preferred spelling, so it wins if a caller somehow supplies both rather
132
+ * than the two silently overwriting each other in map order.
133
+ */
134
+ if (data.scope.llmUserEmail) {
135
+ attributes[METRIC_USER_ATTRIBUTE_KEY] = data.scope.llmUserEmail;
136
+ } else if (data.scope.llmUserId) {
137
+ attributes[METRIC_USER_ATTRIBUTE_KEY] = data.scope.llmUserId;
138
+ }
139
+
140
+ if (data.scope.llmTeam) {
141
+ attributes[METRIC_TEAM_ATTRIBUTE_KEY] = data.scope.llmTeam;
142
+ }
143
+
144
+ if (Object.keys(attributes).length > 0) {
145
+ query["attributes"] = attributes;
146
+ }
147
+
148
+ return query as Query<Metric>;
149
+ }
150
+
151
+ public static buildCostQuery(scope: LlmMetricScope): Query<Metric> {
152
+ return this.buildBaseQuery({
153
+ scope: scope,
154
+ metricNames: LlmCostMetricNames,
155
+ });
156
+ }
157
+
158
+ /**
159
+ * The micro-USD cost query — Codex-style emitters that report spend in
160
+ * MILLIONTHS of a dollar.
161
+ *
162
+ * Separate from buildCostQuery on purpose. A single query over both name
163
+ * lists would sum two different units into one number, and a $3 Codex turn
164
+ * would reach a cost budget as $3,000,000. The unit cannot be recovered
165
+ * after the sum, so it has to be applied per-list, before the addition —
166
+ * which is what combineCostTotals below is for.
167
+ */
168
+ public static buildMicroUsdCostQuery(scope: LlmMetricScope): Query<Metric> {
169
+ return this.buildBaseQuery({
170
+ scope: scope,
171
+ metricNames: LlmMicroUsdCostMetricNames,
172
+ });
173
+ }
174
+
175
+ public static buildTokenQuery(scope: LlmMetricScope): Query<Metric> {
176
+ return this.buildBaseQuery({
177
+ scope: scope,
178
+ metricNames: LlmTokenUsageMetricNames,
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Combine a USD total with a micro-USD total into one USD figure.
184
+ *
185
+ * The ONLY place MICRO_USD_TO_USD is applied. Keeping the scale factor in a
186
+ * single pure function is the whole point: a million-fold unit error is
187
+ * invisible in a diff and catastrophic in a cost budget, so it lives
188
+ * somewhere a test can pin it exactly once.
189
+ *
190
+ * Non-finite inputs contribute 0 rather than poisoning the result, matching
191
+ * sumAggregatedRows: these figures gate alerting, and a NaN spend compares
192
+ * false against every threshold and would silently disable the monitor.
193
+ */
194
+ public static combineCostTotals(data: {
195
+ usd: number;
196
+ microUsd: number;
197
+ }): number {
198
+ const usd: number = isFinite(data.usd) ? data.usd : 0;
199
+ const microUsd: number = isFinite(data.microUsd) ? data.microUsd : 0;
200
+
201
+ return usd + microUsd * MICRO_USD_TO_USD;
202
+ }
203
+
204
+ /**
205
+ * Sum an aggregate result's buckets. Non-numeric, missing, NaN and infinite
206
+ * values contribute nothing rather than poisoning the total — these figures
207
+ * feed monitors, and one bad row must not turn a budget's spend into NaN.
208
+ */
209
+ public static sumAggregatedRows(
210
+ rows: Array<AggregatedModel> | undefined | null,
211
+ ): number {
212
+ if (!rows || !Array.isArray(rows)) {
213
+ return 0;
214
+ }
215
+
216
+ return rows.reduce((acc: number, row: AggregatedModel): number => {
217
+ const value: number = Number(row?.value ?? 0);
218
+
219
+ if (!isFinite(value)) {
220
+ return acc;
221
+ }
222
+
223
+ return acc + value;
224
+ }, 0);
225
+ }
226
+
227
+ /**
228
+ * Fold grouped token rows into input/output totals.
229
+ *
230
+ * A row is counted once, against the first recognized token-type attribute
231
+ * it carries (the keys are ordered preferred-first). Rows whose token type
232
+ * is absent, unrecognized, or one of the kinds we deliberately exclude
233
+ * (cache_read / cache_creation) are dropped rather than guessed at.
234
+ */
235
+ public static reduceTokenRows(
236
+ rows: Array<AggregatedModel> | undefined | null,
237
+ ): LlmMetricTokenTotals {
238
+ const totals: LlmMetricTokenTotals = {
239
+ inputTokens: 0,
240
+ outputTokens: 0,
241
+ };
242
+
243
+ if (!rows || !Array.isArray(rows)) {
244
+ return totals;
245
+ }
246
+
247
+ for (const row of rows) {
248
+ if (!row) {
249
+ continue;
250
+ }
251
+
252
+ const attributes: JSONObject =
253
+ (row["attributes"] as JSONObject | undefined) || {};
254
+
255
+ let direction: LlmTokenDirection | null = null;
256
+
257
+ for (const key of LlmTokenTypeAttributeKeys) {
258
+ const candidate: LlmTokenDirection | null = getLlmTokenDirection(
259
+ attributes[key] as string | undefined,
260
+ );
261
+
262
+ if (candidate) {
263
+ direction = candidate;
264
+ break;
265
+ }
266
+ }
267
+
268
+ if (!direction) {
269
+ continue;
270
+ }
271
+
272
+ const value: number = Number(row.value ?? 0);
273
+
274
+ if (!isFinite(value)) {
275
+ continue;
276
+ }
277
+
278
+ if (direction === "input") {
279
+ totals.inputTokens += value;
280
+ } else {
281
+ totals.outputTokens += value;
282
+ }
283
+ }
284
+
285
+ return totals;
286
+ }
287
+ }
@@ -1060,6 +1060,74 @@ let Span = class Span extends AnalyticsBaseModel {
1060
1060
  },
1061
1061
  accessControl: llmColumnAccessControl,
1062
1062
  });
1063
+ /*
1064
+ * Employee identity columns. Denormalized at ingest from the OTel general
1065
+ * semantic-convention identity attributes (user.id / user.email / team.id)
1066
+ * and the gateway + coding-agent fallbacks — see
1067
+ * LlmUserIdAttributeKeys in Common/Types/Telemetry/LlmConventions.ts.
1068
+ *
1069
+ * These answer the question the trace-only LLM feature could not: which
1070
+ * PERSON, and which cost centre, does this spend belong to. They are
1071
+ * populated only on LLM spans (the extractor gates them on isLlmSpan), so
1072
+ * the RUM and HTTP fleet — which also carries user.id — pays nothing for
1073
+ * them.
1074
+ *
1075
+ * They hold real identifying data, so TraceScrubRuleService.scrubSpan
1076
+ * applies the project's Attributes-scoped scrub rules to them just as it
1077
+ * does to the attribute they were derived from.
1078
+ */
1079
+ const llmUserIdColumn = new AnalyticsTableColumn({
1080
+ key: "llmUserId",
1081
+ isLowCardinality: true,
1082
+ title: "LLM User ID",
1083
+ description: "Id of the employee / internal actor who made this LLM call (user.id, enduser.id, or the gateway / coding-agent equivalent). NOT the caller's own downstream customer — that identity is deliberately never mapped here. '' when the instrumentation reports none.",
1084
+ required: false,
1085
+ type: TableColumnType.Text,
1086
+ /*
1087
+ * Set index sized like idx_llm_request_model: a project's employee
1088
+ * roster is in the hundreds-to-low-thousands, so 1000 distinct values
1089
+ * per granule keeps the index useful for the "one person's spend"
1090
+ * query without degenerating into a per-granule scan.
1091
+ */
1092
+ skipIndex: {
1093
+ name: "idx_llm_user_id",
1094
+ type: SkipIndexType.Set,
1095
+ params: [1000],
1096
+ granularity: 4,
1097
+ },
1098
+ accessControl: llmColumnAccessControl,
1099
+ });
1100
+ const llmUserEmailColumn = new AnalyticsTableColumn({
1101
+ key: "llmUserEmail",
1102
+ isLowCardinality: true,
1103
+ title: "LLM User Email",
1104
+ description: "Email of the employee / internal actor who made this LLM call (user.email, emitted natively by Claude Code, Gemini CLI and Codex). Subject to the project's Attributes-scoped trace scrub rules. '' when the instrumentation reports none.",
1105
+ required: false,
1106
+ type: TableColumnType.Text,
1107
+ // Same sizing rationale as idx_llm_user_id — one row per employee.
1108
+ skipIndex: {
1109
+ name: "idx_llm_user_email",
1110
+ type: SkipIndexType.Set,
1111
+ params: [1000],
1112
+ granularity: 4,
1113
+ },
1114
+ accessControl: llmColumnAccessControl,
1115
+ });
1116
+ const llmTeamColumn = new AnalyticsTableColumn({
1117
+ key: "llmTeam",
1118
+ isLowCardinality: true,
1119
+ title: "LLM Team",
1120
+ description: "Team / cost centre the LLM spend charges to (team.id, cost_center, department — conventionally set via OTEL_RESOURCE_ATTRIBUTES). '' when none is reported.",
1121
+ required: false,
1122
+ type: TableColumnType.Text,
1123
+ /*
1124
+ * No skip index, mirroring llmResponseModel. Teams number in the tens:
1125
+ * a Set index would match nearly every granule and cost more to read
1126
+ * than the filter it saves, and the LowCardinality dictionary already
1127
+ * makes the column cheap to scan.
1128
+ */
1129
+ accessControl: llmColumnAccessControl,
1130
+ });
1063
1131
  const retentionDateColumn = new AnalyticsTableColumn({
1064
1132
  key: "retentionDate",
1065
1133
  codec: [{ codec: "DoubleDelta" }, { codec: "ZSTD", level: 1 }],
@@ -1152,6 +1220,9 @@ let Span = class Span extends AnalyticsBaseModel {
1152
1220
  llmTotalTokensColumn,
1153
1221
  llmCostColumn,
1154
1222
  llmConversationIdColumn,
1223
+ llmUserIdColumn,
1224
+ llmUserEmailColumn,
1225
+ llmTeamColumn,
1155
1226
  retentionDateColumn,
1156
1227
  ],
1157
1228
  projections: [
@@ -1403,6 +1474,24 @@ let Span = class Span extends AnalyticsBaseModel {
1403
1474
  set llmConversationId(v) {
1404
1475
  this.setColumnValue("llmConversationId", v);
1405
1476
  }
1477
+ get llmUserId() {
1478
+ return this.getColumnValue("llmUserId");
1479
+ }
1480
+ set llmUserId(v) {
1481
+ this.setColumnValue("llmUserId", v);
1482
+ }
1483
+ get llmUserEmail() {
1484
+ return this.getColumnValue("llmUserEmail");
1485
+ }
1486
+ set llmUserEmail(v) {
1487
+ this.setColumnValue("llmUserEmail", v);
1488
+ }
1489
+ get llmTeam() {
1490
+ return this.getColumnValue("llmTeam");
1491
+ }
1492
+ set llmTeam(v) {
1493
+ this.setColumnValue("llmTeam", v);
1494
+ }
1406
1495
  };
1407
1496
  Span = __decorate([
1408
1497
  OperationalResource(),