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