@oneuptime/common 11.7.3 → 11.7.4
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/AlertEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Index.ts +5 -0
- package/Models/DatabaseModels/LogDropFilter.ts +89 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
- package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
- package/Server/API/AlertAPI.ts +32 -17
- package/Server/API/BaseAPI.ts +31 -2
- package/Server/API/BaseAnalyticsAPI.ts +36 -2
- package/Server/API/CommonAPI.ts +57 -0
- package/Server/API/IncidentAPI.ts +60 -33
- package/Server/API/IncidentEpisodeAPI.ts +31 -16
- package/Server/API/MonitorAPI.ts +30 -0
- package/Server/API/MonitorTemplateAPI.ts +8 -0
- package/Server/API/ProbeAPI.ts +196 -15
- package/Server/API/ProjectAPI.ts +79 -1
- package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
- package/Server/API/StatusPageAPI.ts +74 -37
- package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Services/AlertEpisodeService.ts +47 -0
- package/Server/Services/AlertService.ts +125 -0
- package/Server/Services/BillingService.ts +98 -0
- package/Server/Services/DatabaseService.ts +184 -20
- package/Server/Services/IncidentEpisodeService.ts +47 -0
- package/Server/Services/IncidentService.ts +123 -0
- package/Server/Services/IncidentTemplateService.ts +123 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LogDropFilterService.ts +133 -0
- package/Server/Services/MonitorProbeService.ts +94 -4
- package/Server/Services/MonitorService.ts +48 -43
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
- package/Server/Services/ProbeService.ts +66 -0
- package/Server/Services/ProjectService.ts +131 -7
- package/Server/Services/RecommendationDismissalService.ts +52 -0
- package/Server/Services/RunbookAgentJobService.ts +68 -7
- package/Server/Services/ScheduledMaintenanceService.ts +103 -0
- package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
- package/Server/Services/StatusPageGroupService.ts +186 -0
- package/Server/Services/TraceDropFilterService.ts +133 -0
- package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
- package/Server/Types/Workflow/Components/API/Get.ts +12 -0
- package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
- package/Server/Types/Workflow/Components/API/Post.ts +12 -0
- package/Server/Types/Workflow/Components/API/Put.ts +13 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
- package/Server/Utils/DropFilterValidation.ts +127 -0
- package/Server/Utils/Errors.ts +5 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
- package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
- package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
- package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
- package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
- package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
- package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
- package/Tests/Server/API/ProbeAPI.test.ts +422 -53
- package/Tests/Server/API/ProjectAPI.test.ts +180 -0
- package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
- package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
- package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
- package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
- package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
- package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
- package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
- package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
- package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
- package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
- package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
- package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
- package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
- package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
- package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
- package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
- package/Tests/Types/DateUserTimezone.test.ts +52 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
- package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
- package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
- package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
- package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
- package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
- package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
- package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
- package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
- package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
- package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
- package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
- package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
- package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
- package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
- package/Tests/UI/Components/InfoCard.test.tsx +358 -0
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
- package/Tests/UI/Components/Modal.test.tsx +43 -5
- package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +427 -0
- package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
- package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
- package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
- package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
- package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
- package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
- package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
- package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
- package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
- package/Types/Date.ts +15 -6
- package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
- package/Types/Monitor/MonitorStep.ts +10 -1
- package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
- package/Types/Permission.ts +45 -0
- package/Types/Recommendation/RecommendationType.ts +20 -0
- package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
- package/Types/Runbook/RunbookStepTimeout.ts +202 -0
- package/Types/Telemetry/DropFilterSampling.ts +92 -0
- package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
- package/UI/Components/Accordion/Accordion.tsx +10 -2
- package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
- package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
- package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
- package/UI/Components/Error/PageError.tsx +0 -4
- package/UI/Components/ErrorBoundary.tsx +252 -15
- package/UI/Components/InfoCard/InfoCard.tsx +58 -1
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
- package/UI/Components/Modal/Modal.tsx +16 -60
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
- package/UI/Components/ModelTable/BaseModelTable.tsx +116 -67
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +150 -47
- package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
- package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
- package/UI/Utils/DropdownAlignment.ts +162 -0
- package/UI/Utils/TableFilterUrlState.ts +59 -20
- package/Utils/API.ts +106 -2
- package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
- package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
- package/Utils/StatusPage/GroupTree.ts +307 -0
- package/Utils/StatusPage/ResourceUptime.ts +90 -17
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
- package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/API/AlertAPI.js +22 -13
- package/build/dist/Server/API/AlertAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +22 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +42 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +43 -26
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
- package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
- package/build/dist/Server/API/MonitorAPI.js +23 -0
- package/build/dist/Server/API/MonitorAPI.js.map +1 -1
- package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
- package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
- package/build/dist/Server/API/ProbeAPI.js +131 -15
- package/build/dist/Server/API/ProbeAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectAPI.js +62 -3
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +61 -33
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
- package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
- package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +95 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +71 -0
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +140 -17
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +100 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LogDropFilterService.js +93 -0
- package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +73 -5
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +34 -39
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +64 -0
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +103 -7
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
- package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
- package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
- package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
- package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
- package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
- package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
- package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
- package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
- package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
- package/build/dist/Server/Utils/Errors.js +3 -0
- package/build/dist/Server/Utils/Errors.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
- package/build/dist/Types/Date.js +14 -4
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +6 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
- package/build/dist/Types/Permission.js +40 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
- package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
- package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
- package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
- package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
- package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
- package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
- package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
- package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Error/PageError.js +1 -3
- package/build/dist/UI/Components/Error/PageError.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +145 -11
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +4 -32
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +69 -33
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +73 -32
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
- package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
- package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
- package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
- package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
- package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
- package/build/dist/Utils/API.js +39 -2
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
- package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
- package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
- package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DropFilterCandidate,
|
|
3
|
+
DropFilterValidationOptions,
|
|
4
|
+
isSqlExpressionValue,
|
|
5
|
+
SAMPLE_ACTION,
|
|
6
|
+
validateDropFilter,
|
|
7
|
+
validateDropFilterFilterQuery,
|
|
8
|
+
validateDropFilterSamplePercentage,
|
|
9
|
+
} from "../../../Server/Utils/DropFilterValidation";
|
|
10
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
11
|
+
import {
|
|
12
|
+
MAX_SAMPLE_PERCENTAGE,
|
|
13
|
+
MIN_SAMPLE_PERCENTAGE,
|
|
14
|
+
} from "../../../Types/Telemetry/DropFilterSampling";
|
|
15
|
+
import LogDropFilterAction from "../../../Types/Log/LogDropFilterAction";
|
|
16
|
+
import TraceDropFilterAction from "../../../Types/Trace/TraceDropFilterAction";
|
|
17
|
+
import { describe, expect, it } from "@jest/globals";
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* Contract under test — the save-time gate on drop filters.
|
|
21
|
+
*
|
|
22
|
+
* Both drop-filter models used to accept configurations the ingest engine
|
|
23
|
+
* could not honour, and both failed silently AND destructively:
|
|
24
|
+
*
|
|
25
|
+
* - A blank `filterQuery` compiles to "match every record", so saving a
|
|
26
|
+
* drop filter with an empty query discarded 100% of a project's
|
|
27
|
+
* telemetry with nothing to notice it by.
|
|
28
|
+
* - `action = "sample"` could be saved with no percentage, which the engine
|
|
29
|
+
* read as "throw away half".
|
|
30
|
+
*
|
|
31
|
+
* The API boundary is the only place a human is present to read the error,
|
|
32
|
+
* which is why this runs on create and on update rather than only in the
|
|
33
|
+
* dashboard form.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const LOGS: DropFilterValidationOptions = { recordNoun: "logs" };
|
|
37
|
+
const SPANS: DropFilterValidationOptions = { recordNoun: "spans" };
|
|
38
|
+
|
|
39
|
+
function candidate(
|
|
40
|
+
overrides: Partial<DropFilterCandidate>,
|
|
41
|
+
): DropFilterCandidate {
|
|
42
|
+
return {
|
|
43
|
+
action: "drop",
|
|
44
|
+
samplePercentage: null,
|
|
45
|
+
filterQuery: "severityText = 'Error'",
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("drop filter action vocabulary", () => {
|
|
51
|
+
/*
|
|
52
|
+
* The validator compares a raw string so one implementation can serve both
|
|
53
|
+
* services. If either enum ever renames its member, this fails instead of
|
|
54
|
+
* silently skipping sample validation.
|
|
55
|
+
*/
|
|
56
|
+
it("matches the literal both action enums use for sampling", () => {
|
|
57
|
+
expect(LogDropFilterAction.Sample).toBe(SAMPLE_ACTION);
|
|
58
|
+
expect(TraceDropFilterAction.Sample).toBe(SAMPLE_ACTION);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("validateDropFilterFilterQuery", () => {
|
|
63
|
+
it("accepts a query with an actual condition", () => {
|
|
64
|
+
expect(() => {
|
|
65
|
+
return validateDropFilterFilterQuery("body CONTAINS 'debug'", LOGS);
|
|
66
|
+
}).not.toThrow();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* The destructive case: empty means match-all, and match-all plus a drop
|
|
71
|
+
* action means the project loses everything.
|
|
72
|
+
*/
|
|
73
|
+
it("rejects an empty query", () => {
|
|
74
|
+
expect(() => {
|
|
75
|
+
return validateDropFilterFilterQuery("", LOGS);
|
|
76
|
+
}).toThrow(BadDataException);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("rejects a whitespace-only query", () => {
|
|
80
|
+
for (const blank of [" ", " ", "\t", "\n", " \t\n "]) {
|
|
81
|
+
expect(() => {
|
|
82
|
+
return validateDropFilterFilterQuery(blank, LOGS);
|
|
83
|
+
}).toThrow(BadDataException);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("rejects a missing query", () => {
|
|
88
|
+
expect(() => {
|
|
89
|
+
return validateDropFilterFilterQuery(undefined, LOGS);
|
|
90
|
+
}).toThrow(BadDataException);
|
|
91
|
+
expect(() => {
|
|
92
|
+
return validateDropFilterFilterQuery(null, LOGS);
|
|
93
|
+
}).toThrow(BadDataException);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
* The message has to explain the consequence, not just name the field —
|
|
98
|
+
* "Filter query is required" alone does not tell someone that saving it
|
|
99
|
+
* blank would have deleted all their logs.
|
|
100
|
+
*/
|
|
101
|
+
it("explains the consequence in the message, in the caller's vocabulary", () => {
|
|
102
|
+
expect(() => {
|
|
103
|
+
return validateDropFilterFilterQuery("", LOGS);
|
|
104
|
+
}).toThrow(/discard all of this project's logs/);
|
|
105
|
+
|
|
106
|
+
expect(() => {
|
|
107
|
+
return validateDropFilterFilterQuery("", SPANS);
|
|
108
|
+
}).toThrow(/discard all of this project's spans/);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("validateDropFilterSamplePercentage", () => {
|
|
113
|
+
it("ignores the percentage entirely for a drop-action filter", () => {
|
|
114
|
+
for (const percentage of [null, undefined, 0, -5, 100, 1000]) {
|
|
115
|
+
expect(() => {
|
|
116
|
+
return validateDropFilterSamplePercentage(
|
|
117
|
+
candidate({ action: "drop", samplePercentage: percentage }),
|
|
118
|
+
LOGS,
|
|
119
|
+
);
|
|
120
|
+
}).not.toThrow();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("accepts a sample filter inside the usable range", () => {
|
|
125
|
+
for (const percentage of [
|
|
126
|
+
MIN_SAMPLE_PERCENTAGE,
|
|
127
|
+
10,
|
|
128
|
+
50,
|
|
129
|
+
MAX_SAMPLE_PERCENTAGE,
|
|
130
|
+
]) {
|
|
131
|
+
expect(() => {
|
|
132
|
+
return validateDropFilterSamplePercentage(
|
|
133
|
+
candidate({ action: SAMPLE_ACTION, samplePercentage: percentage }),
|
|
134
|
+
LOGS,
|
|
135
|
+
);
|
|
136
|
+
}).not.toThrow();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("rejects a sample filter with no percentage at all", () => {
|
|
141
|
+
for (const missing of [null, undefined]) {
|
|
142
|
+
expect(() => {
|
|
143
|
+
return validateDropFilterSamplePercentage(
|
|
144
|
+
candidate({ action: SAMPLE_ACTION, samplePercentage: missing }),
|
|
145
|
+
LOGS,
|
|
146
|
+
);
|
|
147
|
+
}).toThrow(BadDataException);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("rejects a sample filter with a non-finite percentage", () => {
|
|
152
|
+
for (const bad of [NaN, Infinity, -Infinity]) {
|
|
153
|
+
expect(() => {
|
|
154
|
+
return validateDropFilterSamplePercentage(
|
|
155
|
+
candidate({ action: SAMPLE_ACTION, samplePercentage: bad }),
|
|
156
|
+
LOGS,
|
|
157
|
+
);
|
|
158
|
+
}).toThrow(BadDataException);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/*
|
|
163
|
+
* 0 and 100 are the two values a user reaches for when they mean something
|
|
164
|
+
* else. Point them at the tool that actually does it rather than accepting
|
|
165
|
+
* a filter the engine will treat as a no-op.
|
|
166
|
+
*/
|
|
167
|
+
it("rejects 0 and points at the Drop action instead", () => {
|
|
168
|
+
expect(() => {
|
|
169
|
+
return validateDropFilterSamplePercentage(
|
|
170
|
+
candidate({ action: SAMPLE_ACTION, samplePercentage: 0 }),
|
|
171
|
+
LOGS,
|
|
172
|
+
);
|
|
173
|
+
}).toThrow(/"Drop" action/);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("rejects 100 and points at disabling the filter instead", () => {
|
|
177
|
+
expect(() => {
|
|
178
|
+
return validateDropFilterSamplePercentage(
|
|
179
|
+
candidate({ action: SAMPLE_ACTION, samplePercentage: 100 }),
|
|
180
|
+
LOGS,
|
|
181
|
+
);
|
|
182
|
+
}).toThrow(/disable the filter/);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("rejects percentages outside the range on both sides", () => {
|
|
186
|
+
for (const outOfRange of [-1, 0, 100, 101, 1000]) {
|
|
187
|
+
expect(() => {
|
|
188
|
+
return validateDropFilterSamplePercentage(
|
|
189
|
+
candidate({ action: SAMPLE_ACTION, samplePercentage: outOfRange }),
|
|
190
|
+
LOGS,
|
|
191
|
+
);
|
|
192
|
+
}).toThrow(BadDataException);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("names the allowed range in the message so the fix is obvious", () => {
|
|
197
|
+
expect(() => {
|
|
198
|
+
return validateDropFilterSamplePercentage(
|
|
199
|
+
candidate({ action: SAMPLE_ACTION, samplePercentage: null }),
|
|
200
|
+
LOGS,
|
|
201
|
+
);
|
|
202
|
+
}).toThrow(
|
|
203
|
+
new RegExp(`${MIN_SAMPLE_PERCENTAGE} and ${MAX_SAMPLE_PERCENTAGE}`),
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("validateDropFilter", () => {
|
|
209
|
+
it("accepts a well-formed drop filter", () => {
|
|
210
|
+
expect(() => {
|
|
211
|
+
return validateDropFilter(
|
|
212
|
+
candidate({ action: "drop", filterQuery: "severityText = 'Debug'" }),
|
|
213
|
+
LOGS,
|
|
214
|
+
);
|
|
215
|
+
}).not.toThrow();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("accepts a well-formed sample filter", () => {
|
|
219
|
+
expect(() => {
|
|
220
|
+
return validateDropFilter(
|
|
221
|
+
candidate({
|
|
222
|
+
action: SAMPLE_ACTION,
|
|
223
|
+
samplePercentage: 10,
|
|
224
|
+
filterQuery: "severityText = 'Debug'",
|
|
225
|
+
}),
|
|
226
|
+
LOGS,
|
|
227
|
+
);
|
|
228
|
+
}).not.toThrow();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/*
|
|
232
|
+
* The query check runs first on purpose. A row that is broken both ways
|
|
233
|
+
* should surface the more destructive problem, because a match-all drop
|
|
234
|
+
* filter deletes everything while a bad percentage only samples.
|
|
235
|
+
*/
|
|
236
|
+
it("reports the empty query first when a candidate is broken both ways", () => {
|
|
237
|
+
expect(() => {
|
|
238
|
+
return validateDropFilter(
|
|
239
|
+
candidate({
|
|
240
|
+
action: SAMPLE_ACTION,
|
|
241
|
+
samplePercentage: null,
|
|
242
|
+
filterQuery: "",
|
|
243
|
+
}),
|
|
244
|
+
LOGS,
|
|
245
|
+
);
|
|
246
|
+
}).toThrow(/Filter query is required/);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("isSqlExpressionValue", () => {
|
|
251
|
+
/*
|
|
252
|
+
* `PartialEntity` values may be raw SQL expressions. We cannot evaluate one
|
|
253
|
+
* to validate it, and stringifying a function to compare it would be worse
|
|
254
|
+
* than skipping — so the update hook detects and skips.
|
|
255
|
+
*/
|
|
256
|
+
it("detects a raw SQL expression value", () => {
|
|
257
|
+
expect(
|
|
258
|
+
isSqlExpressionValue(() => {
|
|
259
|
+
return "NOW()";
|
|
260
|
+
}),
|
|
261
|
+
).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("treats every ordinary literal as a plain value", () => {
|
|
265
|
+
for (const literal of ["sample", "", 0, 50, null, undefined, {}, []]) {
|
|
266
|
+
expect(isSqlExpressionValue(literal)).toBe(false);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import SpanUtil, {
|
|
2
|
+
SpanAttributes,
|
|
3
|
+
} from "../../../../Server/Utils/Telemetry/SpanUtil";
|
|
4
|
+
import { describe, expect, it, jest } from "@jest/globals";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* SpanUtil.addAttributesToSpan copies telemetry attributes onto a span, but it
|
|
8
|
+
* must drop undefined/null values - OpenTelemetry rejects those and throwing
|
|
9
|
+
* mid-request would break the traced operation. This pins the filtering so a
|
|
10
|
+
* partially-populated SpanAttributes object stays safe to pass in.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// A stand-in Span that just records what setAttribute was called with.
|
|
14
|
+
function fakeSpan(): {
|
|
15
|
+
span: { setAttribute: (key: string, value: unknown) => void };
|
|
16
|
+
calls: Array<[string, unknown]>;
|
|
17
|
+
} {
|
|
18
|
+
const calls: Array<[string, unknown]> = [];
|
|
19
|
+
const setAttribute: (key: string, value: unknown) => void = jest.fn(
|
|
20
|
+
(key: string, value: unknown): void => {
|
|
21
|
+
calls.push([key, value]);
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
return { span: { setAttribute }, calls };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("SpanUtil.addAttributesToSpan", () => {
|
|
28
|
+
it("sets every defined attribute on the span", () => {
|
|
29
|
+
const { span, calls } = fakeSpan();
|
|
30
|
+
|
|
31
|
+
const attributes: SpanAttributes = {
|
|
32
|
+
userId: "user-1",
|
|
33
|
+
projectId: "project-1",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
SpanUtil.addAttributesToSpan({
|
|
37
|
+
span: span as never,
|
|
38
|
+
attributes,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(calls).toContainEqual(["userId", "user-1"]);
|
|
42
|
+
expect(calls).toContainEqual(["projectId", "project-1"]);
|
|
43
|
+
expect(calls).toHaveLength(2);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("skips undefined and null values (OpenTelemetry rejects them)", () => {
|
|
47
|
+
const { span, calls } = fakeSpan();
|
|
48
|
+
|
|
49
|
+
const attributes: SpanAttributes = {
|
|
50
|
+
userId: "user-1",
|
|
51
|
+
projectId: undefined,
|
|
52
|
+
// null is not part of the typed surface but can arrive at runtime.
|
|
53
|
+
monitorId: null as unknown as string,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
SpanUtil.addAttributesToSpan({
|
|
57
|
+
span: span as never,
|
|
58
|
+
attributes,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(calls).toEqual([["userId", "user-1"]]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("preserves falsy-but-defined values like empty string, 0 and false", () => {
|
|
65
|
+
const { span, calls } = fakeSpan();
|
|
66
|
+
|
|
67
|
+
const attributes: SpanAttributes = {
|
|
68
|
+
channelId: "",
|
|
69
|
+
retryCount: 0,
|
|
70
|
+
isRetry: false,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
SpanUtil.addAttributesToSpan({
|
|
74
|
+
span: span as never,
|
|
75
|
+
attributes,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(calls).toContainEqual(["channelId", ""]);
|
|
79
|
+
expect(calls).toContainEqual(["retryCount", 0]);
|
|
80
|
+
expect(calls).toContainEqual(["isRetry", false]);
|
|
81
|
+
expect(calls).toHaveLength(3);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("does nothing for an empty attribute object", () => {
|
|
85
|
+
const { span, calls } = fakeSpan();
|
|
86
|
+
|
|
87
|
+
SpanUtil.addAttributesToSpan({
|
|
88
|
+
span: span as never,
|
|
89
|
+
attributes: {},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(calls).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -139,6 +139,58 @@ describe("OneUptimeDate user timezone", () => {
|
|
|
139
139
|
expect(formatted).toContain("EDT");
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
it("names the abbreviation in effect on the formatted date, not today's", () => {
|
|
143
|
+
OneUptimeDate.setUserTimezone(NY);
|
|
144
|
+
|
|
145
|
+
/*
|
|
146
|
+
* The abbreviation used to be looked up for "now", so all summer every
|
|
147
|
+
* winter timestamp read "EDT" — and 13:00 EDT is a different instant
|
|
148
|
+
* than the 13:00 EST that was actually stored.
|
|
149
|
+
*/
|
|
150
|
+
const winter: string = OneUptimeDate.getDateAsLocalFormattedString(
|
|
151
|
+
new Date("2026-01-15T18:00:00Z"),
|
|
152
|
+
);
|
|
153
|
+
const summer: string = OneUptimeDate.getDateAsLocalFormattedString(
|
|
154
|
+
new Date("2026-07-15T18:00:00Z"),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(winter).toContain("13:00");
|
|
158
|
+
expect(winter).toContain("EST");
|
|
159
|
+
expect(winter).not.toContain("EDT");
|
|
160
|
+
|
|
161
|
+
expect(summer).toContain("14:00");
|
|
162
|
+
expect(summer).toContain("EDT");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("labels each pass of a folded fall-back hour with its own abbreviation", () => {
|
|
166
|
+
OneUptimeDate.setUserTimezone(NY);
|
|
167
|
+
|
|
168
|
+
// 01:30 local happens twice on 2026-11-01 — once EDT, once EST.
|
|
169
|
+
const firstPass: string = OneUptimeDate.getDateAsLocalFormattedString(
|
|
170
|
+
new Date("2026-11-01T05:30:00Z"),
|
|
171
|
+
);
|
|
172
|
+
const secondPass: string = OneUptimeDate.getDateAsLocalFormattedString(
|
|
173
|
+
new Date("2026-11-01T06:30:00Z"),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(firstPass).toContain("01:30");
|
|
177
|
+
expect(secondPass).toContain("01:30");
|
|
178
|
+
// Identical wall clocks; the abbreviation is what tells them apart.
|
|
179
|
+
expect(firstPass).toContain("EDT");
|
|
180
|
+
expect(secondPass).toContain("EST");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("still omits the abbreviation when only the date is shown", () => {
|
|
184
|
+
OneUptimeDate.setUserTimezone(NY);
|
|
185
|
+
|
|
186
|
+
const dateOnly: string = OneUptimeDate.getDateAsLocalFormattedString(
|
|
187
|
+
new Date("2026-01-15T18:00:00Z"),
|
|
188
|
+
true,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(dateOnly).toBe("Jan 15, 2026");
|
|
192
|
+
});
|
|
193
|
+
|
|
142
194
|
it("reads the hour and minute of an instant in the user timezone", () => {
|
|
143
195
|
OneUptimeDate.setUserTimezone(KOLKATA);
|
|
144
196
|
|
|
@@ -7,17 +7,21 @@ import {
|
|
|
7
7
|
import MonitorStep from "../../../Types/Monitor/MonitorStep";
|
|
8
8
|
import MonitorStepKubernetesMonitor from "../../../Types/Monitor/MonitorStepKubernetesMonitor";
|
|
9
9
|
import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
|
|
10
|
+
import { FilterType } from "../../../Types/Monitor/CriteriaFilter";
|
|
10
11
|
import ObjectID from "../../../Types/ObjectID";
|
|
11
12
|
|
|
12
13
|
/*
|
|
13
|
-
* These tests lock in the subtle, easy-to-regress decisions in the
|
|
14
|
-
* ratio alert templates (request utilization
|
|
14
|
+
* These tests lock in the subtle, easy-to-regress decisions in the
|
|
15
|
+
* per-series ratio alert templates (node request/usage utilization, plus
|
|
16
|
+
* the autoscaling and container-limit saturation templates):
|
|
15
17
|
*
|
|
16
18
|
* 1. Group-by uses the ClickHouse-stored `resource.`-prefixed attribute
|
|
17
19
|
* name (`resource.k8s.node.name`), not the bare `k8s.node.name`.
|
|
18
20
|
* OneUptime stamps OTel resource attributes with a `resource.` prefix
|
|
19
21
|
* at ingest, so the bare key would match nothing and collapse every
|
|
20
|
-
* node into one mislabeled series.
|
|
22
|
+
* node into one mislabeled series. The key differs per template — the
|
|
23
|
+
* HPA template groups per HPA and the pod-limit templates per pod —
|
|
24
|
+
* so each case carries its own expected key.
|
|
21
25
|
*
|
|
22
26
|
* 2. The aggregation differs by numerator shape:
|
|
23
27
|
* - Request utilization sums MANY container series per node, and both
|
|
@@ -27,9 +31,17 @@ import ObjectID from "../../../Types/ObjectID";
|
|
|
27
31
|
* (kubeletstats) and denominator (k8s_cluster) come from different
|
|
28
32
|
* receivers, so `Avg` on both sides gives the correct per-minute
|
|
29
33
|
* ratio regardless of each receiver's scrape count.
|
|
34
|
+
* - The saturation templates are all `Avg` — see the pod-memory
|
|
35
|
+
* template's note in KubernetesAlertTemplates.ts for the
|
|
36
|
+
* multi-container trade-off that choice accepts.
|
|
30
37
|
*
|
|
31
38
|
* 3. The criteria reference the FORMULA alias (the computed percentage),
|
|
32
39
|
* not a raw query alias.
|
|
40
|
+
*
|
|
41
|
+
* 4. The unhealthy and healthy criteria PARTITION the value range — no
|
|
42
|
+
* gap (a value that matches neither leaves the monitor stuck in its
|
|
43
|
+
* previous status) and no overlap (a value that matches both makes the
|
|
44
|
+
* resulting status depend on evaluation order).
|
|
33
45
|
*/
|
|
34
46
|
|
|
35
47
|
interface RatioTemplateCase {
|
|
@@ -41,6 +53,7 @@ interface RatioTemplateCase {
|
|
|
41
53
|
resultAlias: string;
|
|
42
54
|
aggregation: MetricsAggregationType;
|
|
43
55
|
threshold: number;
|
|
56
|
+
groupBy: string;
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
const RATIO_TEMPLATES: Array<RatioTemplateCase> = [
|
|
@@ -54,6 +67,7 @@ const RATIO_TEMPLATES: Array<RatioTemplateCase> = [
|
|
|
54
67
|
resultAlias: "node_cpu_request_utilization",
|
|
55
68
|
aggregation: MetricsAggregationType.Sum,
|
|
56
69
|
threshold: 90,
|
|
70
|
+
groupBy: "resource.k8s.node.name",
|
|
57
71
|
},
|
|
58
72
|
{
|
|
59
73
|
id: "k8s-node-memory-request-utilization",
|
|
@@ -64,6 +78,7 @@ const RATIO_TEMPLATES: Array<RatioTemplateCase> = [
|
|
|
64
78
|
resultAlias: "node_memory_request_utilization",
|
|
65
79
|
aggregation: MetricsAggregationType.Sum,
|
|
66
80
|
threshold: 90,
|
|
81
|
+
groupBy: "resource.k8s.node.name",
|
|
67
82
|
},
|
|
68
83
|
// Usage utilization — Avg/Avg (one series per node, cross-receiver).
|
|
69
84
|
{
|
|
@@ -75,6 +90,7 @@ const RATIO_TEMPLATES: Array<RatioTemplateCase> = [
|
|
|
75
90
|
resultAlias: "node_cpu_utilization",
|
|
76
91
|
aggregation: MetricsAggregationType.Avg,
|
|
77
92
|
threshold: 90,
|
|
93
|
+
groupBy: "resource.k8s.node.name",
|
|
78
94
|
},
|
|
79
95
|
{
|
|
80
96
|
id: "k8s-high-memory",
|
|
@@ -85,6 +101,50 @@ const RATIO_TEMPLATES: Array<RatioTemplateCase> = [
|
|
|
85
101
|
resultAlias: "node_memory_utilization",
|
|
86
102
|
aggregation: MetricsAggregationType.Avg,
|
|
87
103
|
threshold: 85,
|
|
104
|
+
groupBy: "resource.k8s.node.name",
|
|
105
|
+
},
|
|
106
|
+
/*
|
|
107
|
+
* Autoscaling / limit saturation — Avg/Avg, and NOT keyed on the node.
|
|
108
|
+
*
|
|
109
|
+
* The HPA ratio groups per HPA object; the two pod-limit ratios group
|
|
110
|
+
* per pod. Locking the group-by key here is the point of these cases:
|
|
111
|
+
* these were the first ratio templates in this file not keyed on
|
|
112
|
+
* `resource.k8s.node.name`, so a copy-paste of the node key would
|
|
113
|
+
* silently collapse every HPA (or every pod) into one mislabeled
|
|
114
|
+
* series that still renders and still alerts.
|
|
115
|
+
*/
|
|
116
|
+
{
|
|
117
|
+
id: "k8s-hpa-at-max-replicas",
|
|
118
|
+
numerator: "k8s.hpa.current_replicas",
|
|
119
|
+
denominator: "k8s.hpa.max_replicas",
|
|
120
|
+
numAlias: "current_replicas",
|
|
121
|
+
denAlias: "max_replicas",
|
|
122
|
+
resultAlias: "hpa_replica_saturation",
|
|
123
|
+
aggregation: MetricsAggregationType.Avg,
|
|
124
|
+
threshold: 90,
|
|
125
|
+
groupBy: "resource.k8s.hpa.name",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "k8s-pod-memory-limit-saturation",
|
|
129
|
+
numerator: "k8s.pod.memory.usage",
|
|
130
|
+
denominator: "k8s.container.memory_limit",
|
|
131
|
+
numAlias: "used_mem",
|
|
132
|
+
denAlias: "limit_mem",
|
|
133
|
+
resultAlias: "pod_memory_limit_saturation",
|
|
134
|
+
aggregation: MetricsAggregationType.Avg,
|
|
135
|
+
threshold: 90,
|
|
136
|
+
groupBy: "resource.k8s.pod.name",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "k8s-pod-cpu-limit-saturation",
|
|
140
|
+
numerator: "k8s.pod.cpu.utilization",
|
|
141
|
+
denominator: "k8s.container.cpu_limit",
|
|
142
|
+
numAlias: "used_cpu",
|
|
143
|
+
denAlias: "limit_cpu",
|
|
144
|
+
resultAlias: "pod_cpu_limit_saturation",
|
|
145
|
+
aggregation: MetricsAggregationType.Avg,
|
|
146
|
+
threshold: 90,
|
|
147
|
+
groupBy: "resource.k8s.pod.name",
|
|
88
148
|
},
|
|
89
149
|
];
|
|
90
150
|
|
|
@@ -108,8 +168,8 @@ function getKubernetesMonitor(step: MonitorStep): MonitorStepKubernetesMonitor {
|
|
|
108
168
|
return kubernetesMonitor;
|
|
109
169
|
}
|
|
110
170
|
|
|
111
|
-
describe("KubernetesAlertTemplates - per-
|
|
112
|
-
test("all
|
|
171
|
+
describe("KubernetesAlertTemplates - per-series ratio templates", () => {
|
|
172
|
+
test("all ratio templates are registered", () => {
|
|
113
173
|
const ids: Array<string> = getAllKubernetesAlertTemplates().map(
|
|
114
174
|
(t: KubernetesAlertTemplate) => {
|
|
115
175
|
return t.id;
|
|
@@ -121,7 +181,7 @@ describe("KubernetesAlertTemplates - per-node ratio templates", () => {
|
|
|
121
181
|
});
|
|
122
182
|
|
|
123
183
|
test.each(RATIO_TEMPLATES)(
|
|
124
|
-
"$id is a
|
|
184
|
+
"$id is a ($aggregation/$aggregation) ratio keyed on $groupBy",
|
|
125
185
|
(tc: RatioTemplateCase) => {
|
|
126
186
|
const template: KubernetesAlertTemplate | undefined =
|
|
127
187
|
getKubernetesAlertTemplateById(tc.id);
|
|
@@ -162,16 +222,19 @@ describe("KubernetesAlertTemplates - per-node ratio templates", () => {
|
|
|
162
222
|
);
|
|
163
223
|
|
|
164
224
|
/*
|
|
165
|
-
* Decision (1): group by the resource-prefixed
|
|
225
|
+
* Decision (1): group by the resource-prefixed attribute on BOTH
|
|
166
226
|
* queries so the per-series fingerprints line up for the formula join.
|
|
167
227
|
*/
|
|
168
228
|
expect(numerator.metricQueryData.groupByAttributeKeys).toEqual([
|
|
169
|
-
|
|
229
|
+
tc.groupBy,
|
|
170
230
|
]);
|
|
171
231
|
expect(denominator.metricQueryData.groupByAttributeKeys).toEqual([
|
|
172
|
-
|
|
232
|
+
tc.groupBy,
|
|
173
233
|
]);
|
|
174
234
|
|
|
235
|
+
// The `resource.` prefix is load-bearing — a bare OTel key matches nothing.
|
|
236
|
+
expect(tc.groupBy.startsWith("resource.")).toBe(true);
|
|
237
|
+
|
|
175
238
|
// Formula divides numerator by denominator and scales to a percentage.
|
|
176
239
|
expect(formulaConfigs[0].metricFormulaData.metricFormula).toBe(
|
|
177
240
|
`(${tc.numAlias} / ${tc.denAlias}) * 100`,
|
|
@@ -190,4 +253,109 @@ describe("KubernetesAlertTemplates - per-node ratio templates", () => {
|
|
|
190
253
|
expect(offlineFilters[0].value).toBe(tc.threshold);
|
|
191
254
|
},
|
|
192
255
|
);
|
|
256
|
+
|
|
257
|
+
/*
|
|
258
|
+
* Decision (4): unhealthy and healthy must be exact complements at the
|
|
259
|
+
* same threshold. Both halves are hand-written per template, so the
|
|
260
|
+
* failure mode is a strict/non-strict slip — pairing `> 90` with
|
|
261
|
+
* `< 90` leaves exactly 90 matching neither criterion (the monitor
|
|
262
|
+
* silently holds its previous status), and pairing `>= 90` with
|
|
263
|
+
* `<= 90` makes 90 match both (status depends on evaluation order).
|
|
264
|
+
*/
|
|
265
|
+
const COMPLEMENT_OF: Record<string, string> = {
|
|
266
|
+
[FilterType.GreaterThan]: FilterType.LessThanOrEqualTo,
|
|
267
|
+
[FilterType.GreaterThanOrEqualTo]: FilterType.LessThan,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
test.each(RATIO_TEMPLATES)(
|
|
271
|
+
"$id unhealthy/healthy criteria partition the range at $threshold",
|
|
272
|
+
(tc: RatioTemplateCase) => {
|
|
273
|
+
const template: KubernetesAlertTemplate | undefined =
|
|
274
|
+
getKubernetesAlertTemplateById(tc.id);
|
|
275
|
+
const step: MonitorStep = template!.getMonitorStep(buildArgs());
|
|
276
|
+
|
|
277
|
+
const instances: Array<any> = step.data?.monitorCriteria.data
|
|
278
|
+
?.monitorCriteriaInstanceArray as Array<any>;
|
|
279
|
+
const [offline, online] = instances;
|
|
280
|
+
|
|
281
|
+
const offlineFilter: any = offline.data.filters[0];
|
|
282
|
+
const onlineFilter: any = online.data.filters[0];
|
|
283
|
+
|
|
284
|
+
// Same metric, same threshold — only the comparison direction differs.
|
|
285
|
+
expect(onlineFilter.metricMonitorOptions.metricAlias).toBe(
|
|
286
|
+
tc.resultAlias,
|
|
287
|
+
);
|
|
288
|
+
expect(onlineFilter.value).toBe(tc.threshold);
|
|
289
|
+
|
|
290
|
+
expect(COMPLEMENT_OF[offlineFilter.filterType]).toBe(
|
|
291
|
+
onlineFilter.filterType,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// The unhealthy criterion is the one that opens incidents/alerts.
|
|
295
|
+
expect(offline.data.createIncidents).toBe(true);
|
|
296
|
+
expect(offline.data.createAlerts).toBe(true);
|
|
297
|
+
expect(online.data.createIncidents).toBe(false);
|
|
298
|
+
expect(online.data.createAlerts).toBe(false);
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
/*
|
|
304
|
+
* The three saturation templates exist to catch the CAUSE of the
|
|
305
|
+
* "under-resourced workload behind an autoscaler" failure — limits too
|
|
306
|
+
* low, HPA driven to its ceiling, cluster filled — rather than its
|
|
307
|
+
* downstream symptoms (node pressure, pending pods, NotReady), which the
|
|
308
|
+
* older node-side templates already cover. These assertions pin the
|
|
309
|
+
* properties that make them useful for that: they must be discoverable in
|
|
310
|
+
* the picker under a category, and severity must reflect that memory
|
|
311
|
+
* saturation ends in an OOMKill while CPU saturation only throttles.
|
|
312
|
+
*/
|
|
313
|
+
describe("KubernetesAlertTemplates - autoscaling & limit saturation", () => {
|
|
314
|
+
const SATURATION_TEMPLATES: Array<{
|
|
315
|
+
id: string;
|
|
316
|
+
category: string;
|
|
317
|
+
severity: string;
|
|
318
|
+
}> = [
|
|
319
|
+
{
|
|
320
|
+
id: "k8s-hpa-at-max-replicas",
|
|
321
|
+
category: "Workload",
|
|
322
|
+
severity: "Critical",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: "k8s-pod-memory-limit-saturation",
|
|
326
|
+
category: "Workload",
|
|
327
|
+
// Crossing a memory limit is an immediate kill, not a slowdown.
|
|
328
|
+
severity: "Critical",
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: "k8s-pod-cpu-limit-saturation",
|
|
332
|
+
category: "Workload",
|
|
333
|
+
// CFS throttling degrades latency; it never kills the container.
|
|
334
|
+
severity: "Warning",
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
test.each(SATURATION_TEMPLATES)(
|
|
339
|
+
"$id is registered as a $severity $category template",
|
|
340
|
+
(tc: { id: string; category: string; severity: string }) => {
|
|
341
|
+
const template: KubernetesAlertTemplate | undefined =
|
|
342
|
+
getKubernetesAlertTemplateById(tc.id);
|
|
343
|
+
|
|
344
|
+
expect(template).toBeDefined();
|
|
345
|
+
expect(template!.category).toBe(tc.category);
|
|
346
|
+
expect(template!.severity).toBe(tc.severity);
|
|
347
|
+
expect(template!.name.length).toBeGreaterThan(0);
|
|
348
|
+
expect(template!.description.length).toBeGreaterThan(0);
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
test("every template id is unique", () => {
|
|
353
|
+
const ids: Array<string> = getAllKubernetesAlertTemplates().map(
|
|
354
|
+
(t: KubernetesAlertTemplate) => {
|
|
355
|
+
return t.id;
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
360
|
+
});
|
|
193
361
|
});
|