@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
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import ColumnCustomizationModal, {
|
|
2
|
+
AddableColumnsConfig,
|
|
3
|
+
} from "../../../../UI/Components/ModelTable/ColumnCustomizationModal";
|
|
4
|
+
import {
|
|
5
|
+
CustomizableColumn,
|
|
6
|
+
ModelTableColumn,
|
|
7
|
+
} from "../../../../UI/Components/ModelTable/ColumnPreference";
|
|
8
|
+
import { getAttributeColumns } from "../../../../UI/Components/ModelTable/AttributeColumns";
|
|
9
|
+
import Column from "../../../../UI/Components/ModelTable/Column";
|
|
10
|
+
import Columns from "../../../../UI/Components/ModelTable/Columns";
|
|
11
|
+
import FieldType from "../../../../UI/Components/Types/FieldType";
|
|
12
|
+
import SecurityEvent from "../../../../Models/AnalyticsModels/SecurityEvent";
|
|
13
|
+
import getJestMockFunction, { MockFunction } from "../../../MockType";
|
|
14
|
+
import "@testing-library/jest-dom";
|
|
15
|
+
import {
|
|
16
|
+
RenderResult,
|
|
17
|
+
cleanup,
|
|
18
|
+
fireEvent,
|
|
19
|
+
render,
|
|
20
|
+
} from "@testing-library/react";
|
|
21
|
+
import React from "react";
|
|
22
|
+
import {
|
|
23
|
+
afterEach,
|
|
24
|
+
beforeEach,
|
|
25
|
+
describe,
|
|
26
|
+
expect,
|
|
27
|
+
jest,
|
|
28
|
+
test,
|
|
29
|
+
} from "@jest/globals";
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* The "add a column" half of the picker.
|
|
33
|
+
*
|
|
34
|
+
* It exists because some columns cannot be a checklist: the keys inside a
|
|
35
|
+
* security event's `attributes` map are whatever the source sent, differ per
|
|
36
|
+
* event class, and there can be several hundred of them. A checkbox each would
|
|
37
|
+
* bury the columns the table was designed around and hand "Show all" a way to
|
|
38
|
+
* put 500 columns on screen. So they are offered through a search box, and
|
|
39
|
+
* only what someone asks for is materialized.
|
|
40
|
+
*
|
|
41
|
+
* The invariants that carry weight, all pinned below:
|
|
42
|
+
*
|
|
43
|
+
* 1. Adding is still staging. Nothing leaves the modal before Save, exactly
|
|
44
|
+
* like every checkbox and every reorder.
|
|
45
|
+
* 2. A column already in the list is not offered again - otherwise adding it
|
|
46
|
+
* twice would put two columns with the same id into the layout.
|
|
47
|
+
* 3. Removing is hiding plus forgetting, so it is bound by the same rule:
|
|
48
|
+
* the last column standing cannot go.
|
|
49
|
+
* 4. Only columns that say they are removable get a remove button. A column
|
|
50
|
+
* the table ships must never be removable, because nothing would put it
|
|
51
|
+
* back.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
type MakeEntryFunction = (data: {
|
|
55
|
+
id: string;
|
|
56
|
+
title: string;
|
|
57
|
+
isVisible: boolean;
|
|
58
|
+
isRemovable?: boolean | undefined;
|
|
59
|
+
}) => CustomizableColumn<SecurityEvent>;
|
|
60
|
+
|
|
61
|
+
const makeEntry: MakeEntryFunction = (data: {
|
|
62
|
+
id: string;
|
|
63
|
+
title: string;
|
|
64
|
+
isVisible: boolean;
|
|
65
|
+
isRemovable?: boolean | undefined;
|
|
66
|
+
}): CustomizableColumn<SecurityEvent> => {
|
|
67
|
+
const column: ModelTableColumn<SecurityEvent> = {
|
|
68
|
+
field: { message: true },
|
|
69
|
+
title: data.title,
|
|
70
|
+
type: FieldType.Text,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
id: data.id,
|
|
75
|
+
column: column,
|
|
76
|
+
isVisible: data.isVisible,
|
|
77
|
+
isPinned: false,
|
|
78
|
+
isRemovable: data.isRemovable,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type GetDeclaredColumnsFunction = () => Array<
|
|
83
|
+
CustomizableColumn<SecurityEvent>
|
|
84
|
+
>;
|
|
85
|
+
|
|
86
|
+
const getDeclaredColumns: GetDeclaredColumnsFunction = (): Array<
|
|
87
|
+
CustomizableColumn<SecurityEvent>
|
|
88
|
+
> => {
|
|
89
|
+
return [
|
|
90
|
+
makeEntry({ id: "time", title: "Time", isVisible: true }),
|
|
91
|
+
makeEntry({ id: "severityName", title: "Severity", isVisible: true }),
|
|
92
|
+
makeEntry({ id: "eventUid", title: "Event UID", isVisible: false }),
|
|
93
|
+
];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* Real generated attribute columns rather than placeholders: the ids the
|
|
98
|
+
* layout is keyed on come out of that generator, so a pool of fake entries
|
|
99
|
+
* would not exercise the same identity the app uses.
|
|
100
|
+
*/
|
|
101
|
+
type MakePoolFunction = (
|
|
102
|
+
attributeKeys: Array<string>,
|
|
103
|
+
) => Array<CustomizableColumn<SecurityEvent>>;
|
|
104
|
+
|
|
105
|
+
const makePool: MakePoolFunction = (
|
|
106
|
+
attributeKeys: Array<string>,
|
|
107
|
+
): Array<CustomizableColumn<SecurityEvent>> => {
|
|
108
|
+
const columns: Columns<SecurityEvent> = getAttributeColumns<SecurityEvent>({
|
|
109
|
+
attributesColumnKey: "attributes",
|
|
110
|
+
attributeKeys: attributeKeys,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return columns.map(
|
|
114
|
+
(column: Column<SecurityEvent>): CustomizableColumn<SecurityEvent> => {
|
|
115
|
+
return {
|
|
116
|
+
id: column.id as string,
|
|
117
|
+
column: column,
|
|
118
|
+
isVisible: true,
|
|
119
|
+
isPinned: false,
|
|
120
|
+
isRemovable: true,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const POOL_KEYS: Array<string> = [
|
|
127
|
+
"activity_name",
|
|
128
|
+
"class_uid",
|
|
129
|
+
"device.hostname",
|
|
130
|
+
"finding_info.title",
|
|
131
|
+
"metadata.product.name",
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
interface RenderedModal {
|
|
135
|
+
view: RenderResult;
|
|
136
|
+
onSave: MockFunction;
|
|
137
|
+
onClose: MockFunction;
|
|
138
|
+
onReset: MockFunction;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type RenderModalFunction = (data?: {
|
|
142
|
+
columns?: Array<CustomizableColumn<SecurityEvent>> | undefined;
|
|
143
|
+
addableColumns?: AddableColumnsConfig<SecurityEvent> | undefined | null;
|
|
144
|
+
}) => RenderedModal;
|
|
145
|
+
|
|
146
|
+
const renderModal: RenderModalFunction = (data?: {
|
|
147
|
+
columns?: Array<CustomizableColumn<SecurityEvent>> | undefined;
|
|
148
|
+
addableColumns?: AddableColumnsConfig<SecurityEvent> | undefined | null;
|
|
149
|
+
}): RenderedModal => {
|
|
150
|
+
const onSave: MockFunction = getJestMockFunction();
|
|
151
|
+
const onClose: MockFunction = getJestMockFunction();
|
|
152
|
+
const onReset: MockFunction = getJestMockFunction();
|
|
153
|
+
|
|
154
|
+
const addableColumns: AddableColumnsConfig<SecurityEvent> | undefined =
|
|
155
|
+
data?.addableColumns === null
|
|
156
|
+
? undefined
|
|
157
|
+
: data?.addableColumns || {
|
|
158
|
+
title: "Add Attribute Column",
|
|
159
|
+
description: "Attributes differ per event.",
|
|
160
|
+
placeholder: "Search attributes...",
|
|
161
|
+
columns: makePool(POOL_KEYS),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const view: RenderResult = render(
|
|
165
|
+
<ColumnCustomizationModal<SecurityEvent>
|
|
166
|
+
columns={data?.columns || getDeclaredColumns()}
|
|
167
|
+
addableColumns={addableColumns}
|
|
168
|
+
onSave={onSave}
|
|
169
|
+
onClose={onClose}
|
|
170
|
+
onReset={onReset}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return { view, onSave, onClose, onReset };
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
type GetSavedIdsFunction = (onSave: MockFunction) => Array<string>;
|
|
178
|
+
|
|
179
|
+
const getSavedIds: GetSavedIdsFunction = (
|
|
180
|
+
onSave: MockFunction,
|
|
181
|
+
): Array<string> => {
|
|
182
|
+
return (
|
|
183
|
+
onSave.mock.calls[0]![0] as Array<CustomizableColumn<SecurityEvent>>
|
|
184
|
+
).map((entry: CustomizableColumn<SecurityEvent>) => {
|
|
185
|
+
return entry.id;
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
type TypeAddSearchFunction = (view: RenderResult, text: string) => void;
|
|
190
|
+
|
|
191
|
+
const typeAddSearch: TypeAddSearchFunction = (
|
|
192
|
+
view: RenderResult,
|
|
193
|
+
text: string,
|
|
194
|
+
): void => {
|
|
195
|
+
fireEvent.change(view.getByTestId("add-column-search"), {
|
|
196
|
+
target: { value: text },
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
describe("ColumnCustomizationModal - addable columns", () => {
|
|
201
|
+
beforeEach(() => {
|
|
202
|
+
window.localStorage.clear();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
afterEach(() => {
|
|
206
|
+
cleanup();
|
|
207
|
+
jest.restoreAllMocks();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("the section is absent entirely when no pool is configured", () => {
|
|
211
|
+
const { view }: RenderedModal = renderModal({ addableColumns: null });
|
|
212
|
+
|
|
213
|
+
expect(view.queryByTestId("add-column-section")).not.toBeInTheDocument();
|
|
214
|
+
expect(view.queryByTestId("add-column-search")).not.toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("renders its own title, description and search box", () => {
|
|
218
|
+
const { view }: RenderedModal = renderModal();
|
|
219
|
+
|
|
220
|
+
expect(view.getByTestId("add-column-section")).toBeInTheDocument();
|
|
221
|
+
expect(view.getByText("Add Attribute Column")).toBeInTheDocument();
|
|
222
|
+
expect(view.getByText("Attributes differ per event.")).toBeInTheDocument();
|
|
223
|
+
expect(view.getByTestId("add-column-search")).toHaveAttribute(
|
|
224
|
+
"placeholder",
|
|
225
|
+
"Search attributes...",
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("offers every key in the pool", () => {
|
|
230
|
+
const { view }: RenderedModal = renderModal();
|
|
231
|
+
|
|
232
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(
|
|
233
|
+
POOL_KEYS.length,
|
|
234
|
+
);
|
|
235
|
+
expect(view.getByText("device.hostname")).toBeInTheDocument();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/*
|
|
239
|
+
* The pool is offered against the column TITLE, which for an attribute
|
|
240
|
+
* column is the raw key - so searching for the key someone read in the
|
|
241
|
+
* detail panel is what finds it.
|
|
242
|
+
*/
|
|
243
|
+
test("searching narrows the pool by key", () => {
|
|
244
|
+
const { view }: RenderedModal = renderModal();
|
|
245
|
+
|
|
246
|
+
typeAddSearch(view, "device");
|
|
247
|
+
|
|
248
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(1);
|
|
249
|
+
expect(
|
|
250
|
+
view.getByTestId("add-column-attributes.device.hostname"),
|
|
251
|
+
).toBeInTheDocument();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("the search is case-insensitive and matches anywhere in the key", () => {
|
|
255
|
+
const { view }: RenderedModal = renderModal();
|
|
256
|
+
|
|
257
|
+
typeAddSearch(view, "PRODUCT");
|
|
258
|
+
|
|
259
|
+
expect(
|
|
260
|
+
view.getByTestId("add-column-attributes.metadata.product.name"),
|
|
261
|
+
).toBeInTheDocument();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("surrounding whitespace in the search is ignored", () => {
|
|
265
|
+
const { view }: RenderedModal = renderModal();
|
|
266
|
+
|
|
267
|
+
typeAddSearch(view, " class_uid ");
|
|
268
|
+
|
|
269
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(1);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("a search that matches nothing says so", () => {
|
|
273
|
+
const { view }: RenderedModal = renderModal();
|
|
274
|
+
|
|
275
|
+
typeAddSearch(view, "no-such-key");
|
|
276
|
+
|
|
277
|
+
expect(view.getByTestId("add-column-no-results")).toHaveTextContent(
|
|
278
|
+
'No matches for "no-such-key"',
|
|
279
|
+
);
|
|
280
|
+
expect(view.queryAllByTestId(/^add-column-attributes\./)).toHaveLength(0);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("the search can be cleared, which brings the whole pool back", () => {
|
|
284
|
+
const { view }: RenderedModal = renderModal();
|
|
285
|
+
|
|
286
|
+
typeAddSearch(view, "device");
|
|
287
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(1);
|
|
288
|
+
|
|
289
|
+
fireEvent.click(view.getByTestId("add-column-search-clear"));
|
|
290
|
+
|
|
291
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(
|
|
292
|
+
POOL_KEYS.length,
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("adding puts the column in the list, visible, at the end", () => {
|
|
297
|
+
const { view }: RenderedModal = renderModal();
|
|
298
|
+
|
|
299
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
300
|
+
|
|
301
|
+
expect(
|
|
302
|
+
view.getByTestId("column-toggle-attributes.device.hostname"),
|
|
303
|
+
).toBeChecked();
|
|
304
|
+
|
|
305
|
+
expect(view.getByTestId("column-customization-count")).toHaveTextContent(
|
|
306
|
+
"3 of 4 columns shown",
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("an added column is no longer offered in the pool", () => {
|
|
311
|
+
const { view }: RenderedModal = renderModal();
|
|
312
|
+
|
|
313
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
314
|
+
|
|
315
|
+
expect(
|
|
316
|
+
view.queryByTestId("add-column-attributes.device.hostname"),
|
|
317
|
+
).not.toBeInTheDocument();
|
|
318
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(
|
|
319
|
+
POOL_KEYS.length - 1,
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("adding stages: nothing is handed back before Save", () => {
|
|
324
|
+
const { view, onSave }: RenderedModal = renderModal();
|
|
325
|
+
|
|
326
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
327
|
+
|
|
328
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("Save hands back the added column alongside the declared ones", () => {
|
|
332
|
+
const { view, onSave }: RenderedModal = renderModal();
|
|
333
|
+
|
|
334
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
335
|
+
fireEvent.click(view.getByTestId("add-column-attributes.class_uid"));
|
|
336
|
+
fireEvent.click(view.getByTestId("modal-footer-submit-button"));
|
|
337
|
+
|
|
338
|
+
expect(getSavedIds(onSave)).toEqual([
|
|
339
|
+
"time",
|
|
340
|
+
"severityName",
|
|
341
|
+
"eventUid",
|
|
342
|
+
"attributes.device.hostname",
|
|
343
|
+
"attributes.class_uid",
|
|
344
|
+
]);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("Cancel discards an added column", () => {
|
|
348
|
+
const { view, onSave, onClose }: RenderedModal = renderModal();
|
|
349
|
+
|
|
350
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
351
|
+
fireEvent.click(view.getByTestId("modal-footer-close-button"));
|
|
352
|
+
|
|
353
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
354
|
+
expect(onClose).toHaveBeenCalled();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("an added column can be reordered like any other", () => {
|
|
358
|
+
const { view, onSave }: RenderedModal = renderModal();
|
|
359
|
+
|
|
360
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
361
|
+
fireEvent.click(
|
|
362
|
+
view.getByTestId("column-move-up-attributes.device.hostname"),
|
|
363
|
+
);
|
|
364
|
+
fireEvent.click(view.getByTestId("modal-footer-submit-button"));
|
|
365
|
+
|
|
366
|
+
expect(getSavedIds(onSave)).toEqual([
|
|
367
|
+
"time",
|
|
368
|
+
"severityName",
|
|
369
|
+
"attributes.device.hostname",
|
|
370
|
+
"eventUid",
|
|
371
|
+
]);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("an added column can be switched off without being removed", () => {
|
|
375
|
+
const { view, onSave }: RenderedModal = renderModal();
|
|
376
|
+
|
|
377
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
378
|
+
fireEvent.click(
|
|
379
|
+
view.getByTestId("column-toggle-attributes.device.hostname"),
|
|
380
|
+
);
|
|
381
|
+
fireEvent.click(view.getByTestId("modal-footer-submit-button"));
|
|
382
|
+
|
|
383
|
+
const saved: Array<CustomizableColumn<SecurityEvent>> = onSave.mock
|
|
384
|
+
.calls[0]![0] as Array<CustomizableColumn<SecurityEvent>>;
|
|
385
|
+
|
|
386
|
+
const added: CustomizableColumn<SecurityEvent> | undefined = saved.find(
|
|
387
|
+
(entry: CustomizableColumn<SecurityEvent>) => {
|
|
388
|
+
return entry.id === "attributes.device.hostname";
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
expect(added).toBeDefined();
|
|
393
|
+
expect(added!.isVisible).toBe(false);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("the same pool entry cannot be added twice", () => {
|
|
397
|
+
/*
|
|
398
|
+
* Not reachable through the UI (an added column leaves the pool), but the
|
|
399
|
+
* guard is what keeps two columns with one id out of the stored layout if
|
|
400
|
+
* a double click ever races the re-render.
|
|
401
|
+
*/
|
|
402
|
+
const pool: Array<CustomizableColumn<SecurityEvent>> = makePool([
|
|
403
|
+
"device.hostname",
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
const { view, onSave }: RenderedModal = renderModal({
|
|
407
|
+
columns: [
|
|
408
|
+
...getDeclaredColumns(),
|
|
409
|
+
{ ...(pool[0] as CustomizableColumn<SecurityEvent>) },
|
|
410
|
+
],
|
|
411
|
+
addableColumns: {
|
|
412
|
+
title: "Add Attribute Column",
|
|
413
|
+
columns: pool,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Already staged, so the pool has nothing left to offer.
|
|
418
|
+
expect(view.getByTestId("add-column-empty")).toBeInTheDocument();
|
|
419
|
+
|
|
420
|
+
fireEvent.click(view.getByTestId("modal-footer-submit-button"));
|
|
421
|
+
|
|
422
|
+
expect(
|
|
423
|
+
getSavedIds(onSave).filter((id: string) => {
|
|
424
|
+
return id === "attributes.device.hostname";
|
|
425
|
+
}),
|
|
426
|
+
).toHaveLength(1);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
describe("ColumnCustomizationModal - removing a column", () => {
|
|
431
|
+
beforeEach(() => {
|
|
432
|
+
window.localStorage.clear();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
afterEach(() => {
|
|
436
|
+
cleanup();
|
|
437
|
+
jest.restoreAllMocks();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("only removable columns get a remove button", () => {
|
|
441
|
+
const { view }: RenderedModal = renderModal();
|
|
442
|
+
|
|
443
|
+
expect(view.queryByTestId("column-remove-time")).not.toBeInTheDocument();
|
|
444
|
+
expect(
|
|
445
|
+
view.queryByTestId("column-remove-severityName"),
|
|
446
|
+
).not.toBeInTheDocument();
|
|
447
|
+
|
|
448
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
449
|
+
|
|
450
|
+
expect(
|
|
451
|
+
view.getByTestId("column-remove-attributes.device.hostname"),
|
|
452
|
+
).toBeInTheDocument();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("removing takes the column out of the list", () => {
|
|
456
|
+
const { view }: RenderedModal = renderModal();
|
|
457
|
+
|
|
458
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
459
|
+
fireEvent.click(
|
|
460
|
+
view.getByTestId("column-remove-attributes.device.hostname"),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
expect(
|
|
464
|
+
view.queryByTestId("column-toggle-attributes.device.hostname"),
|
|
465
|
+
).not.toBeInTheDocument();
|
|
466
|
+
expect(view.getByTestId("column-customization-count")).toHaveTextContent(
|
|
467
|
+
"2 of 3 columns shown",
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("a removed column goes back into the pool", () => {
|
|
472
|
+
const { view }: RenderedModal = renderModal();
|
|
473
|
+
|
|
474
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
475
|
+
fireEvent.click(
|
|
476
|
+
view.getByTestId("column-remove-attributes.device.hostname"),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
expect(
|
|
480
|
+
view.getByTestId("add-column-attributes.device.hostname"),
|
|
481
|
+
).toBeInTheDocument();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("Save without the removed column is what drops it for good", () => {
|
|
485
|
+
const { view, onSave }: RenderedModal = renderModal({
|
|
486
|
+
columns: [
|
|
487
|
+
...getDeclaredColumns(),
|
|
488
|
+
...makePool(["device.hostname"]).map(
|
|
489
|
+
(entry: CustomizableColumn<SecurityEvent>) => {
|
|
490
|
+
return { ...entry, isVisible: true };
|
|
491
|
+
},
|
|
492
|
+
),
|
|
493
|
+
],
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
fireEvent.click(
|
|
497
|
+
view.getByTestId("column-remove-attributes.device.hostname"),
|
|
498
|
+
);
|
|
499
|
+
fireEvent.click(view.getByTestId("modal-footer-submit-button"));
|
|
500
|
+
|
|
501
|
+
expect(getSavedIds(onSave)).toEqual(["time", "severityName", "eventUid"]);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
/*
|
|
505
|
+
* Removing is hiding plus forgetting, so it inherits the rule that keeps a
|
|
506
|
+
* table from becoming an empty shell with no header row and no way back.
|
|
507
|
+
*/
|
|
508
|
+
test("the last visible column cannot be removed", () => {
|
|
509
|
+
const only: Array<CustomizableColumn<SecurityEvent>> = makePool([
|
|
510
|
+
"device.hostname",
|
|
511
|
+
]);
|
|
512
|
+
|
|
513
|
+
const { view }: RenderedModal = renderModal({
|
|
514
|
+
columns: only,
|
|
515
|
+
addableColumns: { title: "Add Attribute Column", columns: [] },
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const removeButton: HTMLElement = view.getByTestId(
|
|
519
|
+
"column-remove-attributes.device.hostname",
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
expect(removeButton).toBeDisabled();
|
|
523
|
+
|
|
524
|
+
fireEvent.click(removeButton);
|
|
525
|
+
|
|
526
|
+
expect(
|
|
527
|
+
view.getByTestId("column-toggle-attributes.device.hostname"),
|
|
528
|
+
).toBeInTheDocument();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("a hidden removable column can still be removed while another is on", () => {
|
|
532
|
+
const { view, onSave }: RenderedModal = renderModal();
|
|
533
|
+
|
|
534
|
+
fireEvent.click(view.getByTestId("add-column-attributes.device.hostname"));
|
|
535
|
+
fireEvent.click(
|
|
536
|
+
view.getByTestId("column-toggle-attributes.device.hostname"),
|
|
537
|
+
);
|
|
538
|
+
fireEvent.click(
|
|
539
|
+
view.getByTestId("column-remove-attributes.device.hostname"),
|
|
540
|
+
);
|
|
541
|
+
fireEvent.click(view.getByTestId("modal-footer-submit-button"));
|
|
542
|
+
|
|
543
|
+
expect(getSavedIds(onSave)).toEqual(["time", "severityName", "eventUid"]);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe("ColumnCustomizationModal - pool states", () => {
|
|
548
|
+
afterEach(() => {
|
|
549
|
+
cleanup();
|
|
550
|
+
jest.restoreAllMocks();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("says it is loading rather than reading as empty", () => {
|
|
554
|
+
const { view }: RenderedModal = renderModal({
|
|
555
|
+
addableColumns: {
|
|
556
|
+
title: "Add Attribute Column",
|
|
557
|
+
isLoading: true,
|
|
558
|
+
columns: [],
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
expect(view.getByTestId("add-column-loading")).toBeInTheDocument();
|
|
563
|
+
expect(view.queryByTestId("add-column-empty")).not.toBeInTheDocument();
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test("surfaces a failure instead of pretending there is nothing to add", () => {
|
|
567
|
+
const { view }: RenderedModal = renderModal({
|
|
568
|
+
addableColumns: {
|
|
569
|
+
title: "Add Attribute Column",
|
|
570
|
+
errorMessage: "Could not load attributes.",
|
|
571
|
+
columns: [],
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
expect(view.getByTestId("add-column-error")).toHaveTextContent(
|
|
576
|
+
"Could not load attributes.",
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/*
|
|
581
|
+
* An error takes precedence over the loading state: a retry that fails
|
|
582
|
+
* again must not flash back to "Loading..." and hide why.
|
|
583
|
+
*/
|
|
584
|
+
test("an error wins over a concurrent loading flag", () => {
|
|
585
|
+
const { view }: RenderedModal = renderModal({
|
|
586
|
+
addableColumns: {
|
|
587
|
+
title: "Add Attribute Column",
|
|
588
|
+
isLoading: true,
|
|
589
|
+
errorMessage: "Could not load attributes.",
|
|
590
|
+
columns: [],
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
expect(view.getByTestId("add-column-error")).toBeInTheDocument();
|
|
595
|
+
expect(view.queryByTestId("add-column-loading")).not.toBeInTheDocument();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("an empty pool shows the caller's message", () => {
|
|
599
|
+
const { view }: RenderedModal = renderModal({
|
|
600
|
+
addableColumns: {
|
|
601
|
+
title: "Add Attribute Column",
|
|
602
|
+
emptyMessage: "No attributes seen on recent events.",
|
|
603
|
+
columns: [],
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
expect(view.getByTestId("add-column-empty")).toHaveTextContent(
|
|
608
|
+
"No attributes seen on recent events.",
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
test("a pool whose entries are all already added says something different", () => {
|
|
613
|
+
const pool: Array<CustomizableColumn<SecurityEvent>> = makePool([
|
|
614
|
+
"device.hostname",
|
|
615
|
+
]);
|
|
616
|
+
|
|
617
|
+
const { view }: RenderedModal = renderModal({
|
|
618
|
+
columns: [...getDeclaredColumns(), ...pool],
|
|
619
|
+
addableColumns: {
|
|
620
|
+
title: "Add Attribute Column",
|
|
621
|
+
emptyMessage: "No attributes seen on recent events.",
|
|
622
|
+
columns: pool,
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
expect(view.getByTestId("add-column-empty")).toHaveTextContent(
|
|
627
|
+
"already in the list above",
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
/*
|
|
632
|
+
* A project can carry thousands of attribute keys. Rendering all of them
|
|
633
|
+
* would put the whole list in the DOM and make the picker crawl, so the
|
|
634
|
+
* list is capped and the cap is said out loud - otherwise someone whose key
|
|
635
|
+
* is not shown has no reason to believe typing more would find it.
|
|
636
|
+
*/
|
|
637
|
+
test("caps how many matches are rendered at once", () => {
|
|
638
|
+
const keys: Array<string> = Array.from(
|
|
639
|
+
{ length: 40 },
|
|
640
|
+
(_unused: unknown, index: number) => {
|
|
641
|
+
return `attr.key${String(index).padStart(2, "0")}`;
|
|
642
|
+
},
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
const { view }: RenderedModal = renderModal({
|
|
646
|
+
addableColumns: {
|
|
647
|
+
title: "Add Attribute Column",
|
|
648
|
+
columns: makePool(keys),
|
|
649
|
+
maxResults: 10,
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(10);
|
|
654
|
+
expect(view.getByTestId("add-column-hint")).toHaveTextContent(
|
|
655
|
+
"Showing 10 of 40",
|
|
656
|
+
);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test("the cap notice disappears once the search fits inside it", () => {
|
|
660
|
+
const keys: Array<string> = Array.from(
|
|
661
|
+
{ length: 40 },
|
|
662
|
+
(_unused: unknown, index: number) => {
|
|
663
|
+
return `attr.key${String(index).padStart(2, "0")}`;
|
|
664
|
+
},
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const { view }: RenderedModal = renderModal({
|
|
668
|
+
addableColumns: {
|
|
669
|
+
title: "Add Attribute Column",
|
|
670
|
+
columns: makePool(keys),
|
|
671
|
+
maxResults: 10,
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
typeAddSearch(view, "key07");
|
|
676
|
+
|
|
677
|
+
expect(view.getAllByTestId(/^add-column-attributes\./)).toHaveLength(1);
|
|
678
|
+
expect(view.queryByTestId("add-column-hint")).not.toBeInTheDocument();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("a key beyond the cap is still reachable by searching for it", () => {
|
|
682
|
+
const keys: Array<string> = Array.from(
|
|
683
|
+
{ length: 40 },
|
|
684
|
+
(_unused: unknown, index: number) => {
|
|
685
|
+
return `attr.key${String(index).padStart(2, "0")}`;
|
|
686
|
+
},
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
const { view, onSave }: RenderedModal = renderModal({
|
|
690
|
+
addableColumns: {
|
|
691
|
+
title: "Add Attribute Column",
|
|
692
|
+
columns: makePool(keys),
|
|
693
|
+
maxResults: 10,
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
expect(
|
|
698
|
+
view.queryByTestId("add-column-attributes.attr.key39"),
|
|
699
|
+
).not.toBeInTheDocument();
|
|
700
|
+
|
|
701
|
+
typeAddSearch(view, "key39");
|
|
702
|
+
fireEvent.click(view.getByTestId("add-column-attributes.attr.key39"));
|
|
703
|
+
fireEvent.click(view.getByTestId("modal-footer-submit-button"));
|
|
704
|
+
|
|
705
|
+
expect(getSavedIds(onSave)).toContain("attributes.attr.key39");
|
|
706
|
+
});
|
|
707
|
+
});
|
|
@@ -46,7 +46,7 @@ const PAGE_CHROME_Z_INDEXES: Array<number> = [
|
|
|
46
46
|
10 /* MasterPage sticky header */, 20 /* topology + logs viewer toolbars */,
|
|
47
47
|
];
|
|
48
48
|
const OVERLAY_Z_INDEXES_ABOVE_SIDE_OVER: Array<number> = [
|
|
49
|
-
40 /*
|
|
49
|
+
40 /* AIChatPanel */, 50 /* Modal */, 60 /* portalled dropdowns */,
|
|
50
50
|
];
|
|
51
51
|
|
|
52
52
|
type ZIndexOfFunction = (element: HTMLElement) => number | null;
|