@oneuptime/common 12.0.23 → 12.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/Span.ts +101 -0
- package/Models/DatabaseModels/DetectionRule.ts +55 -0
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
- package/Models/DatabaseModels/Project.ts +68 -0
- package/Models/DatabaseModels/User.ts +68 -0
- package/Server/API/BaseAPI.ts +0 -24
- package/Server/API/SlackAPI.ts +0 -2
- package/Server/API/TelemetryAPI.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/GzipRequestBody.ts +311 -0
- package/Server/Middleware/MultipartFormData.ts +87 -3
- package/Server/Middleware/SlackAuthorization.ts +96 -18
- package/Server/Services/DetectionRuleService.ts +73 -6
- package/Server/Services/Index.ts +205 -0
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
- package/Server/Services/NetworkDeviceService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
- package/Server/Services/ProjectService.ts +29 -19
- package/Server/Services/SecurityEventService.ts +90 -13
- package/Server/Services/StatusPageResourceService.ts +223 -0
- package/Server/Services/TelemetryAttributeService.ts +17 -0
- package/Server/Services/UserService.ts +4 -6
- package/Server/Types/Markdown.ts +161 -67
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
- package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
- package/Server/Utils/Attribution.ts +4 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
- package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
- package/Server/Utils/StartServer.ts +12 -32
- package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
- package/Server/Utils/Telemetry/LlmMetricSpend.ts +181 -0
- package/Server/Utils/Telemetry/LlmSpan.ts +46 -0
- package/Server/Utils/Workspace/Slack/Actions/Auth.ts +0 -12
- package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
- package/Server/Views/Partials/Head.ejs +2 -1
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
- package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
- package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
- package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
- package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
- package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
- package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
- package/Tests/App/Dashboard/LlmCallsTableIdentity.test.tsx +322 -0
- package/Tests/App/Dashboard/LlmOverview.test.tsx +335 -0
- package/Tests/App/Dashboard/LlmSpanDisplay.test.ts +391 -0
- package/Tests/App/Dashboard/LlmUsageBreakdown.test.tsx +1007 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
- package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
- package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
- package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
- package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
- package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
- package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
- package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
- package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
- package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
- package/Tests/Server/API/BaseAPI.test.ts +41 -0
- package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +9 -16
- package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
- package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
- package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +262 -5
- package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
- package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
- package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
- package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
- package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
- package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
- package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
- package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
- package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
- package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +387 -4
- package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +386 -0
- package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +804 -0
- package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
- package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
- package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
- package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
- package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +584 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
- package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
- package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
- package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
- package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
- package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
- package/Tests/UI/Components/SideOver.test.tsx +1 -1
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
- package/Tests/UI/Components/Toggle.test.tsx +25 -0
- package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
- package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
- package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +770 -0
- package/Types/Code/YamlSyntax.ts +396 -0
- package/Types/Exception/ExceptionCode.ts +1 -0
- package/Types/Exception/PayloadTooLargeException.ts +8 -0
- package/Types/Marketing/Attribution.ts +10 -0
- package/Types/Marketing/MarketingEvent.ts +4 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
- package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
- package/Types/Telemetry/LlmConventions.ts +255 -0
- package/Types/Telemetry/LlmMetricConventions.ts +329 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
- package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
- package/UI/Components/Detail/Detail.tsx +10 -0
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
- package/UI/Components/Forms/Fields/FormField.tsx +31 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
- package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
- package/UI/Components/Forms/Validation.ts +46 -0
- package/UI/Components/Input/Input.tsx +3 -0
- package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
- package/UI/Components/ModelTable/Column.ts +8 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
- package/UI/Components/SideOver/SideOver.tsx +5 -5
- package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
- package/UI/Components/StatusPage/ResourceList.tsx +197 -38
- package/UI/Components/Toggle/Toggle.tsx +9 -1
- package/UI/Components/Types/FieldType.ts +1 -0
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
- package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
- package/Utils/StatusPage/ResourceExplorer.ts +258 -0
- package/Utils/Telemetry/LlmMetricQuery.ts +287 -0
- package/build/dist/Models/AnalyticsModels/Span.js +89 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +76 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +76 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +3 -19
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +0 -2
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +17 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
- package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
- package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
- package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +58 -7
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DetectionRuleService.js +49 -4
- package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +204 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +25 -45
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventService.js +57 -5
- package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
- package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +2 -12
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +147 -61
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -21
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +151 -0
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js +24 -1
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +0 -10
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
- package/build/dist/Types/Code/YamlSyntax.js +212 -0
- package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
- package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
- package/build/dist/Types/Marketing/Attribution.js +10 -0
- package/build/dist/Types/Marketing/Attribution.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmConventions.js +236 -0
- package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmMetricConventions.js +290 -0
- package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +9 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +27 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
- package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Types/FieldType.js +1 -0
- package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js +175 -0
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +2 -1
- package/Tests/UI/Components/Toast.test.tsx +0 -197
- package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
- package/UI/Components/Toast/Toast.tsx +0 -189
- package/UI/Components/Toast/ToastInit.tsx +0 -110
- package/build/dist/UI/Components/Toast/Toast.js +0 -123
- package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
- package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
|
@@ -0,0 +1,1122 @@
|
|
|
1
|
+
import CriteriaFilterUtil from "../../../../App/FeatureSet/Dashboard/src/Utils/Form/Monitor/CriteriaFilter";
|
|
2
|
+
import MonitorCriteriaAlignmentUtil, {
|
|
3
|
+
CriteriaSeedOptions,
|
|
4
|
+
MonitorStepsAlignmentResult,
|
|
5
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Utils/Form/Monitor/MonitorCriteriaAlignment";
|
|
6
|
+
import URL from "../../../Types/API/URL";
|
|
7
|
+
import FilterCondition from "../../../Types/Filter/FilterCondition";
|
|
8
|
+
import { CriteriaIncident } from "../../../Types/Monitor/CriteriaIncident";
|
|
9
|
+
import {
|
|
10
|
+
CheckOn,
|
|
11
|
+
CriteriaFilter,
|
|
12
|
+
FilterType,
|
|
13
|
+
} from "../../../Types/Monitor/CriteriaFilter";
|
|
14
|
+
import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
|
|
15
|
+
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
16
|
+
import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
|
|
17
|
+
import MonitorType, {
|
|
18
|
+
MonitorTypeHelper,
|
|
19
|
+
} from "../../../Types/Monitor/MonitorType";
|
|
20
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
21
|
+
import { DropdownOption } from "../../../UI/Components/Dropdown/Dropdown";
|
|
22
|
+
import { describe, expect, test } from "@jest/globals";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Monitor type and monitor criteria are on different steps of the monitor
|
|
26
|
+
* create form, and the criteria step's fields unmount while the user is on
|
|
27
|
+
* another step. So a user can seed criteria for one monitor type and then
|
|
28
|
+
* change their mind: visit the criteria step, walk back, pick a different
|
|
29
|
+
* type, come forward again. The criteria that come back with them were
|
|
30
|
+
* written against the type they abandoned.
|
|
31
|
+
*
|
|
32
|
+
* Left alone, those criteria name checks the new monitor type does not
|
|
33
|
+
* offer - "Is Online" on an External Status Page monitor, say - and the
|
|
34
|
+
* criteria form's dropdowns fall back to react-select's empty "Select..."
|
|
35
|
+
* placeholder because they cannot find an option matching what is stored.
|
|
36
|
+
* The rule is not empty; the form cannot draw it. Saved as-is it also never
|
|
37
|
+
* fires, because the server's comparators switch on a filter type they no
|
|
38
|
+
* longer recognise.
|
|
39
|
+
*
|
|
40
|
+
* This is the same class of defect as #3412 (the Filter Condition dropdown
|
|
41
|
+
* on the seeded External Status Page criteria, pinned in
|
|
42
|
+
* CriteriaFilterDefaults.test.ts), arriving by a different route: there the
|
|
43
|
+
* seed itself was unrenderable, here a renderable seed is outlived by the
|
|
44
|
+
* monitor type it was written for.
|
|
45
|
+
*
|
|
46
|
+
* The fix has two halves, and this file pins both:
|
|
47
|
+
*
|
|
48
|
+
* 1. Criteria still identical to the defaults of the type they were
|
|
49
|
+
* seeded for carry nothing of the user's, so they are re-seeded
|
|
50
|
+
* outright for the new type - the user gets what picking that type
|
|
51
|
+
* first would have given them.
|
|
52
|
+
* 2. Criteria the user has edited are repaired filter by filter instead.
|
|
53
|
+
* Their names, incidents, alerts and every filter that still applies
|
|
54
|
+
* to the new type survive; filters the new type cannot express are
|
|
55
|
+
* dropped, never swapped for a fabricated one - see the inversion
|
|
56
|
+
* test below for why that distinction is the important one.
|
|
57
|
+
*
|
|
58
|
+
* And over both halves, the invariant that matters: whatever monitor type
|
|
59
|
+
* the criteria were seeded for and whichever one the user lands on, every
|
|
60
|
+
* filter that reaches the form is renderable.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
const SEED_OPTIONS: CriteriaSeedOptions = {
|
|
64
|
+
monitorName: "Acme",
|
|
65
|
+
onlineMonitorStatusId: new ObjectID("aaaaaaaaaaaaaaaaaaaaaaaa"),
|
|
66
|
+
offlineMonitorStatusId: new ObjectID("bbbbbbbbbbbbbbbbbbbbbbbb"),
|
|
67
|
+
defaultIncidentSeverityId: new ObjectID("cccccccccccccccccccccccc"),
|
|
68
|
+
defaultAlertSeverityId: new ObjectID("dddddddddddddddddddddddd"),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const ALL_MONITOR_TYPES: Array<MonitorType> = Object.values(
|
|
72
|
+
MonitorType,
|
|
73
|
+
) as Array<MonitorType>;
|
|
74
|
+
|
|
75
|
+
function optionValues(options: Array<DropdownOption>): Array<string> {
|
|
76
|
+
return options.map((option: DropdownOption) => {
|
|
77
|
+
return option.value.toString();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function checkOnOptionsFor(monitorType: MonitorType): Array<string> {
|
|
82
|
+
return optionValues(
|
|
83
|
+
CriteriaFilterUtil.getCheckOnOptionsByMonitorType(monitorType),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function filterTypeOptionsFor(checkOn: CheckOn): Array<string> {
|
|
88
|
+
return optionValues(
|
|
89
|
+
CriteriaFilterUtil.getFilterTypeOptionsByCheckOn(checkOn),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The monitor steps the criteria form seeds when a monitor type is picked.
|
|
94
|
+
function stepsSeededFor(monitorType: MonitorType): MonitorSteps {
|
|
95
|
+
return MonitorSteps.getDefaultMonitorSteps({
|
|
96
|
+
monitorType: monitorType,
|
|
97
|
+
monitorName: SEED_OPTIONS.monitorName,
|
|
98
|
+
defaultMonitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
|
|
99
|
+
onlineMonitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
|
|
100
|
+
offlineMonitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
|
|
101
|
+
defaultIncidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
|
|
102
|
+
defaultAlertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
* Monitor types the criteria form is ever drawn for. Manual is not one:
|
|
108
|
+
* nothing polls a manual monitor, getCheckOnOptionsByMonitorType returns
|
|
109
|
+
* an empty list for it, and the Criteria page renders an empty state in
|
|
110
|
+
* place of the form - so "the criteria are renderable" is not a question
|
|
111
|
+
* that can be asked of it.
|
|
112
|
+
*/
|
|
113
|
+
const MONITOR_TYPES_WITH_CRITERIA: Array<MonitorType> =
|
|
114
|
+
ALL_MONITOR_TYPES.filter((monitorType: MonitorType) => {
|
|
115
|
+
return MonitorTypeHelper.doesMonitorTypeHaveCriteria(monitorType);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Monitor types that seed no real criteria. Only Manual: nothing polls a
|
|
120
|
+
* manual monitor, so getCheckOnOptionsByMonitorType returns an empty list
|
|
121
|
+
* for it and there is no check its criteria form could ever draw.
|
|
122
|
+
*
|
|
123
|
+
* The metric-backed family (Docker, Host, Podman, Docker Swarm, Proxmox,
|
|
124
|
+
* Ceph and IoT Device) and Profiles once belonged here too - they fell
|
|
125
|
+
* through getDefaultOfflineMonitorCriteriaInstance to the constructor's
|
|
126
|
+
* blank "Is Online" placeholder their own narrowed option lists did not
|
|
127
|
+
* offer. That gap was closed in "seed real criteria for the metric and
|
|
128
|
+
* profile monitor types": they now seed a real default on the one check
|
|
129
|
+
* their evaluator reads (MetricValue for the metric family, ProfileCount
|
|
130
|
+
* for Profiles), so they seed something the form can draw and have moved to
|
|
131
|
+
* the WITH list, swept as both sources and targets like every other type.
|
|
132
|
+
*
|
|
133
|
+
* This list pins the gap from both sides - the assertions below fail if a
|
|
134
|
+
* type here quietly grows a real default, and if a type not here quietly
|
|
135
|
+
* loses one.
|
|
136
|
+
*/
|
|
137
|
+
const MONITOR_TYPES_WITHOUT_SEEDED_CRITERIA: Array<MonitorType> = [
|
|
138
|
+
MonitorType.Manual,
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const MONITOR_TYPES_WITH_SEEDED_CRITERIA: Array<MonitorType> =
|
|
142
|
+
MONITOR_TYPES_WITH_CRITERIA.filter((monitorType: MonitorType) => {
|
|
143
|
+
return !MONITOR_TYPES_WITHOUT_SEEDED_CRITERIA.includes(monitorType);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* The types a sweep starts from, given the type it is aligning to.
|
|
148
|
+
* "Changing" to the type you are already on is the one case where nothing
|
|
149
|
+
* is meant to happen, and it is pinned separately by the no-churn tests.
|
|
150
|
+
*/
|
|
151
|
+
function sourceTypesFor(monitorType: MonitorType): Array<MonitorType> {
|
|
152
|
+
return MONITOR_TYPES_WITH_SEEDED_CRITERIA.filter((candidate: MonitorType) => {
|
|
153
|
+
return candidate !== monitorType;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function alignTo(
|
|
158
|
+
monitorSteps: MonitorSteps,
|
|
159
|
+
monitorType: MonitorType,
|
|
160
|
+
): MonitorStepsAlignmentResult {
|
|
161
|
+
return MonitorCriteriaAlignmentUtil.alignMonitorStepsWithMonitorType({
|
|
162
|
+
monitorSteps: monitorSteps,
|
|
163
|
+
monitorType: monitorType,
|
|
164
|
+
seedOptions: SEED_OPTIONS,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function criteriaOf(
|
|
169
|
+
monitorSteps: MonitorSteps,
|
|
170
|
+
): Array<MonitorCriteriaInstance> {
|
|
171
|
+
return (monitorSteps.data?.monitorStepsInstanceArray || []).flatMap(
|
|
172
|
+
(monitorStep: MonitorStep) => {
|
|
173
|
+
return (
|
|
174
|
+
monitorStep.data?.monitorCriteria.data?.monitorCriteriaInstanceArray ||
|
|
175
|
+
[]
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function filtersOf(monitorSteps: MonitorSteps): Array<CriteriaFilter> {
|
|
182
|
+
return criteriaOf(monitorSteps).flatMap(
|
|
183
|
+
(instance: MonitorCriteriaInstance) => {
|
|
184
|
+
return instance.data?.filters || [];
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function criteriaNamesOf(monitorSteps: MonitorSteps): Array<string> {
|
|
190
|
+
return criteriaOf(monitorSteps).map((instance: MonitorCriteriaInstance) => {
|
|
191
|
+
return instance.data!.name;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Every filter as "<check> <condition> <value>", so a failure reads.
|
|
196
|
+
function describeFilters(monitorSteps: MonitorSteps): Array<string> {
|
|
197
|
+
return filtersOf(monitorSteps).map((filter: CriteriaFilter) => {
|
|
198
|
+
return `${filter.checkOn} ${filter.filterType} ${filter.value}`;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/*
|
|
203
|
+
* Everything about `monitorSteps` the criteria form for `monitorType`
|
|
204
|
+
* cannot draw, as readable sentences - an empty array is the assertion,
|
|
205
|
+
* and anything else names what broke.
|
|
206
|
+
*/
|
|
207
|
+
function unrenderable(
|
|
208
|
+
monitorSteps: MonitorSteps,
|
|
209
|
+
monitorType: MonitorType,
|
|
210
|
+
): Array<string> {
|
|
211
|
+
const offeredChecks: Array<string> = checkOnOptionsFor(monitorType);
|
|
212
|
+
const problems: Array<string> = [];
|
|
213
|
+
|
|
214
|
+
for (const instance of criteriaOf(monitorSteps)) {
|
|
215
|
+
for (const filter of instance.data?.filters || []) {
|
|
216
|
+
if (!offeredChecks.includes(filter.checkOn?.toString())) {
|
|
217
|
+
problems.push(
|
|
218
|
+
`"${instance.data!.name}" filters on check "${filter.checkOn}", which ${monitorType} does not offer`,
|
|
219
|
+
);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!filter.filterType) {
|
|
224
|
+
problems.push(
|
|
225
|
+
`"${instance.data!.name}" filters on check "${filter.checkOn}" with no condition at all`,
|
|
226
|
+
);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const offeredConditions: Array<string> = filterTypeOptionsFor(
|
|
231
|
+
filter.checkOn,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (!offeredConditions.includes(filter.filterType.toString())) {
|
|
235
|
+
problems.push(
|
|
236
|
+
`"${instance.data!.name}" filters on check "${filter.checkOn}" with condition "${filter.filterType}", which is not one of [${offeredConditions.join(", ")}]`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return problems;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
describe("Criteria after the monitor type changes under them", () => {
|
|
246
|
+
describe("the reported defect", () => {
|
|
247
|
+
test("criteria seeded for a Website are unrenderable once the type is External Status Page", () => {
|
|
248
|
+
const seededForWebsite: MonitorSteps = stepsSeededFor(
|
|
249
|
+
MonitorType.Website,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
/*
|
|
253
|
+
* The bug, stated: a Website's criteria check "Is Online" and
|
|
254
|
+
* "Response Status Code", and an External Status Page monitor offers
|
|
255
|
+
* neither, so both of that filter's dropdowns render empty.
|
|
256
|
+
*/
|
|
257
|
+
expect(
|
|
258
|
+
unrenderable(seededForWebsite, MonitorType.ExternalStatusPage),
|
|
259
|
+
).not.toEqual([]);
|
|
260
|
+
|
|
261
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
262
|
+
seededForWebsite,
|
|
263
|
+
MonitorType.ExternalStatusPage,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(aligned.didChange).toBe(true);
|
|
267
|
+
expect(
|
|
268
|
+
unrenderable(aligned.monitorSteps, MonitorType.ExternalStatusPage),
|
|
269
|
+
).toEqual([]);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("the External Status Page criteria the user ends up with are the ones picking that type first would have given them", () => {
|
|
273
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
274
|
+
stepsSeededFor(MonitorType.Website),
|
|
275
|
+
MonitorType.ExternalStatusPage,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(describeFilters(aligned.monitorSteps)).toEqual(
|
|
279
|
+
describeFilters(stepsSeededFor(MonitorType.ExternalStatusPage)),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(criteriaNamesOf(aligned.monitorSteps)).toEqual(
|
|
283
|
+
criteriaNamesOf(stepsSeededFor(MonitorType.ExternalStatusPage)),
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("every monitor type change leaves criteria the form can draw", () => {
|
|
289
|
+
test.each(MONITOR_TYPES_WITH_CRITERIA)(
|
|
290
|
+
"criteria seeded for any other type are renderable once aligned to %s",
|
|
291
|
+
(monitorType: MonitorType) => {
|
|
292
|
+
const problems: Array<string> = [];
|
|
293
|
+
|
|
294
|
+
for (const seededFor of sourceTypesFor(monitorType)) {
|
|
295
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
296
|
+
stepsSeededFor(seededFor),
|
|
297
|
+
monitorType,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
for (const problem of unrenderable(
|
|
301
|
+
aligned.monitorSteps,
|
|
302
|
+
monitorType,
|
|
303
|
+
)) {
|
|
304
|
+
problems.push(`seeded for ${seededFor}: ${problem}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
expect(problems).toEqual([]);
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
test.each(MONITOR_TYPES_WITH_SEEDED_CRITERIA)(
|
|
313
|
+
"%s: criteria the user has edited are still renderable once aligned to every other type",
|
|
314
|
+
(seededFor: MonitorType) => {
|
|
315
|
+
/*
|
|
316
|
+
* An edited criteria takes the repair path rather than the
|
|
317
|
+
* re-seed path, so sweep that path over every pair too. The edit
|
|
318
|
+
* is deliberately one that survives - a renamed criteria - so what
|
|
319
|
+
* is under test is the filters, not whether the edit was noticed.
|
|
320
|
+
*/
|
|
321
|
+
const problems: Array<string> = [];
|
|
322
|
+
|
|
323
|
+
for (const monitorType of sourceTypesFor(seededFor)) {
|
|
324
|
+
const edited: MonitorSteps = stepsSeededFor(seededFor);
|
|
325
|
+
criteriaOf(edited)[0]!.setName("My own rule");
|
|
326
|
+
|
|
327
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
328
|
+
edited,
|
|
329
|
+
monitorType,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
for (const problem of unrenderable(
|
|
333
|
+
aligned.monitorSteps,
|
|
334
|
+
monitorType,
|
|
335
|
+
)) {
|
|
336
|
+
problems.push(`aligned to ${monitorType}: ${problem}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
expect(problems).toEqual([]);
|
|
341
|
+
},
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe("criteria that already suit the monitor type are left alone", () => {
|
|
346
|
+
test.each(ALL_MONITOR_TYPES)(
|
|
347
|
+
"%s: its own freshly seeded criteria come back untouched",
|
|
348
|
+
(monitorType: MonitorType) => {
|
|
349
|
+
const seeded: MonitorSteps = stepsSeededFor(monitorType);
|
|
350
|
+
|
|
351
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
352
|
+
seeded,
|
|
353
|
+
monitorType,
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
expect(aligned.didChange).toBe(false);
|
|
357
|
+
/*
|
|
358
|
+
* Same object, not just equal content. The criteria form writes
|
|
359
|
+
* the result back into the form on change, and re-seeding criteria
|
|
360
|
+
* that were already right would churn their generated ids and mark
|
|
361
|
+
* an untouched form dirty every time it mounted.
|
|
362
|
+
*/
|
|
363
|
+
expect(aligned.monitorSteps).toBe(seeded);
|
|
364
|
+
},
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
test("criteria carrying an edit that is still valid are not re-seeded", () => {
|
|
368
|
+
const edited: MonitorSteps = stepsSeededFor(MonitorType.Website);
|
|
369
|
+
criteriaOf(edited)[0]!.setName("Down for two checks in a row");
|
|
370
|
+
|
|
371
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
372
|
+
edited,
|
|
373
|
+
MonitorType.Website,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
expect(aligned.didChange).toBe(false);
|
|
377
|
+
expect(criteriaNamesOf(aligned.monitorSteps)).toContain(
|
|
378
|
+
"Down for two checks in a row",
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe("untouched defaults are re-seeded for the new monitor type", () => {
|
|
384
|
+
test("Website to Incoming Request swaps in the Incoming Request defaults wholesale", () => {
|
|
385
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
386
|
+
stepsSeededFor(MonitorType.Website),
|
|
387
|
+
MonitorType.IncomingRequest,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
/*
|
|
391
|
+
* Repairing rather than re-seeding here would have left the user
|
|
392
|
+
* with "Check if Acme is offline" over a blank "not received in
|
|
393
|
+
* minutes" threshold - the first check Incoming Request offers -
|
|
394
|
+
* which is not a rule anybody asked for. What they get instead is
|
|
395
|
+
* the pair of request-body rules picking Incoming Request first
|
|
396
|
+
* would have seeded.
|
|
397
|
+
*/
|
|
398
|
+
expect(describeFilters(aligned.monitorSteps)).toEqual([
|
|
399
|
+
`${CheckOn.RequestBody} ${FilterType.Contains} ${MonitorCriteriaInstance.DEFAULT_INCOMING_BODY_ERROR_KEYWORD}`,
|
|
400
|
+
`${CheckOn.RequestBody} ${FilterType.NotContains} ${MonitorCriteriaInstance.DEFAULT_INCOMING_BODY_ERROR_KEYWORD}`,
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
expect(describeFilters(aligned.monitorSteps)).toEqual(
|
|
404
|
+
describeFilters(stepsSeededFor(MonitorType.IncomingRequest)),
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("the incidents the new monitor type seeds come with it", () => {
|
|
409
|
+
/*
|
|
410
|
+
* Re-seeding has to bring the whole criteria across, not just the
|
|
411
|
+
* filters - a Logs monitor whose criteria change the monitor status
|
|
412
|
+
* but open no incident is not what picking Logs gives you.
|
|
413
|
+
*/
|
|
414
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
415
|
+
stepsSeededFor(MonitorType.Ping),
|
|
416
|
+
MonitorType.Logs,
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
expect(describeFilters(aligned.monitorSteps)).toEqual(
|
|
420
|
+
describeFilters(stepsSeededFor(MonitorType.Logs)),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const incidents: Array<CriteriaIncident> = criteriaOf(
|
|
424
|
+
aligned.monitorSteps,
|
|
425
|
+
).flatMap((instance: MonitorCriteriaInstance) => {
|
|
426
|
+
return instance.data?.incidents || [];
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
expect(incidents.length).toBeGreaterThan(0);
|
|
430
|
+
expect(incidents[0]!.incidentSeverityId?.toString()).toBe(
|
|
431
|
+
SEED_OPTIONS.defaultIncidentSeverityId.toString(),
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("what the user configured on the monitor step itself is not collateral damage", () => {
|
|
436
|
+
/*
|
|
437
|
+
* Only the criteria are re-seeded. The destination, request method
|
|
438
|
+
* and everything else on the step belong to the user even when the
|
|
439
|
+
* criteria on it are untouched defaults.
|
|
440
|
+
*
|
|
441
|
+
* The pair matters: Website and API seed byte-identical criteria, so
|
|
442
|
+
* aligning between them short-circuits on "already untouched
|
|
443
|
+
* defaults for this type" and hands the very same object back -
|
|
444
|
+
* which would make this assertion pass no matter what the re-seed
|
|
445
|
+
* branch did to the step. External Status Page actually re-seeds.
|
|
446
|
+
*/
|
|
447
|
+
const seeded: MonitorSteps = stepsSeededFor(MonitorType.Website);
|
|
448
|
+
seeded.data!.monitorStepsInstanceArray[0]!.setMonitorDestination(
|
|
449
|
+
URL.fromString("https://acme.example.com/health"),
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
453
|
+
seeded,
|
|
454
|
+
MonitorType.ExternalStatusPage,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
expect(aligned.didChange).toBe(true);
|
|
458
|
+
|
|
459
|
+
expect(
|
|
460
|
+
aligned.monitorSteps.data!.monitorStepsInstanceArray[0]!.data!.monitorDestination?.toString(),
|
|
461
|
+
).toBe("https://acme.example.com/health");
|
|
462
|
+
|
|
463
|
+
// The step's other settings are not collateral damage either.
|
|
464
|
+
expect(
|
|
465
|
+
aligned.monitorSteps.data!.monitorStepsInstanceArray[0]!.data!
|
|
466
|
+
.requestType,
|
|
467
|
+
).toBe(seeded.data!.monitorStepsInstanceArray[0]!.data!.requestType);
|
|
468
|
+
|
|
469
|
+
expect(
|
|
470
|
+
aligned.monitorSteps.data!.defaultMonitorStatusId?.toString(),
|
|
471
|
+
).toBe(SEED_OPTIONS.onlineMonitorStatusId.toString());
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe("criteria the user has edited are repaired, not replaced", () => {
|
|
476
|
+
function editedWebsiteSteps(): MonitorSteps {
|
|
477
|
+
const monitorSteps: MonitorSteps = stepsSeededFor(MonitorType.Website);
|
|
478
|
+
|
|
479
|
+
const offlineCriteria: MonitorCriteriaInstance =
|
|
480
|
+
criteriaOf(monitorSteps)[0]!;
|
|
481
|
+
|
|
482
|
+
offlineCriteria.setName("Acme is down and I want to know about it");
|
|
483
|
+
offlineCriteria.setFilters([
|
|
484
|
+
...offlineCriteria.data!.filters,
|
|
485
|
+
{
|
|
486
|
+
checkOn: CheckOn.ResponseTime,
|
|
487
|
+
filterType: FilterType.GreaterThan,
|
|
488
|
+
value: 3000,
|
|
489
|
+
},
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
return monitorSteps;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
test("the name the user wrote survives a switch to a completely different monitor type", () => {
|
|
496
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
497
|
+
editedWebsiteSteps(),
|
|
498
|
+
MonitorType.ExternalStatusPage,
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
expect(criteriaNamesOf(aligned.monitorSteps)).toContain(
|
|
502
|
+
"Acme is down and I want to know about it",
|
|
503
|
+
);
|
|
504
|
+
expect(
|
|
505
|
+
unrenderable(aligned.monitorSteps, MonitorType.ExternalStatusPage),
|
|
506
|
+
).toEqual([]);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("the incident the user's criteria opens survives too", () => {
|
|
510
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
511
|
+
editedWebsiteSteps(),
|
|
512
|
+
MonitorType.ExternalStatusPage,
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
const incidents: Array<CriteriaIncident> = criteriaOf(
|
|
516
|
+
aligned.monitorSteps,
|
|
517
|
+
).flatMap((instance: MonitorCriteriaInstance) => {
|
|
518
|
+
return instance.data?.incidents || [];
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
expect(
|
|
522
|
+
incidents.map((incident: CriteriaIncident) => {
|
|
523
|
+
return incident.title;
|
|
524
|
+
}),
|
|
525
|
+
).toContain("Acme is offline");
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("a threshold the user typed survives when the new monitor type still offers that check", () => {
|
|
529
|
+
/*
|
|
530
|
+
* Website and API both offer Response Time, so switching between
|
|
531
|
+
* them must not touch a threshold typed against it - the whole point
|
|
532
|
+
* of repairing rather than re-seeding.
|
|
533
|
+
*/
|
|
534
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
535
|
+
editedWebsiteSteps(),
|
|
536
|
+
MonitorType.API,
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
expect(
|
|
540
|
+
filtersOf(aligned.monitorSteps).find((filter: CriteriaFilter) => {
|
|
541
|
+
return filter.checkOn === CheckOn.ResponseTime;
|
|
542
|
+
})?.value,
|
|
543
|
+
).toBe(3000);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("only the filters the new monitor type cannot render are dropped", () => {
|
|
547
|
+
/*
|
|
548
|
+
* The edited criteria carries three filters: "Is Online" is false,
|
|
549
|
+
* the status code is not 200, and the response took over three
|
|
550
|
+
* seconds. A Server monitor offers Is Online but neither of the
|
|
551
|
+
* other two checks, so the first filter should come through
|
|
552
|
+
* untouched and the other two should simply go.
|
|
553
|
+
*/
|
|
554
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
555
|
+
editedWebsiteSteps(),
|
|
556
|
+
MonitorType.Server,
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const repairedCriteria: MonitorCriteriaInstance = criteriaOf(
|
|
560
|
+
aligned.monitorSteps,
|
|
561
|
+
).find((instance: MonitorCriteriaInstance) => {
|
|
562
|
+
return (
|
|
563
|
+
instance.data!.name === "Acme is down and I want to know about it"
|
|
564
|
+
);
|
|
565
|
+
})!;
|
|
566
|
+
|
|
567
|
+
expect(repairedCriteria.data!.filters).toEqual([
|
|
568
|
+
{
|
|
569
|
+
checkOn: CheckOn.IsOnline,
|
|
570
|
+
filterType: FilterType.False,
|
|
571
|
+
value: undefined,
|
|
572
|
+
},
|
|
573
|
+
]);
|
|
574
|
+
|
|
575
|
+
expect(unrenderable(aligned.monitorSteps, MonitorType.Server)).toEqual(
|
|
576
|
+
[],
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("a filter the new monitor type cannot express is dropped, never swapped for one the user did not write", () => {
|
|
581
|
+
/*
|
|
582
|
+
* This is the regression that matters most in this file.
|
|
583
|
+
*
|
|
584
|
+
* The obvious repair - swap an unusable filter for this monitor
|
|
585
|
+
* type's default - quietly INVERTS the criteria. Every monitor
|
|
586
|
+
* type's default filter is a positive, immediately-matching rule
|
|
587
|
+
* ("is online / True"), and every seeded offline criteria runs
|
|
588
|
+
* under the "Any" filter condition, so one such filter is enough to
|
|
589
|
+
* fire the whole criteria. The criteria keeps its name, its
|
|
590
|
+
* "create incident" flag, its "Acme is offline" incident and its
|
|
591
|
+
* offline monitor status - and now fires while Acme is perfectly
|
|
592
|
+
* healthy.
|
|
593
|
+
*
|
|
594
|
+
* A stale filter at least failed safe by never matching. So: no
|
|
595
|
+
* fabricated filter may ever be added alongside the user's
|
|
596
|
+
* surviving ones.
|
|
597
|
+
*/
|
|
598
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
599
|
+
editedWebsiteSteps(),
|
|
600
|
+
MonitorType.Server,
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
const repairedCriteria: MonitorCriteriaInstance = criteriaOf(
|
|
604
|
+
aligned.monitorSteps,
|
|
605
|
+
).find((instance: MonitorCriteriaInstance) => {
|
|
606
|
+
return (
|
|
607
|
+
instance.data!.name === "Acme is down and I want to know about it"
|
|
608
|
+
);
|
|
609
|
+
})!;
|
|
610
|
+
|
|
611
|
+
// The criteria still opens an incident, under "Any".
|
|
612
|
+
expect(repairedCriteria.data!.createIncidents).toBe(true);
|
|
613
|
+
expect(repairedCriteria.data!.filterCondition).toBe(FilterCondition.Any);
|
|
614
|
+
|
|
615
|
+
const positiveFilters: Array<CriteriaFilter> =
|
|
616
|
+
repairedCriteria.data!.filters.filter((filter: CriteriaFilter) => {
|
|
617
|
+
return filter.filterType === FilterType.True;
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
expect(positiveFilters).toEqual([]);
|
|
621
|
+
expect(repairedCriteria.data!.filters).not.toContainEqual(
|
|
622
|
+
CriteriaFilterUtil.getDefaultCriteriaFilter(MonitorType.Server),
|
|
623
|
+
);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("a criteria left with nothing it can express is refilled from the matching half of the new type's defaults", () => {
|
|
627
|
+
/*
|
|
628
|
+
* Website to External Status Page drops every filter of the offline
|
|
629
|
+
* criteria, and a criteria with no filters at all is not something
|
|
630
|
+
* the form can show. It is refilled - but from the type's OFFLINE
|
|
631
|
+
* defaults, because this criteria opens an incident. Refilling it
|
|
632
|
+
* from the online defaults would produce the inversion above by
|
|
633
|
+
* another route.
|
|
634
|
+
*/
|
|
635
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
636
|
+
editedWebsiteSteps(),
|
|
637
|
+
MonitorType.ExternalStatusPage,
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
const repairedCriteria: MonitorCriteriaInstance = criteriaOf(
|
|
641
|
+
aligned.monitorSteps,
|
|
642
|
+
).find((instance: MonitorCriteriaInstance) => {
|
|
643
|
+
return (
|
|
644
|
+
instance.data!.name === "Acme is down and I want to know about it"
|
|
645
|
+
);
|
|
646
|
+
})!;
|
|
647
|
+
|
|
648
|
+
const offlineDefaults: MonitorCriteriaInstance =
|
|
649
|
+
MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
|
|
650
|
+
monitorType: MonitorType.ExternalStatusPage,
|
|
651
|
+
monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
|
|
652
|
+
incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
|
|
653
|
+
alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
|
|
654
|
+
monitorName: SEED_OPTIONS.monitorName,
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
expect(repairedCriteria.data!.filters).toEqual(
|
|
658
|
+
offlineDefaults.data!.filters,
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
/*
|
|
662
|
+
* Concretely: it fires when the status page is NOT online, which is
|
|
663
|
+
* what a criteria named "Acme is down" and carrying an "Acme is
|
|
664
|
+
* offline" incident has to do.
|
|
665
|
+
*/
|
|
666
|
+
expect(repairedCriteria.data!.filters[0]).toEqual({
|
|
667
|
+
checkOn: CheckOn.ExternalStatusPageIsOnline,
|
|
668
|
+
filterType: FilterType.False,
|
|
669
|
+
value: undefined,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// The user's own wording and incident are still theirs.
|
|
673
|
+
expect(repairedCriteria.data!.name).toBe(
|
|
674
|
+
"Acme is down and I want to know about it",
|
|
675
|
+
);
|
|
676
|
+
expect(repairedCriteria.data!.incidents[0]!.title).toBe(
|
|
677
|
+
"Acme is offline",
|
|
678
|
+
);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("an up criteria left with nothing it can express is refilled from the online defaults", () => {
|
|
682
|
+
/*
|
|
683
|
+
* The other half of the same rule. The seeded "is online" criteria
|
|
684
|
+
* raises nothing and parks the monitor on the operational status,
|
|
685
|
+
* so it must come back matching when the target is UP.
|
|
686
|
+
*/
|
|
687
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
688
|
+
editedWebsiteSteps(),
|
|
689
|
+
MonitorType.ExternalStatusPage,
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
const onlineCriteria: MonitorCriteriaInstance = criteriaOf(
|
|
693
|
+
aligned.monitorSteps,
|
|
694
|
+
).find((instance: MonitorCriteriaInstance) => {
|
|
695
|
+
return (
|
|
696
|
+
instance.data!.monitorStatusId?.toString() ===
|
|
697
|
+
SEED_OPTIONS.onlineMonitorStatusId.toString()
|
|
698
|
+
);
|
|
699
|
+
})!;
|
|
700
|
+
|
|
701
|
+
const onlineDefaults: MonitorCriteriaInstance =
|
|
702
|
+
MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
|
|
703
|
+
monitorType: MonitorType.ExternalStatusPage,
|
|
704
|
+
monitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
|
|
705
|
+
monitorName: SEED_OPTIONS.monitorName,
|
|
706
|
+
})!;
|
|
707
|
+
|
|
708
|
+
expect(onlineCriteria.data!.filters).toEqual(
|
|
709
|
+
onlineDefaults.data!.filters,
|
|
710
|
+
);
|
|
711
|
+
expect(onlineCriteria.data!.filters[0]).toEqual({
|
|
712
|
+
checkOn: CheckOn.ExternalStatusPageIsOnline,
|
|
713
|
+
filterType: FilterType.True,
|
|
714
|
+
value: undefined,
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test.each(MONITOR_TYPES_WITH_CRITERIA)(
|
|
719
|
+
"no criteria is ever left with no filters at all, aligning to %s",
|
|
720
|
+
(monitorType: MonitorType) => {
|
|
721
|
+
const empty: Array<string> = [];
|
|
722
|
+
|
|
723
|
+
for (const seededFor of sourceTypesFor(monitorType)) {
|
|
724
|
+
const edited: MonitorSteps = stepsSeededFor(seededFor);
|
|
725
|
+
criteriaOf(edited).forEach((instance: MonitorCriteriaInstance) => {
|
|
726
|
+
instance.setName("My own rule");
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
730
|
+
edited,
|
|
731
|
+
monitorType,
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
for (const instance of criteriaOf(aligned.monitorSteps)) {
|
|
735
|
+
if ((instance.data?.filters || []).length === 0) {
|
|
736
|
+
empty.push(
|
|
737
|
+
`seeded for ${seededFor}: "${instance.data!.name}" came back with no filters`,
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
expect(empty).toEqual([]);
|
|
744
|
+
},
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
test.each(MONITOR_TYPES_WITH_CRITERIA)(
|
|
748
|
+
"an incident-raising criteria is never refilled with the up-rules, aligning to %s",
|
|
749
|
+
(monitorType: MonitorType) => {
|
|
750
|
+
/*
|
|
751
|
+
* The inversion, swept over every pair. A criteria that opens an
|
|
752
|
+
* incident and had to be refilled must not come back carrying the
|
|
753
|
+
* rules this monitor type uses to decide it is UP - that is the
|
|
754
|
+
* shape that alerts when all is well.
|
|
755
|
+
*
|
|
756
|
+
* Stated against the online defaults rather than against the
|
|
757
|
+
* offline ones on purpose: "did not end up with the up-rules" is
|
|
758
|
+
* the property that matters and is not a restatement of how the
|
|
759
|
+
* replacement is chosen.
|
|
760
|
+
*/
|
|
761
|
+
const onlineDefaults: string = JSON.stringify(
|
|
762
|
+
MonitorCriteriaAlignmentUtil.renderableFilters(
|
|
763
|
+
MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
|
|
764
|
+
monitorType: monitorType,
|
|
765
|
+
monitorStatusId: SEED_OPTIONS.onlineMonitorStatusId,
|
|
766
|
+
monitorName: SEED_OPTIONS.monitorName,
|
|
767
|
+
})?.data?.filters || [],
|
|
768
|
+
monitorType,
|
|
769
|
+
),
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
const offlineDefaults: string = JSON.stringify(
|
|
773
|
+
MonitorCriteriaAlignmentUtil.renderableFilters(
|
|
774
|
+
MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
|
|
775
|
+
monitorType: monitorType,
|
|
776
|
+
monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
|
|
777
|
+
incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
|
|
778
|
+
alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
|
|
779
|
+
monitorName: SEED_OPTIONS.monitorName,
|
|
780
|
+
}).data?.filters || [],
|
|
781
|
+
monitorType,
|
|
782
|
+
),
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
/*
|
|
786
|
+
* Nothing to get wrong on types whose two halves are the same
|
|
787
|
+
* rules (or which seed only one half).
|
|
788
|
+
*/
|
|
789
|
+
if (onlineDefaults === offlineDefaults || onlineDefaults === "[]") {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const inverted: Array<string> = [];
|
|
794
|
+
|
|
795
|
+
for (const seededFor of sourceTypesFor(monitorType)) {
|
|
796
|
+
const edited: MonitorSteps = stepsSeededFor(seededFor);
|
|
797
|
+
criteriaOf(edited).forEach((instance: MonitorCriteriaInstance) => {
|
|
798
|
+
instance.setName("My own rule");
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const before: Map<string, string> = new Map<string, string>(
|
|
802
|
+
criteriaOf(edited).map((instance: MonitorCriteriaInstance) => {
|
|
803
|
+
return [
|
|
804
|
+
instance.data!.id,
|
|
805
|
+
JSON.stringify(instance.data!.filters),
|
|
806
|
+
];
|
|
807
|
+
}),
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
811
|
+
edited,
|
|
812
|
+
monitorType,
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
for (const instance of criteriaOf(aligned.monitorSteps)) {
|
|
816
|
+
if (!instance.data?.createIncidents) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const after: string = JSON.stringify(instance.data.filters);
|
|
821
|
+
|
|
822
|
+
// Untouched criteria are not refills; nothing was chosen.
|
|
823
|
+
if (before.get(instance.data.id) === after) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (after === onlineDefaults) {
|
|
828
|
+
inverted.push(
|
|
829
|
+
`seeded for ${seededFor}: incident-raising "${instance.data.name}" was refilled with ${monitorType}'s up-rules`,
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
expect(inverted).toEqual([]);
|
|
836
|
+
},
|
|
837
|
+
);
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
describe("the monitor types excluded from the sweeps above", () => {
|
|
841
|
+
/*
|
|
842
|
+
* The exclusion list is only honest while it is true. These two
|
|
843
|
+
* assertions fail if a type on it grows real seeded criteria (at which
|
|
844
|
+
* point it should come off, and the sweeps should cover it) or if a
|
|
845
|
+
* type not on it loses them.
|
|
846
|
+
*/
|
|
847
|
+
test.each(MONITOR_TYPES_WITHOUT_SEEDED_CRITERIA)(
|
|
848
|
+
"%s really does seed nothing its own form can draw",
|
|
849
|
+
(monitorType: MonitorType) => {
|
|
850
|
+
expect(
|
|
851
|
+
MonitorCriteriaAlignmentUtil.renderableFilters(
|
|
852
|
+
MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
|
|
853
|
+
monitorType: monitorType,
|
|
854
|
+
monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
|
|
855
|
+
incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
|
|
856
|
+
alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
|
|
857
|
+
monitorName: SEED_OPTIONS.monitorName,
|
|
858
|
+
}).data?.filters || [],
|
|
859
|
+
monitorType,
|
|
860
|
+
),
|
|
861
|
+
).toEqual([]);
|
|
862
|
+
},
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
test.each(MONITOR_TYPES_WITH_SEEDED_CRITERIA)(
|
|
866
|
+
"%s really does seed something its own form can draw",
|
|
867
|
+
(monitorType: MonitorType) => {
|
|
868
|
+
expect(
|
|
869
|
+
MonitorCriteriaAlignmentUtil.renderableFilters(
|
|
870
|
+
MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
|
|
871
|
+
monitorType: monitorType,
|
|
872
|
+
monitorStatusId: SEED_OPTIONS.offlineMonitorStatusId,
|
|
873
|
+
incidentSeverityId: SEED_OPTIONS.defaultIncidentSeverityId,
|
|
874
|
+
alertSeverityId: SEED_OPTIONS.defaultAlertSeverityId,
|
|
875
|
+
monitorName: SEED_OPTIONS.monitorName,
|
|
876
|
+
}).data?.filters || [],
|
|
877
|
+
monitorType,
|
|
878
|
+
).length,
|
|
879
|
+
).toBeGreaterThan(0);
|
|
880
|
+
},
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
test("aligning TO one of them still produces something drawable", () => {
|
|
884
|
+
/*
|
|
885
|
+
* The half that does work, and the half this change is responsible
|
|
886
|
+
* for: a Website's criteria arriving on a Docker monitor come back
|
|
887
|
+
* on the metric check Docker offers, not on the placeholder Docker
|
|
888
|
+
* would have seeded for itself.
|
|
889
|
+
*/
|
|
890
|
+
const aligned: MonitorStepsAlignmentResult = alignTo(
|
|
891
|
+
stepsSeededFor(MonitorType.Website),
|
|
892
|
+
MonitorType.Docker,
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
expect(aligned.didChange).toBe(true);
|
|
896
|
+
expect(unrenderable(aligned.monitorSteps, MonitorType.Docker)).toEqual(
|
|
897
|
+
[],
|
|
898
|
+
);
|
|
899
|
+
expect(filtersOf(aligned.monitorSteps)[0]!.checkOn).toBe(
|
|
900
|
+
CheckOn.MetricValue,
|
|
901
|
+
);
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
describe("repairCriteriaFilterForMonitorType", () => {
|
|
906
|
+
test("a filter the monitor type can already draw comes back as the very same object", () => {
|
|
907
|
+
const criteriaFilter: CriteriaFilter = {
|
|
908
|
+
checkOn: CheckOn.ResponseTime,
|
|
909
|
+
filterType: FilterType.GreaterThan,
|
|
910
|
+
value: 5000,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
expect(
|
|
914
|
+
CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
|
|
915
|
+
criteriaFilter: criteriaFilter,
|
|
916
|
+
monitorType: MonitorType.Website,
|
|
917
|
+
}),
|
|
918
|
+
).toBe(criteriaFilter);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
test("a check the monitor type still offers keeps its value, only the condition moves", () => {
|
|
922
|
+
/*
|
|
923
|
+
* "Is Online" has never accepted Equal To - criteria stored that way
|
|
924
|
+
* predate the seed being fixed. The check is fine on a Website, so
|
|
925
|
+
* only the condition is replaced.
|
|
926
|
+
*/
|
|
927
|
+
const repaired: CriteriaFilter | null =
|
|
928
|
+
CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
|
|
929
|
+
criteriaFilter: {
|
|
930
|
+
checkOn: CheckOn.IsOnline,
|
|
931
|
+
filterType: FilterType.EqualTo,
|
|
932
|
+
value: "keep me",
|
|
933
|
+
},
|
|
934
|
+
monitorType: MonitorType.Website,
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
expect(repaired).not.toBeNull();
|
|
938
|
+
expect(repaired!.checkOn).toBe(CheckOn.IsOnline);
|
|
939
|
+
expect(repaired!.filterType).toBe(FilterType.True);
|
|
940
|
+
expect(repaired!.value).toBe("keep me");
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
test("a check the monitor type does not offer has no repair at all", () => {
|
|
944
|
+
/*
|
|
945
|
+
* Not this monitor type's default filter - null. The value went
|
|
946
|
+
* with the check (3000ms of response time means nothing as a status
|
|
947
|
+
* page component status), and substituting the type's default here
|
|
948
|
+
* would hand the caller a positive, immediately-matching rule the
|
|
949
|
+
* user never wrote. Whether that rule belongs in this criteria
|
|
950
|
+
* depends on the whole criteria, so the caller decides.
|
|
951
|
+
*/
|
|
952
|
+
expect(
|
|
953
|
+
CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
|
|
954
|
+
criteriaFilter: {
|
|
955
|
+
checkOn: CheckOn.ResponseTime,
|
|
956
|
+
filterType: FilterType.GreaterThan,
|
|
957
|
+
value: 3000,
|
|
958
|
+
},
|
|
959
|
+
monitorType: MonitorType.ExternalStatusPage,
|
|
960
|
+
}),
|
|
961
|
+
).toBeNull();
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
test.each(ALL_MONITOR_TYPES)(
|
|
965
|
+
"%s: whatever filter it is handed, what comes back has both dropdowns chosen",
|
|
966
|
+
(monitorType: MonitorType) => {
|
|
967
|
+
const handedIn: Array<CriteriaFilter> = [
|
|
968
|
+
...ALL_MONITOR_TYPES.flatMap((seededFor: MonitorType) => {
|
|
969
|
+
return filtersOf(stepsSeededFor(seededFor));
|
|
970
|
+
}),
|
|
971
|
+
// Not filters any seed produces: mismatched, blank, and nonsense.
|
|
972
|
+
{
|
|
973
|
+
checkOn: CheckOn.IsOnline,
|
|
974
|
+
filterType: FilterType.EqualTo,
|
|
975
|
+
value: undefined,
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
checkOn: CheckOn.MetricValue,
|
|
979
|
+
filterType: undefined,
|
|
980
|
+
value: undefined,
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
checkOn: "Not A Real Check" as CheckOn,
|
|
984
|
+
filterType: "Not A Real Filter Type" as FilterType,
|
|
985
|
+
value: undefined,
|
|
986
|
+
},
|
|
987
|
+
];
|
|
988
|
+
|
|
989
|
+
const offeredChecks: Array<string> = checkOnOptionsFor(monitorType);
|
|
990
|
+
const problems: Array<string> = [];
|
|
991
|
+
|
|
992
|
+
for (const criteriaFilter of handedIn) {
|
|
993
|
+
const repaired: CriteriaFilter | null =
|
|
994
|
+
CriteriaFilterUtil.repairCriteriaFilterForMonitorType({
|
|
995
|
+
criteriaFilter: criteriaFilter,
|
|
996
|
+
monitorType: monitorType,
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
if (!repaired) {
|
|
1000
|
+
// "cannot be expressed" is a valid answer; the caller handles it.
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (!offeredChecks.includes(repaired.checkOn?.toString())) {
|
|
1005
|
+
problems.push(
|
|
1006
|
+
`"${criteriaFilter.checkOn}" was repaired to check "${repaired.checkOn}", which ${monitorType} does not offer`,
|
|
1007
|
+
);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (
|
|
1012
|
+
!repaired.filterType ||
|
|
1013
|
+
!filterTypeOptionsFor(repaired.checkOn).includes(
|
|
1014
|
+
repaired.filterType.toString(),
|
|
1015
|
+
)
|
|
1016
|
+
) {
|
|
1017
|
+
problems.push(
|
|
1018
|
+
`"${criteriaFilter.checkOn}" was repaired to check "${repaired.checkOn}" with condition "${repaired.filterType}", which that check does not offer`,
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
expect(problems).toEqual([]);
|
|
1024
|
+
},
|
|
1025
|
+
);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
describe("isUntouchedDefaultFor", () => {
|
|
1029
|
+
test.each(ALL_MONITOR_TYPES)(
|
|
1030
|
+
"%s recognises its own freshly seeded criteria",
|
|
1031
|
+
(monitorType: MonitorType) => {
|
|
1032
|
+
expect(
|
|
1033
|
+
MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
|
|
1034
|
+
monitorCriteria:
|
|
1035
|
+
stepsSeededFor(monitorType).data!.monitorStepsInstanceArray[0]!
|
|
1036
|
+
.data!.monitorCriteria,
|
|
1037
|
+
monitorType: monitorType,
|
|
1038
|
+
seedOptions: SEED_OPTIONS,
|
|
1039
|
+
}),
|
|
1040
|
+
).toBe(true);
|
|
1041
|
+
},
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
test("the ids generated fresh for every seed are not mistaken for edits", () => {
|
|
1045
|
+
/*
|
|
1046
|
+
* Each seed generates new ObjectIDs for its criteria, incidents and
|
|
1047
|
+
* alerts, so two seeds of the same type are never byte-identical.
|
|
1048
|
+
* Comparing on those would report every criteria as edited and turn
|
|
1049
|
+
* the re-seed path off entirely.
|
|
1050
|
+
*/
|
|
1051
|
+
const first: MonitorSteps = stepsSeededFor(MonitorType.Website);
|
|
1052
|
+
const second: MonitorSteps = stepsSeededFor(MonitorType.Website);
|
|
1053
|
+
|
|
1054
|
+
expect(criteriaOf(first)[0]!.data!.id).not.toBe(
|
|
1055
|
+
criteriaOf(second)[0]!.data!.id,
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
expect(
|
|
1059
|
+
MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
|
|
1060
|
+
monitorCriteria:
|
|
1061
|
+
first.data!.monitorStepsInstanceArray[0]!.data!.monitorCriteria,
|
|
1062
|
+
monitorType: MonitorType.Website,
|
|
1063
|
+
seedOptions: SEED_OPTIONS,
|
|
1064
|
+
}),
|
|
1065
|
+
).toBe(true);
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
test("a criteria the user renamed is not an untouched default", () => {
|
|
1069
|
+
const monitorSteps: MonitorSteps = stepsSeededFor(MonitorType.Website);
|
|
1070
|
+
criteriaOf(monitorSteps)[0]!.setName("Mine now");
|
|
1071
|
+
|
|
1072
|
+
expect(
|
|
1073
|
+
MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
|
|
1074
|
+
monitorCriteria:
|
|
1075
|
+
monitorSteps.data!.monitorStepsInstanceArray[0]!.data!
|
|
1076
|
+
.monitorCriteria,
|
|
1077
|
+
monitorType: MonitorType.Website,
|
|
1078
|
+
seedOptions: SEED_OPTIONS,
|
|
1079
|
+
}),
|
|
1080
|
+
).toBe(false);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
test("criteria that survived a round trip through the form's JSON are still untouched", () => {
|
|
1084
|
+
/*
|
|
1085
|
+
* The criteria the form hands back on mount have been serialized
|
|
1086
|
+
* into the form's value and read out again, which drops keys whose
|
|
1087
|
+
* value is undefined. That is not an edit, and treating it as one
|
|
1088
|
+
* would disable re-seeding on exactly the path the bug arrives by.
|
|
1089
|
+
*/
|
|
1090
|
+
const roundTripped: MonitorSteps = MonitorSteps.fromJSON(
|
|
1091
|
+
stepsSeededFor(MonitorType.Website).toJSON(),
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
expect(
|
|
1095
|
+
MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
|
|
1096
|
+
monitorCriteria:
|
|
1097
|
+
roundTripped.data!.monitorStepsInstanceArray[0]!.data!
|
|
1098
|
+
.monitorCriteria,
|
|
1099
|
+
monitorType: MonitorType.Website,
|
|
1100
|
+
seedOptions: SEED_OPTIONS,
|
|
1101
|
+
}),
|
|
1102
|
+
).toBe(true);
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
test("the monitor name is part of what makes criteria untouched", () => {
|
|
1106
|
+
/*
|
|
1107
|
+
* The seeded names and incident titles quote the monitor's name, so
|
|
1108
|
+
* criteria seeded for one name are not the defaults for another.
|
|
1109
|
+
* Being wrong in this direction only costs a re-seed - the criteria
|
|
1110
|
+
* are repaired instead - which is the safe way to be wrong.
|
|
1111
|
+
*/
|
|
1112
|
+
expect(
|
|
1113
|
+
MonitorCriteriaAlignmentUtil.isUntouchedDefaultFor({
|
|
1114
|
+
monitorCriteria: stepsSeededFor(MonitorType.Website).data!
|
|
1115
|
+
.monitorStepsInstanceArray[0]!.data!.monitorCriteria,
|
|
1116
|
+
monitorType: MonitorType.Website,
|
|
1117
|
+
seedOptions: { ...SEED_OPTIONS, monitorName: "Something else" },
|
|
1118
|
+
}),
|
|
1119
|
+
).toBe(false);
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
});
|