@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.
- package/Models/AnalyticsModels/Span.ts +101 -0
- package/Models/DatabaseModels/DetectionRule.ts +55 -0
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
- package/Models/DatabaseModels/Project.ts +68 -0
- package/Models/DatabaseModels/User.ts +68 -0
- package/Server/API/BaseAPI.ts +0 -24
- package/Server/API/SlackAPI.ts +0 -2
- package/Server/API/TelemetryAPI.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/GzipRequestBody.ts +311 -0
- package/Server/Middleware/MultipartFormData.ts +87 -3
- package/Server/Middleware/SlackAuthorization.ts +96 -18
- package/Server/Services/DetectionRuleService.ts +73 -6
- package/Server/Services/Index.ts +205 -0
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
- package/Server/Services/NetworkDeviceService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
- package/Server/Services/ProjectService.ts +29 -19
- package/Server/Services/SecurityEventService.ts +90 -13
- package/Server/Services/StatusPageResourceService.ts +223 -0
- package/Server/Services/TelemetryAttributeService.ts +17 -0
- package/Server/Services/UserService.ts +4 -6
- package/Server/Types/Markdown.ts +161 -67
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
- package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
- package/Server/Utils/Attribution.ts +4 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
- package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
- package/Server/Utils/StartServer.ts +12 -32
- package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
- package/Server/Utils/Telemetry/LlmMetricSpend.ts +181 -0
- package/Server/Utils/Telemetry/LlmSpan.ts +46 -0
- package/Server/Utils/Workspace/Slack/Actions/Auth.ts +0 -12
- package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
- package/Server/Views/Partials/Head.ejs +2 -1
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
- package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
- package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
- package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
- package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
- package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
- package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
- package/Tests/App/Dashboard/LlmCallsTableIdentity.test.tsx +322 -0
- package/Tests/App/Dashboard/LlmOverview.test.tsx +335 -0
- package/Tests/App/Dashboard/LlmSpanDisplay.test.ts +391 -0
- package/Tests/App/Dashboard/LlmUsageBreakdown.test.tsx +1007 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
- package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
- package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
- package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
- package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
- package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
- package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
- package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
- package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
- package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
- package/Tests/Server/API/BaseAPI.test.ts +41 -0
- package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +9 -16
- package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
- package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
- package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +262 -5
- package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
- package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
- package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
- package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
- package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
- package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
- package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
- package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
- package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
- package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +387 -4
- package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +386 -0
- package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +804 -0
- package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
- package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
- package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
- package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
- package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +584 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
- package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
- package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
- package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
- package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
- package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
- package/Tests/UI/Components/SideOver.test.tsx +1 -1
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
- package/Tests/UI/Components/Toggle.test.tsx +25 -0
- package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
- package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
- package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +770 -0
- package/Types/Code/YamlSyntax.ts +396 -0
- package/Types/Exception/ExceptionCode.ts +1 -0
- package/Types/Exception/PayloadTooLargeException.ts +8 -0
- package/Types/Marketing/Attribution.ts +10 -0
- package/Types/Marketing/MarketingEvent.ts +4 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
- package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
- package/Types/Telemetry/LlmConventions.ts +255 -0
- package/Types/Telemetry/LlmMetricConventions.ts +329 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
- package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
- package/UI/Components/Detail/Detail.tsx +10 -0
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
- package/UI/Components/Forms/Fields/FormField.tsx +31 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
- package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
- package/UI/Components/Forms/Validation.ts +46 -0
- package/UI/Components/Input/Input.tsx +3 -0
- package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
- package/UI/Components/ModelTable/Column.ts +8 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
- package/UI/Components/SideOver/SideOver.tsx +5 -5
- package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
- package/UI/Components/StatusPage/ResourceList.tsx +197 -38
- package/UI/Components/Toggle/Toggle.tsx +9 -1
- package/UI/Components/Types/FieldType.ts +1 -0
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
- package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
- package/Utils/StatusPage/ResourceExplorer.ts +258 -0
- package/Utils/Telemetry/LlmMetricQuery.ts +287 -0
- package/build/dist/Models/AnalyticsModels/Span.js +89 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +76 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +76 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +3 -19
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +0 -2
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +17 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
- package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
- package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
- package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +58 -7
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DetectionRuleService.js +49 -4
- package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +204 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +25 -45
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventService.js +57 -5
- package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
- package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +2 -12
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +147 -61
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -21
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +151 -0
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js +24 -1
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +0 -10
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
- package/build/dist/Types/Code/YamlSyntax.js +212 -0
- package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
- package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
- package/build/dist/Types/Marketing/Attribution.js +10 -0
- package/build/dist/Types/Marketing/Attribution.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmConventions.js +236 -0
- package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmMetricConventions.js +290 -0
- package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +9 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +27 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
- package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Types/FieldType.js +1 -0
- package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js +175 -0
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +2 -1
- package/Tests/UI/Components/Toast.test.tsx +0 -197
- package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
- package/UI/Components/Toast/Toast.tsx +0 -189
- package/UI/Components/Toast/ToastInit.tsx +0 -110
- package/build/dist/UI/Components/Toast/Toast.js +0 -123
- package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
- 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
|
|
394
|
-
* no severity fails
|
|
395
|
-
* whole create would
|
|
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
|
-
*
|
|
401
|
-
* MonitorCriteriaInstance.getValidationError, so the whole
|
|
402
|
-
* fail at submit with a message about a field the user
|
|
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
|
|
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
|
});
|