@oneuptime/common 12.0.23 → 12.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/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 +130 -0
- 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/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/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/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 +368 -4
- package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +245 -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 +193 -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 +472 -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/LlmMetricConventions.ts +124 -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 +204 -0
- 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/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/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 +102 -0
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
- 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/LlmMetricConventions.js +98 -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 +119 -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
|
@@ -5,7 +5,12 @@ import Select from "../../Types/AnalyticsDatabase/Select";
|
|
|
5
5
|
import Sort from "../../Types/AnalyticsDatabase/Sort";
|
|
6
6
|
import UpdateBy from "../../Types/AnalyticsDatabase/UpdateBy";
|
|
7
7
|
import logger from "../Logger";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
QualifiedColumn,
|
|
10
|
+
SQL,
|
|
11
|
+
Statement,
|
|
12
|
+
escapeIlikePattern,
|
|
13
|
+
} from "./Statement";
|
|
9
14
|
import {
|
|
10
15
|
adaptTableSettingsForStorage,
|
|
11
16
|
getDistributedEngine,
|
|
@@ -25,10 +30,12 @@ import AnalyticsTableColumn, {
|
|
|
25
30
|
} from "../../../Types/AnalyticsDatabase/TableColumn";
|
|
26
31
|
import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
|
|
27
32
|
import EqualTo from "../../../Types/BaseDatabase/EqualTo";
|
|
33
|
+
import EqualToOrNull from "../../../Types/BaseDatabase/EqualToOrNull";
|
|
28
34
|
import GreaterThan from "../../../Types/BaseDatabase/GreaterThan";
|
|
29
35
|
import GreaterThanOrEqual from "../../../Types/BaseDatabase/GreaterThanOrEqual";
|
|
30
36
|
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
31
37
|
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
38
|
+
import IncludesAll from "../../../Types/BaseDatabase/IncludesAll";
|
|
32
39
|
import IncludesNone from "../../../Types/BaseDatabase/IncludesNone";
|
|
33
40
|
import ObjectID from "../../../Types/ObjectID";
|
|
34
41
|
import IsNull from "../../../Types/BaseDatabase/IsNull";
|
|
@@ -39,6 +46,8 @@ import LessThanOrNull from "../../../Types/BaseDatabase/LessThanOrNull";
|
|
|
39
46
|
import NotEqual from "../../../Types/BaseDatabase/NotEqual";
|
|
40
47
|
import NotContains from "../../../Types/BaseDatabase/NotContains";
|
|
41
48
|
import NotNull from "../../../Types/BaseDatabase/NotNull";
|
|
49
|
+
import QueryOperator from "../../../Types/BaseDatabase/QueryOperator";
|
|
50
|
+
import GenericObject from "../../../Types/GenericObject";
|
|
42
51
|
import Search from "../../../Types/BaseDatabase/Search";
|
|
43
52
|
import MultiSearch from "../../../Types/BaseDatabase/MultiSearch";
|
|
44
53
|
import StartsWith from "../../../Types/BaseDatabase/StartsWith";
|
|
@@ -108,6 +117,28 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
108
117
|
this.database = data.database;
|
|
109
118
|
}
|
|
110
119
|
|
|
120
|
+
/*
|
|
121
|
+
* Map(String,String) and JSON columns have per-sub-key handling further
|
|
122
|
+
* down the operator chain; a scalar operator applied to the whole column
|
|
123
|
+
* would compile to SQL ClickHouse rejects with a cryptic type error.
|
|
124
|
+
* Fail with the same loud BadDataException the fallback uses.
|
|
125
|
+
*/
|
|
126
|
+
private throwIfMapOrJsonColumn(input: {
|
|
127
|
+
operator: QueryOperator<GenericObject | number | string>;
|
|
128
|
+
tableColumn: AnalyticsTableColumn;
|
|
129
|
+
key: string;
|
|
130
|
+
}): void {
|
|
131
|
+
if (
|
|
132
|
+
input.tableColumn.type === TableColumnType.MapStringString ||
|
|
133
|
+
input.tableColumn.type === TableColumnType.JSON ||
|
|
134
|
+
input.tableColumn.type === TableColumnType.JSONArray
|
|
135
|
+
) {
|
|
136
|
+
throw new BadDataException(
|
|
137
|
+
`Unsupported query operator ${input.operator.constructor.name} on column: ${input.key}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
111
142
|
private appendMapKeyPresenceFilter(input: {
|
|
112
143
|
whereStatement: Statement;
|
|
113
144
|
mapColumn: AnalyticsTableColumn;
|
|
@@ -755,13 +786,86 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
755
786
|
whereStatement.append(SQL` `);
|
|
756
787
|
}
|
|
757
788
|
|
|
758
|
-
|
|
789
|
+
/*
|
|
790
|
+
* Several operators AND-ed onto ONE column — e.g.
|
|
791
|
+
* `observables: [Includes([x]), IncludesNone([y])]` for "mentions x
|
|
792
|
+
* AND does not mention y". The flat query map has a single slot per
|
|
793
|
+
* column, so without this an AND of two predicates on the same column
|
|
794
|
+
* is inexpressible (the Security Events correlation builder needs
|
|
795
|
+
* it). Each element compiles through a single-key recursive call, so
|
|
796
|
+
* every per-operator branch below applies unchanged; elements whose
|
|
797
|
+
* branch drops the predicate (e.g. an empty Includes) contribute
|
|
798
|
+
* nothing. A bare value array (exact-array equality on ArrayText)
|
|
799
|
+
* has non-operator elements and falls through untouched.
|
|
800
|
+
*/
|
|
801
|
+
if (
|
|
802
|
+
Array.isArray(value) &&
|
|
803
|
+
value.length > 0 &&
|
|
804
|
+
value.every((element: unknown) => {
|
|
805
|
+
return element instanceof QueryOperator;
|
|
806
|
+
})
|
|
807
|
+
) {
|
|
808
|
+
let isFirstFragment: boolean = true;
|
|
809
|
+
for (const operator of value) {
|
|
810
|
+
const fragment: Statement = this.toWhereStatement(
|
|
811
|
+
{ [key]: operator } as Query<TBaseModel>,
|
|
812
|
+
options,
|
|
813
|
+
);
|
|
814
|
+
if (!fragment.query) {
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
if (isFirstFragment) {
|
|
818
|
+
isFirstFragment = false;
|
|
819
|
+
} else {
|
|
820
|
+
whereStatement.append(SQL` `);
|
|
821
|
+
}
|
|
822
|
+
whereStatement.append(fragment);
|
|
823
|
+
}
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (
|
|
828
|
+
value instanceof Search &&
|
|
829
|
+
tableColumn.type === TableColumnType.ArrayText
|
|
830
|
+
) {
|
|
831
|
+
/*
|
|
832
|
+
* Substring search over an Array(String) column ("any element
|
|
833
|
+
* contains v"): `arrayExists(x -> x ILIKE '%v%', col)`. The scalar
|
|
834
|
+
* `col ILIKE ...` form below would declare the bound parameter as
|
|
835
|
+
* Array(String) while carrying a single pattern string, which
|
|
836
|
+
* ClickHouse rejects at parameter-parse time — so array columns get
|
|
837
|
+
* their own per-element form. This is what makes a plain text filter
|
|
838
|
+
* on `observables` (Security Events) work at all.
|
|
839
|
+
*/
|
|
840
|
+
whereStatement.append(
|
|
841
|
+
SQL`AND arrayExists(x -> x ILIKE ${{
|
|
842
|
+
value: value,
|
|
843
|
+
type: TableColumnType.Text,
|
|
844
|
+
}}, ${columnRef(key)})`,
|
|
845
|
+
);
|
|
846
|
+
} else if (value instanceof Search) {
|
|
759
847
|
whereStatement.append(
|
|
760
848
|
SQL`AND ${columnRef(key)} ILIKE ${{
|
|
761
849
|
value: value,
|
|
762
850
|
type: tableColumn.type,
|
|
763
851
|
}}`,
|
|
764
852
|
);
|
|
853
|
+
} else if (
|
|
854
|
+
value instanceof NotEqual &&
|
|
855
|
+
tableColumn.type === TableColumnType.ArrayText
|
|
856
|
+
) {
|
|
857
|
+
/*
|
|
858
|
+
* "not equal to v" on an Array(String) column means "does not
|
|
859
|
+
* mention v" — the scalar `col != v` form would bind a single
|
|
860
|
+
* string against an Array(String) parameter and fail at
|
|
861
|
+
* ClickHouse parameter-parse time.
|
|
862
|
+
*/
|
|
863
|
+
whereStatement.append(
|
|
864
|
+
SQL`AND NOT has(${columnRef(key)}, ${{
|
|
865
|
+
value: String((value as NotEqual<string>).value ?? ""),
|
|
866
|
+
type: TableColumnType.Text,
|
|
867
|
+
}})`,
|
|
868
|
+
);
|
|
765
869
|
} else if (value instanceof NotEqual) {
|
|
766
870
|
whereStatement.append(
|
|
767
871
|
SQL`AND ${columnRef(key)} != ${{
|
|
@@ -887,13 +991,183 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
887
991
|
);
|
|
888
992
|
}
|
|
889
993
|
} else if (value instanceof IsNull) {
|
|
890
|
-
if (tableColumn.type === TableColumnType.
|
|
994
|
+
if (tableColumn.type === TableColumnType.ArrayText) {
|
|
995
|
+
/*
|
|
996
|
+
* Array(String) columns are non-Nullable — `IS NULL` would be
|
|
997
|
+
* constant-false. "Not set" for an array is the empty array.
|
|
998
|
+
*/
|
|
999
|
+
whereStatement.append(SQL`AND empty(${columnRef(key)})`);
|
|
1000
|
+
} else if (tableColumn.type === TableColumnType.Text) {
|
|
891
1001
|
whereStatement.append(
|
|
892
1002
|
SQL`AND (${columnRef(key)} IS NULL OR ${columnRef(key)} = '')`,
|
|
893
1003
|
);
|
|
894
1004
|
} else {
|
|
895
1005
|
whereStatement.append(SQL`AND ${columnRef(key)} IS NULL`);
|
|
896
1006
|
}
|
|
1007
|
+
} else if (value instanceof NotNull) {
|
|
1008
|
+
/*
|
|
1009
|
+
* Mirror of IsNull above: Text columns store '' as their "not set"
|
|
1010
|
+
* default (they are non-nullable Strings unless optional), so a
|
|
1011
|
+
* present-value check must reject the empty string too; Array
|
|
1012
|
+
* columns are non-Nullable, so `IS NOT NULL` would be constant-true
|
|
1013
|
+
* — presence for an array means "has at least one element".
|
|
1014
|
+
*/
|
|
1015
|
+
if (tableColumn.type === TableColumnType.ArrayText) {
|
|
1016
|
+
whereStatement.append(SQL`AND notEmpty(${columnRef(key)})`);
|
|
1017
|
+
} else if (tableColumn.type === TableColumnType.Text) {
|
|
1018
|
+
whereStatement.append(
|
|
1019
|
+
SQL`AND (${columnRef(key)} IS NOT NULL AND ${columnRef(key)} != '')`,
|
|
1020
|
+
);
|
|
1021
|
+
} else {
|
|
1022
|
+
whereStatement.append(SQL`AND ${columnRef(key)} IS NOT NULL`);
|
|
1023
|
+
}
|
|
1024
|
+
} else if (value instanceof EqualTo) {
|
|
1025
|
+
/*
|
|
1026
|
+
* Explicit equality wrapper. Before this branch existed an EqualTo
|
|
1027
|
+
* instance fell through to the bare-value fallback, which bound the
|
|
1028
|
+
* operator OBJECT as the parameter — a silent match-nothing filter.
|
|
1029
|
+
* Scalars bind the wrapped value exactly like a bare value; on an
|
|
1030
|
+
* Array(String) column "equals v" means membership (`has`), which
|
|
1031
|
+
* is what a table filter's Equal To on `observables` intends. Map
|
|
1032
|
+
* and JSON columns have no scalar equality — fail loudly instead
|
|
1033
|
+
* of emitting SQL ClickHouse will reject with a cryptic error.
|
|
1034
|
+
*/
|
|
1035
|
+
this.throwIfMapOrJsonColumn({ operator: value, tableColumn, key });
|
|
1036
|
+
if (tableColumn.type === TableColumnType.ArrayText) {
|
|
1037
|
+
whereStatement.append(
|
|
1038
|
+
SQL`AND has(${columnRef(key)}, ${{
|
|
1039
|
+
value: String((value as EqualTo<string>).value ?? ""),
|
|
1040
|
+
type: TableColumnType.Text,
|
|
1041
|
+
}})`,
|
|
1042
|
+
);
|
|
1043
|
+
} else {
|
|
1044
|
+
whereStatement.append(
|
|
1045
|
+
SQL`AND ${columnRef(key)} = ${{
|
|
1046
|
+
value: (value as EqualTo<string>).value,
|
|
1047
|
+
type: tableColumn.type,
|
|
1048
|
+
}}`,
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
} else if (value instanceof EqualToOrNull) {
|
|
1052
|
+
this.throwIfMapOrJsonColumn({ operator: value, tableColumn, key });
|
|
1053
|
+
if (tableColumn.type === TableColumnType.ArrayText) {
|
|
1054
|
+
throw new BadDataException(
|
|
1055
|
+
`Unsupported query operator EqualToOrNull on column: ${key}`,
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
if (tableColumn.type === TableColumnType.Text) {
|
|
1059
|
+
whereStatement.append(
|
|
1060
|
+
SQL`AND (${columnRef(key)} = ${{
|
|
1061
|
+
value: (value as EqualToOrNull<string>).value,
|
|
1062
|
+
type: tableColumn.type,
|
|
1063
|
+
}} OR ${columnRef(key)} IS NULL OR ${columnRef(key)} = '')`,
|
|
1064
|
+
);
|
|
1065
|
+
} else {
|
|
1066
|
+
whereStatement.append(
|
|
1067
|
+
SQL`AND (${columnRef(key)} = ${{
|
|
1068
|
+
value: (value as EqualToOrNull<string>).value,
|
|
1069
|
+
type: tableColumn.type,
|
|
1070
|
+
}} OR ${columnRef(key)} IS NULL)`,
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
} else if (value instanceof StartsWith) {
|
|
1074
|
+
/*
|
|
1075
|
+
* Prefix / suffix / negated-substring matching, previously only
|
|
1076
|
+
* implemented for map (attributes) sub-keys. Array(String) columns
|
|
1077
|
+
* get the per-element arrayExists form (see the Search branch
|
|
1078
|
+
* above); scalars get a plain ILIKE. Patterns bind as Text because
|
|
1079
|
+
* the pattern itself is a string whatever the column type is, and
|
|
1080
|
+
* the user value is escaped so it matches literally rather than as
|
|
1081
|
+
* wildcard syntax.
|
|
1082
|
+
*/
|
|
1083
|
+
this.throwIfMapOrJsonColumn({ operator: value, tableColumn, key });
|
|
1084
|
+
const startsWithPattern: string = `${escapeIlikePattern(
|
|
1085
|
+
(value.value as string) || "",
|
|
1086
|
+
)}%`;
|
|
1087
|
+
if (tableColumn.type === TableColumnType.ArrayText) {
|
|
1088
|
+
whereStatement.append(
|
|
1089
|
+
SQL`AND arrayExists(x -> x ILIKE ${{
|
|
1090
|
+
value: startsWithPattern,
|
|
1091
|
+
type: TableColumnType.Text,
|
|
1092
|
+
}}, ${columnRef(key)})`,
|
|
1093
|
+
);
|
|
1094
|
+
} else {
|
|
1095
|
+
whereStatement.append(
|
|
1096
|
+
SQL`AND ${columnRef(key)} ILIKE ${{
|
|
1097
|
+
value: startsWithPattern,
|
|
1098
|
+
type: TableColumnType.Text,
|
|
1099
|
+
}}`,
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
} else if (value instanceof EndsWith) {
|
|
1103
|
+
this.throwIfMapOrJsonColumn({ operator: value, tableColumn, key });
|
|
1104
|
+
const endsWithPattern: string = `%${escapeIlikePattern(
|
|
1105
|
+
(value.value as string) || "",
|
|
1106
|
+
)}`;
|
|
1107
|
+
if (tableColumn.type === TableColumnType.ArrayText) {
|
|
1108
|
+
whereStatement.append(
|
|
1109
|
+
SQL`AND arrayExists(x -> x ILIKE ${{
|
|
1110
|
+
value: endsWithPattern,
|
|
1111
|
+
type: TableColumnType.Text,
|
|
1112
|
+
}}, ${columnRef(key)})`,
|
|
1113
|
+
);
|
|
1114
|
+
} else {
|
|
1115
|
+
whereStatement.append(
|
|
1116
|
+
SQL`AND ${columnRef(key)} ILIKE ${{
|
|
1117
|
+
value: endsWithPattern,
|
|
1118
|
+
type: TableColumnType.Text,
|
|
1119
|
+
}}`,
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
} else if (value instanceof NotContains) {
|
|
1123
|
+
/*
|
|
1124
|
+
* On a nullable scalar, `NOT (NULL ILIKE ...)` is NULL and would
|
|
1125
|
+
* filter the row out — but a row with no value at all trivially
|
|
1126
|
+
* "does not contain" the needle, so NULL rows are let through
|
|
1127
|
+
* explicitly (mirrors how missing map keys pass the map-branch
|
|
1128
|
+
* NotContains).
|
|
1129
|
+
*/
|
|
1130
|
+
this.throwIfMapOrJsonColumn({ operator: value, tableColumn, key });
|
|
1131
|
+
const notContainsPattern: string = `%${escapeIlikePattern(
|
|
1132
|
+
(value.value as string) || "",
|
|
1133
|
+
)}%`;
|
|
1134
|
+
if (tableColumn.type === TableColumnType.ArrayText) {
|
|
1135
|
+
whereStatement.append(
|
|
1136
|
+
SQL`AND NOT arrayExists(x -> x ILIKE ${{
|
|
1137
|
+
value: notContainsPattern,
|
|
1138
|
+
type: TableColumnType.Text,
|
|
1139
|
+
}}, ${columnRef(key)})`,
|
|
1140
|
+
);
|
|
1141
|
+
} else {
|
|
1142
|
+
whereStatement.append(
|
|
1143
|
+
SQL`AND (NOT (${columnRef(key)} ILIKE ${{
|
|
1144
|
+
value: notContainsPattern,
|
|
1145
|
+
type: TableColumnType.Text,
|
|
1146
|
+
}}) OR ${columnRef(key)} IS NULL)`,
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
} else if (
|
|
1150
|
+
value instanceof IncludesAll &&
|
|
1151
|
+
tableColumn.type === TableColumnType.ArrayText
|
|
1152
|
+
) {
|
|
1153
|
+
/*
|
|
1154
|
+
* Array(String) conjunction ("mentions ALL of these"): `hasAll`,
|
|
1155
|
+
* the AND-counterpart of the Includes/hasAny branch above. This is
|
|
1156
|
+
* what a correlation like "events naming host X AND ip Y" compiles
|
|
1157
|
+
* to on the `observables` column. An empty IncludesAll constrains
|
|
1158
|
+
* nothing — drop the predicate (hasAll(col, []) is vacuously true
|
|
1159
|
+
* anyway, but skipping keeps parity with the empty-Includes path).
|
|
1160
|
+
*/
|
|
1161
|
+
const arrayAllValues: Array<string> =
|
|
1162
|
+
((value as IncludesAll).values as Array<string>) || [];
|
|
1163
|
+
if (arrayAllValues.length > 0) {
|
|
1164
|
+
whereStatement.append(
|
|
1165
|
+
SQL`AND hasAll(${columnRef(key)}, ${{
|
|
1166
|
+
value: arrayAllValues,
|
|
1167
|
+
type: TableColumnType.ArrayText,
|
|
1168
|
+
}})`,
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
897
1171
|
} else if (
|
|
898
1172
|
tableColumn.type === TableColumnType.MapStringString &&
|
|
899
1173
|
typeof value === "object"
|
|
@@ -985,7 +1259,9 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
985
1259
|
}
|
|
986
1260
|
|
|
987
1261
|
if (mapEntry instanceof NotContains) {
|
|
988
|
-
const literalValue: string = `%${(
|
|
1262
|
+
const literalValue: string = `%${escapeIlikePattern(
|
|
1263
|
+
(mapEntry.value as string) || "",
|
|
1264
|
+
)}%`;
|
|
989
1265
|
whereStatement.append(
|
|
990
1266
|
SQL`AND NOT arrayExists((k, v) -> lowerUTF8(k) = lowerUTF8(${{
|
|
991
1267
|
value: mapKey,
|
|
@@ -999,7 +1275,9 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
999
1275
|
}
|
|
1000
1276
|
|
|
1001
1277
|
if (mapEntry instanceof StartsWith) {
|
|
1002
|
-
const literalValue: string = `${(
|
|
1278
|
+
const literalValue: string = `${escapeIlikePattern(
|
|
1279
|
+
(mapEntry.value as string) || "",
|
|
1280
|
+
)}%`;
|
|
1003
1281
|
whereStatement.append(
|
|
1004
1282
|
SQL`AND arrayExists((k, v) -> lowerUTF8(k) = lowerUTF8(${{
|
|
1005
1283
|
value: mapKey,
|
|
@@ -1013,7 +1291,9 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
1013
1291
|
}
|
|
1014
1292
|
|
|
1015
1293
|
if (mapEntry instanceof EndsWith) {
|
|
1016
|
-
const literalValue: string = `%${(
|
|
1294
|
+
const literalValue: string = `%${escapeIlikePattern(
|
|
1295
|
+
(mapEntry.value as string) || "",
|
|
1296
|
+
)}`;
|
|
1017
1297
|
whereStatement.append(
|
|
1018
1298
|
SQL`AND arrayExists((k, v) -> lowerUTF8(k) = lowerUTF8(${{
|
|
1019
1299
|
value: mapKey,
|
|
@@ -1290,6 +1570,17 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
1290
1570
|
}
|
|
1291
1571
|
}
|
|
1292
1572
|
} else {
|
|
1573
|
+
/*
|
|
1574
|
+
* Bare-value equality. A query operator that reaches this point has
|
|
1575
|
+
* no branch for this column type (e.g. IncludesAll on a scalar) —
|
|
1576
|
+
* binding the operator object itself would produce a silent
|
|
1577
|
+
* match-nothing filter, so fail loudly instead.
|
|
1578
|
+
*/
|
|
1579
|
+
if (value instanceof QueryOperator) {
|
|
1580
|
+
throw new BadDataException(
|
|
1581
|
+
`Unsupported query operator ${value.constructor.name} on column: ${key}`,
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1293
1584
|
whereStatement.append(
|
|
1294
1585
|
SQL`AND ${columnRef(key)} = ${{ value, type: tableColumn.type }}`,
|
|
1295
1586
|
);
|
|
@@ -46,6 +46,10 @@ export interface UtmAttribution {
|
|
|
46
46
|
utmCampaign?: string | undefined;
|
|
47
47
|
utmTerm?: string | undefined;
|
|
48
48
|
utmContent?: string | undefined;
|
|
49
|
+
utmId?: string | undefined;
|
|
50
|
+
utmSourcePlatform?: string | undefined;
|
|
51
|
+
utmCreativeFormat?: string | undefined;
|
|
52
|
+
utmMarketingTactic?: string | undefined;
|
|
49
53
|
utmUrl?: string | undefined;
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import Attribution from "../Attribution";
|
|
2
2
|
import MarketingEventWebhook from "./MarketingEventWebhook";
|
|
3
|
+
import {
|
|
4
|
+
UtmPropertyKeys,
|
|
5
|
+
UtmUrlPropertyKey,
|
|
6
|
+
UtmWireKeyToPropertyKey,
|
|
7
|
+
} from "../../../Types/Marketing/Attribution";
|
|
3
8
|
import Queue, { QueueName } from "../../Infrastructure/Queue";
|
|
4
9
|
import { JSONObject } from "../../../Types/JSON";
|
|
5
10
|
import {
|
|
@@ -34,25 +39,58 @@ export interface AttributionSource {
|
|
|
34
39
|
utmCampaign?: string | undefined;
|
|
35
40
|
utmTerm?: string | undefined;
|
|
36
41
|
utmContent?: string | undefined;
|
|
42
|
+
utmId?: string | undefined;
|
|
43
|
+
utmSourcePlatform?: string | undefined;
|
|
44
|
+
utmCreativeFormat?: string | undefined;
|
|
45
|
+
utmMarketingTactic?: string | undefined;
|
|
37
46
|
utmUrl?: string | undefined;
|
|
38
47
|
clickIds?: JSONObject | undefined;
|
|
39
48
|
firstTouchAttribution?: JSONObject | undefined;
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
/*
|
|
52
|
+
* The campaign columns as snake_case analytics properties, driven from the
|
|
53
|
+
* shared contract so a key added there cannot be reported by the browser and
|
|
54
|
+
* the receiver while silently missing from PostHog.
|
|
55
|
+
*/
|
|
56
|
+
export function utmAnalyticsProperties(row: {
|
|
57
|
+
[key: string]: unknown;
|
|
58
|
+
}): JSONObject {
|
|
59
|
+
const properties: JSONObject = {};
|
|
60
|
+
|
|
61
|
+
for (const [wireKey, propertyKey] of Object.entries(
|
|
62
|
+
UtmWireKeyToPropertyKey,
|
|
63
|
+
)) {
|
|
64
|
+
properties[wireKey] = (row[propertyKey] as string | undefined) || "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return properties;
|
|
68
|
+
}
|
|
69
|
+
|
|
42
70
|
export default class MarketingEventUtil {
|
|
71
|
+
/*
|
|
72
|
+
* Driven from UtmPropertyKeys rather than hand-listed. Hand-listing is how
|
|
73
|
+
* utm_id and its three GA4 siblings reached the browser, the User row and
|
|
74
|
+
* the Cal booking metadata while silently never reaching a receiver.
|
|
75
|
+
*
|
|
76
|
+
* Additive only, so the schema version does not move
|
|
77
|
+
* (Docs/analytics/marketing-event-webhooks.md).
|
|
78
|
+
*/
|
|
43
79
|
public static buildAttribution(
|
|
44
80
|
source: AttributionSource | undefined,
|
|
45
81
|
): MarketingEventAttribution {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
utmCampaign: source?.utmCampaign,
|
|
50
|
-
utmTerm: source?.utmTerm,
|
|
51
|
-
utmContent: source?.utmContent,
|
|
52
|
-
utmUrl: source?.utmUrl,
|
|
82
|
+
const sourceRow: JSONObject = (source || {}) as unknown as JSONObject;
|
|
83
|
+
|
|
84
|
+
const attribution: MarketingEventAttribution = {
|
|
53
85
|
clickIds: source?.clickIds || {},
|
|
54
86
|
firstTouch: source?.firstTouchAttribution || {},
|
|
55
87
|
};
|
|
88
|
+
|
|
89
|
+
for (const propertyKey of [...UtmPropertyKeys, UtmUrlPropertyKey]) {
|
|
90
|
+
attribution[propertyKey] = sourceRow[propertyKey];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return attribution;
|
|
56
94
|
}
|
|
57
95
|
|
|
58
96
|
public static buildEvent(data: {
|
|
@@ -16,6 +16,7 @@ import OcsfSeverity, {
|
|
|
16
16
|
import { ocsfCategoryForClassUid } from "../../../Types/SecurityEvent/OcsfEventClass";
|
|
17
17
|
import SigmaRule, { SigmaLevel } from "../../../Types/SecurityEvent/SigmaRule";
|
|
18
18
|
import {
|
|
19
|
+
DETECTION_DISTINCT_COUNT_ATTRIBUTE,
|
|
19
20
|
DETECTION_FINDING_CLASS_NAME,
|
|
20
21
|
DETECTION_FINDING_CLASS_UID,
|
|
21
22
|
DETECTION_GROUP_VALUE_ATTRIBUTE,
|
|
@@ -36,12 +37,14 @@ import OTelIngestService, {
|
|
|
36
37
|
} from "../../Services/OpenTelemetryIngestService";
|
|
37
38
|
import SecurityEventService, {
|
|
38
39
|
DetectionMatchGroup,
|
|
40
|
+
doesGroupMeetDetectionThreshold,
|
|
39
41
|
} from "../../Services/SecurityEventService";
|
|
40
42
|
import { resolveTelemetryRetentionInDays } from "../../../Types/Telemetry/TelemetryRetentionConfig";
|
|
41
43
|
import logger from "../Logger";
|
|
42
44
|
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
43
45
|
import { Statement } from "../AnalyticsDatabase/Statement";
|
|
44
46
|
import SigmaClickhouseCompiler, {
|
|
47
|
+
buildSigmaDistinctCountExpression,
|
|
45
48
|
buildSigmaFieldExpression,
|
|
46
49
|
} from "./Sigma/SigmaClickhouseCompiler";
|
|
47
50
|
import { buildSecurityEventDbRow } from "./SecurityEventRow";
|
|
@@ -96,6 +99,8 @@ export default class DetectionRuleEvaluator {
|
|
|
96
99
|
sigmaRuleYaml: true,
|
|
97
100
|
evaluationIntervalInMinutes: true,
|
|
98
101
|
groupByField: true,
|
|
102
|
+
distinctCountField: true,
|
|
103
|
+
matchCountThreshold: true,
|
|
99
104
|
shouldCreateAlert: true,
|
|
100
105
|
shouldWriteDetectionFinding: true,
|
|
101
106
|
shouldCreateIncident: true,
|
|
@@ -188,6 +193,12 @@ export default class DetectionRuleEvaluator {
|
|
|
188
193
|
? buildSigmaFieldExpression(rule.groupByField)
|
|
189
194
|
: null;
|
|
190
195
|
|
|
196
|
+
const distinctCountExpression: Statement | null = rule.distinctCountField
|
|
197
|
+
? buildSigmaDistinctCountExpression(rule.distinctCountField)
|
|
198
|
+
: null;
|
|
199
|
+
|
|
200
|
+
const matchCountThreshold: number = this.effectiveThreshold(rule);
|
|
201
|
+
|
|
191
202
|
const groups: Array<DetectionMatchGroup> =
|
|
192
203
|
await SecurityEventService.findDetectionMatches({
|
|
193
204
|
projectId: rule.projectId,
|
|
@@ -195,12 +206,24 @@ export default class DetectionRuleEvaluator {
|
|
|
195
206
|
endTime,
|
|
196
207
|
whereFragment,
|
|
197
208
|
groupByExpression,
|
|
209
|
+
distinctCountExpression,
|
|
210
|
+
minMatchCount: matchCountThreshold,
|
|
198
211
|
maxGroups: MAX_GROUPS_PER_EVALUATION,
|
|
199
212
|
});
|
|
200
213
|
|
|
214
|
+
/*
|
|
215
|
+
* The query's HAVING clause already enforces the threshold — this
|
|
216
|
+
* re-applies it (via the predicate exported next to that HAVING
|
|
217
|
+
* builder) so the firing contract does not depend on which side
|
|
218
|
+
* built the rows.
|
|
219
|
+
*/
|
|
201
220
|
const matchedGroups: Array<DetectionMatchGroup> = groups.filter(
|
|
202
221
|
(row: DetectionMatchGroup): boolean => {
|
|
203
|
-
return
|
|
222
|
+
return doesGroupMeetDetectionThreshold({
|
|
223
|
+
group: row,
|
|
224
|
+
usesDistinctCount: Boolean(rule.distinctCountField),
|
|
225
|
+
minMatchCount: matchCountThreshold,
|
|
226
|
+
});
|
|
204
227
|
},
|
|
205
228
|
);
|
|
206
229
|
|
|
@@ -546,6 +569,16 @@ export default class DetectionRuleEvaluator {
|
|
|
546
569
|
)} and ${OneUptimeDate.getDateAsFormattedString(data.endTime)}.`,
|
|
547
570
|
);
|
|
548
571
|
|
|
572
|
+
if (rule.distinctCountField) {
|
|
573
|
+
descriptionParts.push(
|
|
574
|
+
`${matchGroup.distinctCount} distinct ${rule.distinctCountField} value${
|
|
575
|
+
matchGroup.distinctCount === 1 ? "" : "s"
|
|
576
|
+
} across these events (rule threshold: ${this.effectiveThreshold(
|
|
577
|
+
rule,
|
|
578
|
+
)}).`,
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
549
582
|
if (matchGroup.sampleMessage) {
|
|
550
583
|
descriptionParts.push(`Sample event: ${matchGroup.sampleMessage}`);
|
|
551
584
|
}
|
|
@@ -590,6 +623,10 @@ export default class DetectionRuleEvaluator {
|
|
|
590
623
|
|
|
591
624
|
const rows: Array<JSONObject> = matchedGroups.map(
|
|
592
625
|
(matchGroup: DetectionMatchGroup): JSONObject => {
|
|
626
|
+
const distinctSuffix: string = rule.distinctCountField
|
|
627
|
+
? `, ${matchGroup.distinctCount} distinct ${rule.distinctCountField}`
|
|
628
|
+
: "";
|
|
629
|
+
|
|
593
630
|
const normalized: NormalizedSecurityEvent = {
|
|
594
631
|
time: OneUptimeDate.getCurrentDate(),
|
|
595
632
|
eventUid: `detection:${rule.id!.toString()}:${this.buildFingerprint(
|
|
@@ -605,8 +642,8 @@ export default class DetectionRuleEvaluator {
|
|
|
605
642
|
severityName,
|
|
606
643
|
statusName: "New",
|
|
607
644
|
message: matchGroup.groupValue
|
|
608
|
-
? `Detection: ${rule.name} — ${matchGroup.groupValue} (${matchGroup.matchCount} events)`
|
|
609
|
-
: `Detection: ${rule.name} (${matchGroup.matchCount} events)`,
|
|
645
|
+
? `Detection: ${rule.name} — ${matchGroup.groupValue} (${matchGroup.matchCount} events${distinctSuffix})`
|
|
646
|
+
: `Detection: ${rule.name} (${matchGroup.matchCount} events${distinctSuffix})`,
|
|
610
647
|
vendorName: "OneUptime",
|
|
611
648
|
productName: "OneUptime Detections",
|
|
612
649
|
ruleId: rule.id!.toString(),
|
|
@@ -636,6 +673,13 @@ export default class DetectionRuleEvaluator {
|
|
|
636
673
|
[DETECTION_RULE_ID_ATTRIBUTE]: rule.id!.toString(),
|
|
637
674
|
[DETECTION_RULE_NAME_ATTRIBUTE]: rule.name || parsedRule.title,
|
|
638
675
|
[DETECTION_MATCH_COUNT_ATTRIBUTE]: String(matchGroup.matchCount),
|
|
676
|
+
...(rule.distinctCountField
|
|
677
|
+
? {
|
|
678
|
+
[DETECTION_DISTINCT_COUNT_ATTRIBUTE]: String(
|
|
679
|
+
matchGroup.distinctCount,
|
|
680
|
+
),
|
|
681
|
+
}
|
|
682
|
+
: {}),
|
|
639
683
|
...(matchGroup.groupValue
|
|
640
684
|
? { [DETECTION_GROUP_VALUE_ATTRIBUTE]: matchGroup.groupValue }
|
|
641
685
|
: {}),
|
|
@@ -659,6 +703,14 @@ export default class DetectionRuleEvaluator {
|
|
|
659
703
|
return rows.length;
|
|
660
704
|
}
|
|
661
705
|
|
|
706
|
+
/*
|
|
707
|
+
* Unset and 0 both read as 1 so every rule saved before the threshold
|
|
708
|
+
* column existed keeps its fire-on-any-match behavior.
|
|
709
|
+
*/
|
|
710
|
+
private static effectiveThreshold(rule: DetectionRule): number {
|
|
711
|
+
return Math.max(1, rule.matchCountThreshold || 1);
|
|
712
|
+
}
|
|
713
|
+
|
|
662
714
|
private static buildFingerprint(
|
|
663
715
|
ruleId: ObjectID,
|
|
664
716
|
groupValue: string,
|
|
@@ -242,6 +242,27 @@ export function buildSigmaFieldExpression(field: string): Statement {
|
|
|
242
242
|
return new Statement([resolved.column], []);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
/*
|
|
246
|
+
* buildSigmaFieldExpression with the column's "unset" sentinel normalized
|
|
247
|
+
* away, for distinct counting. Text and attribute lookups yield '' when
|
|
248
|
+
* absent, which the caller's nullIf(expr, '') already excludes — but the
|
|
249
|
+
* number columns are non-Nullable with 0 as their documented
|
|
250
|
+
* not-applicable value, and toString(0) is '0', not ''. Mapping 0 to NULL
|
|
251
|
+
* here, where the column type is known, keeps a portless event from
|
|
252
|
+
* counting as distinct port "0" in a port-scan rule. severityId 0 (OCSF
|
|
253
|
+
* Unknown) is deliberately treated as missing too, consistent with the
|
|
254
|
+
* empty-string rule for text.
|
|
255
|
+
*/
|
|
256
|
+
export function buildSigmaDistinctCountExpression(field: string): Statement {
|
|
257
|
+
const resolved: ResolvedField = resolveSigmaField(field);
|
|
258
|
+
|
|
259
|
+
if (resolved.kind === "numberColumn") {
|
|
260
|
+
return new Statement([`toString(nullIf(${resolved.column}, 0))`], []);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return buildSigmaFieldExpression(field);
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
export default class SigmaClickhouseCompiler {
|
|
246
267
|
/*
|
|
247
268
|
* Compile a rule (or raw YAML) into a boolean SQL expression. The
|