@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
@@ -390,10 +390,10 @@ describe("MonitorRecommendationSeverityMapper", () => {
390
390
  /*
391
391
  * "No map" is the state of every caller that predates this feature, and
392
392
  * it must mean "leave the template's severity alone" rather than throw or
393
- * return a blank id — a criteria instance with a populated incident and
394
- * no severity fails MonitorCriteriaInstance.getValidationError, so the
395
- * whole create would fail at submit with a message about a field the user
396
- * never saw.
393
+ * return a blank id — a criteria instance that creates incidents and has
394
+ * a populated incident with no severity fails
395
+ * MonitorCriteriaInstance.getValidationError, so the whole create would
396
+ * fail at submit with a message about a field the user never saw.
397
397
  */
398
398
  expect(
399
399
  MonitorRecommendationSeverityMapper.resolveSeverityId({
@@ -397,9 +397,10 @@ describe("MonitorRecommendationUtil", () => {
397
397
  it("leaves the template's severity alone when the map has no entry for that severity", () => {
398
398
  /*
399
399
  * A partial map must not blank the severity out. A criteria instance
400
- * with a populated incident and no severity id fails
401
- * MonitorCriteriaInstance.getValidationError, so the whole create would
402
- * fail at submit with a message about a field the user never saw.
400
+ * that creates incidents and has a populated incident with no severity
401
+ * id fails MonitorCriteriaInstance.getValidationError, so the whole
402
+ * create would fail at submit with a message about a field the user
403
+ * never saw.
403
404
  */
404
405
  const monitorStep: MonitorStep =
405
406
  MonitorRecommendationUtil.applyNotificationSettingsToMonitorStep({
@@ -0,0 +1,584 @@
1
+ import {
2
+ LlmCostMetricNames,
3
+ LlmInputTokenTypeValues,
4
+ LlmMetricTeamAttributeKeys,
5
+ LlmMetricUserAttributeKeys,
6
+ LlmMicroUsdCostMetricNames,
7
+ LlmOutputTokenTypeValues,
8
+ LlmTokenDirection,
9
+ LlmTokenTypeAttributeKeys,
10
+ LlmTokenUsageMetricNames,
11
+ MICRO_USD_TO_USD,
12
+ getLlmTokenDirection,
13
+ getLlmTokenTypeValues,
14
+ } from "../../../Types/Telemetry/LlmMetricConventions";
15
+ import { describe, expect, test } from "@jest/globals";
16
+
17
+ describe("LlmMetricConventions", () => {
18
+ describe("metric name lists", () => {
19
+ test("token usage list leads with the OTel semantic convention", () => {
20
+ expect(LlmTokenUsageMetricNames[0]).toBe("gen_ai.client.token.usage");
21
+ });
22
+
23
+ test("cost list leads with the gen_ai-namespaced spelling", () => {
24
+ expect(LlmCostMetricNames[0]).toBe("gen_ai.client.cost");
25
+ });
26
+
27
+ test("token usage list covers the pre-convention spellings", () => {
28
+ expect(LlmTokenUsageMetricNames).toEqual(
29
+ expect.arrayContaining([
30
+ "gen_ai.client.token.usage",
31
+ "gen_ai.client.token.count",
32
+ "llm.token.usage",
33
+ "llm.usage.tokens",
34
+ ]),
35
+ );
36
+ });
37
+
38
+ test("cost list covers the LiteLLM gateway spellings", () => {
39
+ expect(LlmCostMetricNames).toEqual(
40
+ expect.arrayContaining(["litellm_spend_metric", "litellm.cost.total"]),
41
+ );
42
+ });
43
+
44
+ test("no metric name appears in both lists", () => {
45
+ const overlap: Array<string> = LlmTokenUsageMetricNames.filter(
46
+ (name: string) => {
47
+ return LlmCostMetricNames.includes(name);
48
+ },
49
+ );
50
+
51
+ expect(overlap).toEqual([]);
52
+ });
53
+
54
+ test("neither list contains duplicates", () => {
55
+ expect(new Set(LlmTokenUsageMetricNames).size).toBe(
56
+ LlmTokenUsageMetricNames.length,
57
+ );
58
+ expect(new Set(LlmCostMetricNames).size).toBe(LlmCostMetricNames.length);
59
+ });
60
+
61
+ test("every metric name is a non-empty trimmed string", () => {
62
+ for (const name of [...LlmTokenUsageMetricNames, ...LlmCostMetricNames]) {
63
+ expect(typeof name).toBe("string");
64
+ expect(name.length).toBeGreaterThan(0);
65
+ expect(name).toBe(name.trim());
66
+ }
67
+ });
68
+ });
69
+
70
+ describe("token type attribute keys", () => {
71
+ test("leads with the OTel semantic convention key", () => {
72
+ expect(LlmTokenTypeAttributeKeys[0]).toBe("gen_ai.token.type");
73
+ });
74
+
75
+ test("includes the pre-convention llm-namespaced key", () => {
76
+ expect(LlmTokenTypeAttributeKeys).toContain("llm.token.type");
77
+ });
78
+
79
+ test("contains no duplicates", () => {
80
+ expect(new Set(LlmTokenTypeAttributeKeys).size).toBe(
81
+ LlmTokenTypeAttributeKeys.length,
82
+ );
83
+ });
84
+ });
85
+
86
+ describe("token type value lists", () => {
87
+ test("input and output values never overlap", () => {
88
+ const overlap: Array<string> = LlmInputTokenTypeValues.filter(
89
+ (value: string) => {
90
+ return LlmOutputTokenTypeValues.includes(value);
91
+ },
92
+ );
93
+
94
+ expect(overlap).toEqual([]);
95
+ });
96
+
97
+ test("values are lowercase, so the normalizing comparison can match", () => {
98
+ for (const value of [
99
+ ...LlmInputTokenTypeValues,
100
+ ...LlmOutputTokenTypeValues,
101
+ ]) {
102
+ expect(value).toBe(value.toLowerCase());
103
+ }
104
+ });
105
+
106
+ test("cache token kinds are deliberately excluded from both", () => {
107
+ for (const cacheKind of ["cache_read", "cache_creation", "cache_write"]) {
108
+ expect(LlmInputTokenTypeValues).not.toContain(cacheKind);
109
+ expect(LlmOutputTokenTypeValues).not.toContain(cacheKind);
110
+ }
111
+ });
112
+ });
113
+
114
+ describe("getLlmTokenDirection", () => {
115
+ test.each([
116
+ ["input", "input"],
117
+ ["prompt", "input"],
118
+ ["output", "output"],
119
+ ["completion", "output"],
120
+ ])("maps %s to %s", (value: string, expected: string) => {
121
+ expect(getLlmTokenDirection(value)).toBe(expected);
122
+ });
123
+
124
+ test.each([
125
+ ["INPUT", "input"],
126
+ ["Output", "output"],
127
+ [" prompt ", "input"],
128
+ ["\tCOMPLETION\n", "output"],
129
+ ])(
130
+ "normalizes casing and surrounding whitespace: %s",
131
+ (value: string, expected: string) => {
132
+ expect(getLlmTokenDirection(value)).toBe(expected);
133
+ },
134
+ );
135
+
136
+ test.each([
137
+ "cache_read",
138
+ "cache_creation",
139
+ "reasoning",
140
+ "total",
141
+ "unknown",
142
+ ])("returns null for the uncounted token kind %s", (value: string) => {
143
+ expect(getLlmTokenDirection(value)).toBeNull();
144
+ });
145
+
146
+ test.each([
147
+ ["empty string", ""],
148
+ ["whitespace only", " "],
149
+ ])("returns null for %s", (_label: string, value: string) => {
150
+ expect(getLlmTokenDirection(value)).toBeNull();
151
+ });
152
+
153
+ test("returns null for undefined and null", () => {
154
+ expect(getLlmTokenDirection(undefined)).toBeNull();
155
+ expect(getLlmTokenDirection(null)).toBeNull();
156
+ });
157
+
158
+ test("returns null for non-string values without throwing", () => {
159
+ expect(getLlmTokenDirection(42 as unknown as string)).toBeNull();
160
+ expect(getLlmTokenDirection({} as unknown as string)).toBeNull();
161
+ expect(getLlmTokenDirection([] as unknown as string)).toBeNull();
162
+ expect(getLlmTokenDirection(true as unknown as string)).toBeNull();
163
+ });
164
+
165
+ test("every declared input value resolves to input", () => {
166
+ for (const value of LlmInputTokenTypeValues) {
167
+ expect(getLlmTokenDirection(value)).toBe("input");
168
+ }
169
+ });
170
+
171
+ test("every declared output value resolves to output", () => {
172
+ for (const value of LlmOutputTokenTypeValues) {
173
+ expect(getLlmTokenDirection(value)).toBe("output");
174
+ }
175
+ });
176
+ });
177
+
178
+ describe("getLlmTokenTypeValues", () => {
179
+ test("returns the input list for input", () => {
180
+ expect(getLlmTokenTypeValues("input")).toEqual(LlmInputTokenTypeValues);
181
+ });
182
+
183
+ test("returns the output list for output", () => {
184
+ expect(getLlmTokenTypeValues("output")).toEqual(LlmOutputTokenTypeValues);
185
+ });
186
+
187
+ test("round-trips: every value it returns maps back to that direction", () => {
188
+ const directions: Array<LlmTokenDirection> = ["input", "output"];
189
+
190
+ for (const direction of directions) {
191
+ for (const value of getLlmTokenTypeValues(direction)) {
192
+ expect(getLlmTokenDirection(value)).toBe(direction);
193
+ }
194
+ }
195
+ });
196
+ });
197
+ });
198
+
199
+ /*
200
+ * Coding-agent vendor metric names.
201
+ *
202
+ * The bug class: every major AI coding agent now exports OpenTelemetry
203
+ * natively, and every one of them namespaces its metrics under its own vendor
204
+ * prefix rather than gen_ai.*. A name this module does not know is a name the
205
+ * query never selects, so a whole fleet of agents can be exporting into
206
+ * OneUptime and report exactly $0 of spend — a silent zero, which is the
207
+ * worst kind: no error, no gap in a chart, just a number that looks fine and
208
+ * is wrong.
209
+ */
210
+ describe("LlmMetricConventions — coding-agent vendor metric names", () => {
211
+ test.each([
212
+ ["claude_code.token.usage", "Anthropic Claude Code CLI"],
213
+ ["cursor.token.usage", "Cursor Enterprise OTel export"],
214
+ ["codex.turn.token_usage", "OpenAI Codex CLI"],
215
+ ])("token metric %s (%s) is recognized", (name: string) => {
216
+ expect(LlmTokenUsageMetricNames).toContain(name);
217
+ });
218
+
219
+ /*
220
+ * ---------------------------------------------------------------------
221
+ * The DOUBLE-COUNT hazard, pinned.
222
+ * ---------------------------------------------------------------------
223
+ *
224
+ * LlmMetricQuery.buildTokenQuery issues ONE query with
225
+ * `name: new Includes(LlmTokenUsageMetricNames)` and groups only by the
226
+ * token-TYPE attribute keys — never by metric name. So if a single emitter
227
+ * contributes TWO names to this list, both of its emissions come back as
228
+ * separate rows and reduceTokenRows adds them together. The result is a
229
+ * project reporting exactly 2x its real tokens: no error, no gap in the
230
+ * chart, just a number that looks plausible and is wrong.
231
+ *
232
+ * Google Gemini CLI is the concrete case. It emits `gemini_cli.token.usage`
233
+ * AND the semantic-convention `gen_ai.client.token.usage` for the same
234
+ * tokens (both are listed in its metric table in
235
+ * Docs/Content/en/telemetry/gemini-cli-and-copilot.md), so the vendor name
236
+ * must stay OUT. Nothing is lost — the semconv name it also emits is in the
237
+ * list, so its tokens are counted, once.
238
+ *
239
+ * The other three vendor names were audited against the same docs and do
240
+ * NOT overlap a semconv metric: Claude Code and Cursor publish only their
241
+ * own vendor-namespaced metrics, and Codex's gen_ai.* usage attributes live
242
+ * on SPANS rather than metrics.
243
+ *
244
+ * The list is pinned exactly rather than by `toContain` so that ADDING a
245
+ * name — the way this bug arrives — fails here and makes whoever adds it
246
+ * check for an overlapping semconv emission first.
247
+ */
248
+ test("Gemini CLI's vendor token metric is deliberately NOT recognized", () => {
249
+ expect(LlmTokenUsageMetricNames).not.toContain("gemini_cli.token.usage");
250
+ });
251
+
252
+ test("the token metric list is exactly this set, in this order", () => {
253
+ expect(LlmTokenUsageMetricNames).toEqual([
254
+ "gen_ai.client.token.usage",
255
+ "gen_ai.client.token.count",
256
+ "llm.token.usage",
257
+ "llm.usage.tokens",
258
+ "claude_code.token.usage",
259
+ "cursor.token.usage",
260
+ "codex.turn.token_usage",
261
+ ]);
262
+ });
263
+
264
+ test("no emitter contributes two names to the token list", () => {
265
+ /*
266
+ * The property the pinned list above protects, stated directly. Each
267
+ * entry names one emitter; two names mapping to the same emitter is the
268
+ * double count. Keep this map in step with the list when a genuinely
269
+ * non-overlapping vendor name is added.
270
+ */
271
+ const emitterByMetricName: Record<string, string> = {
272
+ "gen_ai.client.token.usage": "otel-semconv",
273
+ "gen_ai.client.token.count": "otel-semconv-pre-1.27",
274
+ "llm.token.usage": "openinference",
275
+ "llm.usage.tokens": "openllmetry",
276
+ "claude_code.token.usage": "claude-code",
277
+ "cursor.token.usage": "cursor",
278
+ "codex.turn.token_usage": "codex",
279
+ };
280
+
281
+ // Every recognized name is accounted for — no unattributed additions.
282
+ expect(Object.keys(emitterByMetricName).sort()).toEqual(
283
+ [...LlmTokenUsageMetricNames].sort(),
284
+ );
285
+
286
+ const emitters: Array<string> = LlmTokenUsageMetricNames.map(
287
+ (name: string) => {
288
+ return emitterByMetricName[name]!;
289
+ },
290
+ );
291
+
292
+ expect(new Set(emitters).size).toBe(emitters.length);
293
+ });
294
+
295
+ test.each([
296
+ ["claude_code.cost.usage", "Anthropic Claude Code CLI"],
297
+ ["cursor.cost.usage", "Cursor Enterprise OTel export"],
298
+ ])("USD cost metric %s (%s) is recognized", (name: string) => {
299
+ expect(LlmCostMetricNames).toContain(name);
300
+ });
301
+
302
+ test("the semantic convention still leads the token list", () => {
303
+ // Vendor names are appended, never prepended — semconv stays preferred.
304
+ expect(LlmTokenUsageMetricNames[0]).toBe("gen_ai.client.token.usage");
305
+ });
306
+
307
+ test("Codex cost is NOT in the USD list", () => {
308
+ /*
309
+ * The million-fold error. codex.turn.cost_microusd reports MILLIONTHS of
310
+ * a dollar; summed alongside genuinely-USD metrics a $3 turn becomes
311
+ * $3,000,000 and trips every configured cost budget at once.
312
+ */
313
+ expect(LlmCostMetricNames).not.toContain("codex.turn.cost_microusd");
314
+ });
315
+
316
+ test("Codex cost is in the micro-USD list", () => {
317
+ expect(LlmMicroUsdCostMetricNames).toContain("codex.turn.cost_microusd");
318
+ });
319
+
320
+ test("the USD and micro-USD cost lists are disjoint", () => {
321
+ const overlap: Array<string> = LlmCostMetricNames.filter((name: string) => {
322
+ return LlmMicroUsdCostMetricNames.includes(name);
323
+ });
324
+
325
+ expect(overlap).toEqual([]);
326
+ });
327
+
328
+ test("the micro-USD list never overlaps the token list either", () => {
329
+ const overlap: Array<string> = LlmMicroUsdCostMetricNames.filter(
330
+ (name: string) => {
331
+ return LlmTokenUsageMetricNames.includes(name);
332
+ },
333
+ );
334
+
335
+ expect(overlap).toEqual([]);
336
+ });
337
+
338
+ test("no cost metric name is duplicated across the three lists", () => {
339
+ const all: Array<string> = [
340
+ ...LlmTokenUsageMetricNames,
341
+ ...LlmCostMetricNames,
342
+ ...LlmMicroUsdCostMetricNames,
343
+ ];
344
+
345
+ expect(new Set(all).size).toBe(all.length);
346
+ });
347
+
348
+ test("every micro-USD name is a non-empty trimmed string", () => {
349
+ for (const name of LlmMicroUsdCostMetricNames) {
350
+ expect(typeof name).toBe("string");
351
+ expect(name.length).toBeGreaterThan(0);
352
+ expect(name).toBe(name.trim());
353
+ }
354
+ });
355
+
356
+ test("MICRO_USD_TO_USD is exactly one millionth", () => {
357
+ /*
358
+ * Pinned as a literal rather than as 1/1_000_000 so a dropped or added
359
+ * zero fails here loudly. Everything downstream multiplies by this.
360
+ */
361
+ expect(MICRO_USD_TO_USD).toBe(0.000001);
362
+ expect(MICRO_USD_TO_USD).toBe(1e-6);
363
+ expect(1_000_000 * MICRO_USD_TO_USD).toBe(1);
364
+ });
365
+ });
366
+
367
+ /*
368
+ * Vendor token-type spellings.
369
+ *
370
+ * Two failure modes are pinned here. First, a token-type KEY the fold does
371
+ * not recognize means every datapoint from that emitter is dropped as
372
+ * "direction unknown" — tokens silently vanish. Second, a token-type VALUE
373
+ * that is wrongly recognized double-counts: Codex's `total` is a superset of
374
+ * its siblings and `reasoning_output` is a subset of `output`, and both
375
+ * arrive ALONGSIDE the datapoints they overlap.
376
+ */
377
+ describe("LlmMetricConventions — coding-agent token type spellings", () => {
378
+ test.each([
379
+ ["type", "Claude Code (bare)"],
380
+ ["cursor.token.type", "Cursor"],
381
+ ["token_type", "Codex"],
382
+ ])("token type key %s (%s) is recognized", (key: string) => {
383
+ expect(LlmTokenTypeAttributeKeys).toContain(key);
384
+ });
385
+
386
+ test("the bare 'type' key never outranks a namespaced one", () => {
387
+ /*
388
+ * The bounded-risk argument for accepting a key as generic as `type`:
389
+ * it is only ever consulted for rows that already matched an LLM metric
390
+ * NAME, and it sorts after every namespaced spelling, so an emitter that
391
+ * carries both is read from the namespaced one.
392
+ */
393
+ const bare: number = LlmTokenTypeAttributeKeys.indexOf("type");
394
+ const semconv: number =
395
+ LlmTokenTypeAttributeKeys.indexOf("gen_ai.token.type");
396
+
397
+ expect(bare).toBeGreaterThan(semconv);
398
+ expect(bare).toBeGreaterThan(
399
+ LlmTokenTypeAttributeKeys.indexOf("llm.token.type"),
400
+ );
401
+ });
402
+
403
+ test("the token type key list still contains no duplicates", () => {
404
+ expect(new Set(LlmTokenTypeAttributeKeys).size).toBe(
405
+ LlmTokenTypeAttributeKeys.length,
406
+ );
407
+ });
408
+
409
+ test.each(["input", "output"])(
410
+ "the plain %s value is already covered — no vendor value needed",
411
+ (value: string) => {
412
+ expect(getLlmTokenDirection(value)).not.toBeNull();
413
+ },
414
+ );
415
+
416
+ /*
417
+ * Every excluded literal, spelled exactly as its vendor emits it. These
418
+ * MUST map to null; a change that starts counting any of them inflates
419
+ * metric-sourced totals against the span-sourced ones they stand in for,
420
+ * or double-counts outright.
421
+ */
422
+ test.each([
423
+ // Claude Code — camelCase, which is easy to miss when eyeballing a list.
424
+ "cacheRead",
425
+ "cacheCreation",
426
+ // Cursor.
427
+ "cache_read",
428
+ "cache_creation",
429
+ // Codex.
430
+ "cached_input",
431
+ "cache_write_input",
432
+ // Codex: a SUPERSET of its siblings, emitted alongside them.
433
+ "total",
434
+ // Codex: a SUBSET of `output`, emitted alongside it.
435
+ "reasoning_output",
436
+ ])("the excluded token kind %s maps to null", (value: string) => {
437
+ expect(getLlmTokenDirection(value)).toBeNull();
438
+ });
439
+
440
+ test.each(["CacheRead", " cacheCreation ", "TOTAL", "Reasoning_Output"])(
441
+ "the excluded kind %s stays excluded after case/whitespace normalization",
442
+ (value: string) => {
443
+ expect(getLlmTokenDirection(value)).toBeNull();
444
+ },
445
+ );
446
+
447
+ test("no excluded kind leaked into either value list", () => {
448
+ for (const excluded of [
449
+ "cacheread",
450
+ "cachecreation",
451
+ "cache_read",
452
+ "cache_creation",
453
+ "cached_input",
454
+ "cache_write_input",
455
+ "total",
456
+ "reasoning_output",
457
+ ]) {
458
+ expect(LlmInputTokenTypeValues).not.toContain(excluded);
459
+ expect(LlmOutputTokenTypeValues).not.toContain(excluded);
460
+ }
461
+ });
462
+ });
463
+
464
+ /*
465
+ * Metric-side identity keys. These exist so metric-sourced spend can be
466
+ * grouped by employee at all; the risk they carry is the same one the span
467
+ * side documents — grouping the wrong human's spend under an engineer's name.
468
+ */
469
+ describe("LlmMetricConventions — metric identity attribute keys", () => {
470
+ test("the user list leads with the key the coding agents emit natively", () => {
471
+ expect(LlmMetricUserAttributeKeys[0]).toBe("user.email");
472
+ });
473
+
474
+ test("the user list covers the coding-agent and Cursor spellings", () => {
475
+ /*
476
+ * Two tiers, bare first then resource-prefixed, written out as literals.
477
+ *
478
+ * The resource tier is not decoration. OtelMetricsIngestService flattens
479
+ * resource attributes through
480
+ * `TelemetryUtil.getAttributes({ items, prefixKeysWithString: "resource" })`
481
+ * exactly as the traces service does, so a fleet that stamps identity on
482
+ * the resource — the only thing OTEL_RESOURCE_ATTRIBUTES can do, and what
483
+ * Cursor documents for cursor.user.id — arrives here as
484
+ * `resource.user.email`. A bare-key-only list matches none of it and
485
+ * every per-employee metric rollup is silently empty.
486
+ */
487
+ expect(LlmMetricUserAttributeKeys).toEqual([
488
+ "user.email",
489
+ "user.id",
490
+ "user.account_uuid",
491
+ "user.account_id",
492
+ "cursor.user.id",
493
+ "resource.user.email",
494
+ "resource.user.id",
495
+ "resource.user.account_uuid",
496
+ "resource.user.account_id",
497
+ "resource.cursor.user.id",
498
+ ]);
499
+ });
500
+
501
+ test("the team list leads with team.id", () => {
502
+ expect(LlmMetricTeamAttributeKeys[0]).toBe("team.id");
503
+ });
504
+
505
+ test("the team list covers the OTEL_RESOURCE_ATTRIBUTES spellings", () => {
506
+ /*
507
+ * Both spellings of each, and the resource one is the spelling that
508
+ * actually arrives: OTEL_RESOURCE_ATTRIBUTES=team.id=platform reaches the
509
+ * query layer as `resource.team.id`, never `team.id`.
510
+ */
511
+ expect(LlmMetricTeamAttributeKeys).toEqual([
512
+ "team.id",
513
+ "team",
514
+ "cost_center",
515
+ "department",
516
+ "cursor.team.id",
517
+ "resource.team.id",
518
+ "resource.team",
519
+ "resource.cost_center",
520
+ "resource.department",
521
+ "resource.cursor.team.id",
522
+ ]);
523
+ });
524
+
525
+ test("the bare tier still leads, so the scoped filter keys are unchanged", () => {
526
+ /*
527
+ * LlmMetricQuery exports METRIC_USER_ATTRIBUTE_KEY /
528
+ * METRIC_TEAM_ATTRIBUTE_KEY as element zero of these lists and uses them
529
+ * as the single map-filter key for scoped queries. If the resource tier
530
+ * ever led, every scoped query would silently filter on the wrong key.
531
+ */
532
+ expect(LlmMetricUserAttributeKeys[0]).toBe("user.email");
533
+ expect(LlmMetricTeamAttributeKeys[0]).toBe("team.id");
534
+ });
535
+
536
+ test("neither identity list carries a downstream-customer key", () => {
537
+ /*
538
+ * The same exclusion the span side enforces: gen_ai.user and llm.user
539
+ * carry the CALLER'S customer, not the employee. Grouping metric spend by
540
+ * one of those would manufacture a phantom employee per customer.
541
+ */
542
+ for (const customerKey of [
543
+ "gen_ai.user",
544
+ "llm.user",
545
+ "litellm.metadata.user_api_key_end_user_id",
546
+ ]) {
547
+ expect(LlmMetricUserAttributeKeys).not.toContain(customerKey);
548
+ expect(LlmMetricTeamAttributeKeys).not.toContain(customerKey);
549
+ }
550
+ });
551
+
552
+ test("the two identity lists are disjoint", () => {
553
+ const overlap: Array<string> = LlmMetricUserAttributeKeys.filter(
554
+ (key: string) => {
555
+ return LlmMetricTeamAttributeKeys.includes(key);
556
+ },
557
+ );
558
+
559
+ expect(overlap).toEqual([]);
560
+ });
561
+
562
+ test("neither identity list collides with a token type key", () => {
563
+ /*
564
+ * They are both used as groupBy attribute keys against the same rows; a
565
+ * key that means "which employee" in one list and "which side of the
566
+ * exchange" in the other would classify tokens by person.
567
+ */
568
+ for (const key of [
569
+ ...LlmMetricUserAttributeKeys,
570
+ ...LlmMetricTeamAttributeKeys,
571
+ ]) {
572
+ expect(LlmTokenTypeAttributeKeys).not.toContain(key);
573
+ }
574
+ });
575
+
576
+ test("neither identity list contains duplicates", () => {
577
+ expect(new Set(LlmMetricUserAttributeKeys).size).toBe(
578
+ LlmMetricUserAttributeKeys.length,
579
+ );
580
+ expect(new Set(LlmMetricTeamAttributeKeys).size).toBe(
581
+ LlmMetricTeamAttributeKeys.length,
582
+ );
583
+ });
584
+ });
@@ -43,18 +43,6 @@ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
43
43
  };
44
44
  });
45
45
 
46
- jest.mock("../../../UI/Components/Toast/ToastInit", () => {
47
- return {
48
- __esModule: true,
49
- default: () => {
50
- return null;
51
- },
52
- ShowToastNotification: () => {
53
- return null;
54
- },
55
- };
56
- });
57
-
58
46
  const OWNER_PERMISSIONS: Array<Permission> = [
59
47
  Permission.Public,
60
48
  Permission.User,
@@ -29,7 +29,6 @@ import getJestMockFunction, { MockFunction } from "../../MockType";
29
29
 
30
30
  const getItemMock: MockFunction = getJestMockFunction();
31
31
  const createOrUpdateMock: MockFunction = getJestMockFunction();
32
- const showToastMock: MockFunction = getJestMockFunction();
33
32
 
34
33
  jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
35
34
  return {
@@ -45,18 +44,6 @@ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
45
44
  };
46
45
  });
47
46
 
48
- jest.mock("../../../UI/Components/Toast/ToastInit", () => {
49
- return {
50
- __esModule: true,
51
- default: () => {
52
- return null;
53
- },
54
- ShowToastNotification: (...args: Array<any>) => {
55
- return showToastMock(...args);
56
- },
57
- };
58
- });
59
-
60
47
  const OWNER_PERMISSIONS: Array<Permission> = [
61
48
  Permission.Public,
62
49
  Permission.User,
@@ -171,7 +158,6 @@ describe("CardModelDetail edit modal", () => {
171
158
  beforeEach(() => {
172
159
  getItemMock.mockReset();
173
160
  createOrUpdateMock.mockReset();
174
- showToastMock.mockReset();
175
161
  createOrUpdateMock.mockResolvedValue({ data: {} });
176
162
  });
177
163
 
@@ -283,7 +269,7 @@ describe("CardModelDetail edit modal", () => {
283
269
  expect(submitted.model.shouldAutoEnableProbeOnNewMonitors).toBe(false);
284
270
  });
285
271
 
286
- it("closes the editor without showing a toast after a successful edit", async () => {
272
+ it("closes the editor after a successful edit", async () => {
287
273
  getItemMock.mockResolvedValue(loadedProbe());
288
274
 
289
275
  renderProbeCard();
@@ -307,7 +293,5 @@ describe("CardModelDetail edit modal", () => {
307
293
  },
308
294
  { timeout: WAIT_TIMEOUT },
309
295
  );
310
-
311
- expect(showToastMock).not.toHaveBeenCalled();
312
296
  });
313
297
  });