@oneuptime/common 12.0.1 → 12.0.3
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/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/Host.ts +12 -4
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +39 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/EnvironmentConfig.ts +31 -1
- package/Server/Infrastructure/GlobalCache.ts +115 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +27 -45
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +24 -42
- package/Server/Services/DatabaseService.ts +198 -6
- package/Server/Services/DockerHostService.ts +20 -38
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +28 -46
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +47 -57
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +20 -38
- package/Server/Services/KubernetesClusterService.ts +18 -36
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +153 -1
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +146 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/PodmanHostService.ts +20 -38
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +24 -42
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +20 -38
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +25 -43
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- package/Server/Utils/Monitor/MonitorResource.ts +63 -36
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
- package/Server/Utils/SingleFlight.ts +83 -0
- package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
- package/Server/Utils/Telemetry/Telemetry.ts +351 -122
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
- package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
- package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +658 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
- package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +636 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
- package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
- package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
- package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
- package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
- package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
- package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
- package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
- package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
- package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
- package/Tests/Types/Decimal.test.ts +37 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
- package/Types/Dashboard/DashboardTemplates.ts +397 -0
- package/Types/Database/BigIntColumnTransformer.ts +74 -0
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Date.ts +112 -0
- package/Types/Decimal.ts +16 -4
- package/Types/Events/Recurring.ts +99 -1
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.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/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +39 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +27 -1
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -39
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -36
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +155 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -32
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -40
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -64
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.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/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -32
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -30
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +127 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MetricTypeService.js +74 -0
- package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +128 -9
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -32
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -36
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -32
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +24 -37
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +236 -61
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
- package/build/dist/Server/Utils/SingleFlight.js +76 -0
- package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
- package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
- package/build/dist/Types/Database/DatabaseProperty.js +25 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/NumericColumnValue.js +97 -0
- package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
- package/build/dist/Types/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Port.js +15 -5
- package/build/dist/Types/Port.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import DatabaseService from "../../../Server/Services/DatabaseService";
|
|
2
|
+
import Service from "../../../Models/DatabaseModels/Service";
|
|
3
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
4
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
5
|
+
import { getJestSpyOn } from "../../Spy";
|
|
6
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Pins `updateColumnsByIdIfUnlockedWithoutHooks` at the SQL level.
|
|
10
|
+
*
|
|
11
|
+
* This primitive is the structural backstop under every ingest-path throttle,
|
|
12
|
+
* and the thing that makes it a backstop is one clause: FOR UPDATE SKIP
|
|
13
|
+
* LOCKED. A single-statement UPDATE holds its row lock briefly, but "briefly"
|
|
14
|
+
* stops mattering once the arrival rate on ONE row exceeds the service rate —
|
|
15
|
+
* Postgres queues lock waiters strictly, so every waiter pins a backend for
|
|
16
|
+
* the sum of everyone ahead of it. That is what produced 1,017 active
|
|
17
|
+
* connections with 892 parked on row locks and a 3.7-hour tail, on a database
|
|
18
|
+
* that was never short of capacity.
|
|
19
|
+
*
|
|
20
|
+
* SKIP LOCKED caps the number of backends blocked on any single row at one,
|
|
21
|
+
* and it does so unconditionally — it keeps holding when Redis is down, when a
|
|
22
|
+
* fence has a bug, when someone flushes the cache. None of the throttles
|
|
23
|
+
* upstream can make that guarantee, so nothing else in the codebase can
|
|
24
|
+
* substitute for it.
|
|
25
|
+
*
|
|
26
|
+
* Every assertion here is therefore about the generated statement, not about
|
|
27
|
+
* behaviour observable from a call site. Mocking this method out (as the
|
|
28
|
+
* service-level suites do) mocks the guarantee out with it, so it has to be
|
|
29
|
+
* checked here or nowhere. If an assertion below starts failing, the fix is
|
|
30
|
+
* almost certainly in the code, not in the assertion.
|
|
31
|
+
*
|
|
32
|
+
* No Postgres, no Redis — the repository is faked and the emitted SQL and
|
|
33
|
+
* bound parameters are captured.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const SERVICE_ID: ObjectID = new ObjectID(
|
|
37
|
+
"33333333-3333-4333-8333-333333333333",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
interface CapturedQuery {
|
|
41
|
+
sql: string;
|
|
42
|
+
params: Array<unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("DatabaseService.updateColumnsByIdIfUnlockedWithoutHooks", () => {
|
|
46
|
+
let service: DatabaseService<Service>;
|
|
47
|
+
let captured: Array<CapturedQuery>;
|
|
48
|
+
let rowsToReturn: Array<unknown>;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
jest.restoreAllMocks();
|
|
52
|
+
captured = [];
|
|
53
|
+
rowsToReturn = [{ _id: SERVICE_ID.toString() }];
|
|
54
|
+
|
|
55
|
+
service = new DatabaseService<Service>(Service);
|
|
56
|
+
|
|
57
|
+
const columns: Record<string, string> = {
|
|
58
|
+
lastSeenAt: "lastSeenAt",
|
|
59
|
+
serviceVersion: "serviceVersion",
|
|
60
|
+
cloudRegion: "cloudRegion",
|
|
61
|
+
_id: "_id",
|
|
62
|
+
updatedAt: "updatedAt",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
getJestSpyOn(service, "getRepository").mockReturnValue({
|
|
66
|
+
metadata: {
|
|
67
|
+
tableName: "Service",
|
|
68
|
+
findColumnWithPropertyName: (
|
|
69
|
+
propertyName: string,
|
|
70
|
+
): { databaseName: string } | undefined => {
|
|
71
|
+
return columns[propertyName]
|
|
72
|
+
? { databaseName: columns[propertyName]! }
|
|
73
|
+
: undefined;
|
|
74
|
+
},
|
|
75
|
+
updateDateColumn: { databaseName: "updatedAt" },
|
|
76
|
+
primaryColumns: [{ databaseName: "_id" }],
|
|
77
|
+
},
|
|
78
|
+
manager: {
|
|
79
|
+
connection: {
|
|
80
|
+
driver: {
|
|
81
|
+
preparePersistentValue: (value: unknown): unknown => {
|
|
82
|
+
return value;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
query: async (
|
|
87
|
+
sql: string,
|
|
88
|
+
params: Array<unknown>,
|
|
89
|
+
): Promise<Array<unknown>> => {
|
|
90
|
+
captured.push({ sql, params });
|
|
91
|
+
return rowsToReturn;
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
} as any);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("the non-blocking guarantee", () => {
|
|
99
|
+
/*
|
|
100
|
+
* The whole point. Without SKIP LOCKED this is an ordinary blocking
|
|
101
|
+
* UPDATE and the convoy is back.
|
|
102
|
+
*/
|
|
103
|
+
it("locks the target row with FOR UPDATE SKIP LOCKED", async () => {
|
|
104
|
+
const now: Date = new Date();
|
|
105
|
+
|
|
106
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
107
|
+
id: SERVICE_ID,
|
|
108
|
+
data: { lastSeenAt: now },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(captured).toHaveLength(1);
|
|
112
|
+
expect(captured[0]!.sql).toContain("FOR UPDATE SKIP LOCKED");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* The lock must be taken by a sub-SELECT on the same table and the UPDATE
|
|
117
|
+
* driven from its result. Reaching the row any other way (a bare WHERE
|
|
118
|
+
* clause) makes the outer UPDATE take its own blocking lock and the SKIP
|
|
119
|
+
* LOCKED is decorative.
|
|
120
|
+
*/
|
|
121
|
+
it("drives the UPDATE from the locked sub-SELECT, not a bare predicate", async () => {
|
|
122
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
123
|
+
id: SERVICE_ID,
|
|
124
|
+
data: { lastSeenAt: new Date() },
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(captured[0]!.sql).toContain(
|
|
128
|
+
`WHERE "_id" IN (SELECT "_id" FROM "Service" WHERE "_id" = $2 FOR UPDATE SKIP LOCKED)`,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("issues exactly one statement — no BEGIN, no COMMIT, no extra reads", async () => {
|
|
133
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
134
|
+
id: SERVICE_ID,
|
|
135
|
+
data: { lastSeenAt: new Date(), serviceVersion: "1.2.3" },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(captured).toHaveLength(1);
|
|
139
|
+
expect(captured[0]!.sql.toUpperCase()).not.toContain("BEGIN");
|
|
140
|
+
expect(captured[0]!.sql.toUpperCase()).not.toContain("COMMIT");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
* `version = version + 1` would drag this write into a fight with the
|
|
145
|
+
* optimistic lock on a row a human may be editing, and is exactly what
|
|
146
|
+
* routing through the ORM's save() path reintroduces.
|
|
147
|
+
*/
|
|
148
|
+
it("never bumps the optimistic-lock version column", async () => {
|
|
149
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
150
|
+
id: SERVICE_ID,
|
|
151
|
+
data: { lastSeenAt: new Date() },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(captured[0]!.sql).not.toContain("version");
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("reporting whether the write landed", () => {
|
|
159
|
+
/*
|
|
160
|
+
* A skipped write and an applied write are indistinguishable without
|
|
161
|
+
* RETURNING: a bare UPDATE surfaces no rows through TypeORM's query(). If
|
|
162
|
+
* this regresses, every caller silently believes contended writes
|
|
163
|
+
* succeeded — which is how a "reliable" heartbeat stops beating with
|
|
164
|
+
* nothing in the logs.
|
|
165
|
+
*/
|
|
166
|
+
it("asks for the affected row back so the skip is observable", async () => {
|
|
167
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
168
|
+
id: SERVICE_ID,
|
|
169
|
+
data: { lastSeenAt: new Date() },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(captured[0]!.sql).toContain(`RETURNING "_id"`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("returns true when the row was updated", async () => {
|
|
176
|
+
rowsToReturn = [{ _id: SERVICE_ID.toString() }];
|
|
177
|
+
|
|
178
|
+
await expect(
|
|
179
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
180
|
+
id: SERVICE_ID,
|
|
181
|
+
data: { lastSeenAt: new Date() },
|
|
182
|
+
}),
|
|
183
|
+
).resolves.toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("returns false when the row was locked by another writer", async () => {
|
|
187
|
+
rowsToReturn = [];
|
|
188
|
+
|
|
189
|
+
await expect(
|
|
190
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
191
|
+
id: SERVICE_ID,
|
|
192
|
+
data: { lastSeenAt: new Date() },
|
|
193
|
+
}),
|
|
194
|
+
).resolves.toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("returns false when the row no longer exists", async () => {
|
|
198
|
+
rowsToReturn = [];
|
|
199
|
+
|
|
200
|
+
await expect(
|
|
201
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
202
|
+
id: SERVICE_ID,
|
|
203
|
+
data: { lastSeenAt: new Date() },
|
|
204
|
+
}),
|
|
205
|
+
).resolves.toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("returns false rather than throwing on an unexpected driver reply", async () => {
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
210
|
+
rowsToReturn = undefined as any;
|
|
211
|
+
|
|
212
|
+
await expect(
|
|
213
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
214
|
+
id: SERVICE_ID,
|
|
215
|
+
data: { lastSeenAt: new Date() },
|
|
216
|
+
}),
|
|
217
|
+
).resolves.toBe(false);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("parity with the blocking primitive", () => {
|
|
222
|
+
it("binds every value as a parameter", async () => {
|
|
223
|
+
const now: Date = new Date();
|
|
224
|
+
|
|
225
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
226
|
+
id: SERVICE_ID,
|
|
227
|
+
data: { lastSeenAt: now, serviceVersion: "1.2.3" },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(captured[0]!.params).toEqual([
|
|
231
|
+
now,
|
|
232
|
+
"1.2.3",
|
|
233
|
+
SERVICE_ID.toString(),
|
|
234
|
+
]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("refreshes updatedAt by default", async () => {
|
|
238
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
239
|
+
id: SERVICE_ID,
|
|
240
|
+
data: { lastSeenAt: new Date() },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(captured[0]!.sql).toContain(`"updatedAt" = CURRENT_TIMESTAMP`);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("leaves updatedAt alone when asked to", async () => {
|
|
247
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
248
|
+
id: SERVICE_ID,
|
|
249
|
+
data: { lastSeenAt: new Date() },
|
|
250
|
+
skipUpdateDateColumn: true,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(captured[0]!.sql).not.toContain("updatedAt");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* The clamp lives at this choke point precisely so no caller can forget
|
|
258
|
+
* it: an over-long collector-supplied attribute would otherwise fail the
|
|
259
|
+
* whole statement and take lastSeenAt down with it (issue #3006).
|
|
260
|
+
*/
|
|
261
|
+
it("clamps over-long strings to their column width", async () => {
|
|
262
|
+
const huge: string = "x".repeat(5000);
|
|
263
|
+
|
|
264
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
265
|
+
id: SERVICE_ID,
|
|
266
|
+
data: { lastSeenAt: new Date(), cloudRegion: huge },
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const bound: string = captured[0]!.params[1] as string;
|
|
270
|
+
expect(bound.length).toBeLessThan(huge.length);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("does not mutate the caller's payload when clamping", async () => {
|
|
274
|
+
const huge: string = "x".repeat(5000);
|
|
275
|
+
const data: Record<string, unknown> = {
|
|
276
|
+
lastSeenAt: new Date(),
|
|
277
|
+
cloudRegion: huge,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
await service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
281
|
+
id: SERVICE_ID,
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
283
|
+
data: data as any,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(data["cloudRegion"]).toHaveLength(5000);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("rejects an unknown column instead of generating invalid SQL", async () => {
|
|
290
|
+
await expect(
|
|
291
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
292
|
+
id: SERVICE_ID,
|
|
293
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
294
|
+
data: { notAColumn: "x" } as any,
|
|
295
|
+
}),
|
|
296
|
+
).rejects.toThrow(BadDataException);
|
|
297
|
+
|
|
298
|
+
expect(captured).toHaveLength(0);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("rejects SQL-expression values, which this bind path cannot express", async () => {
|
|
302
|
+
await expect(
|
|
303
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
304
|
+
id: SERVICE_ID,
|
|
305
|
+
data: {
|
|
306
|
+
lastSeenAt: () => {
|
|
307
|
+
return "now()";
|
|
308
|
+
},
|
|
309
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
310
|
+
} as any,
|
|
311
|
+
}),
|
|
312
|
+
).rejects.toThrow(BadDataException);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("requires an id", async () => {
|
|
316
|
+
await expect(
|
|
317
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
318
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
319
|
+
id: undefined as any,
|
|
320
|
+
data: { lastSeenAt: new Date() },
|
|
321
|
+
}),
|
|
322
|
+
).rejects.toThrow(BadDataException);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
/*
|
|
326
|
+
* An empty payload must issue nothing at all. Emitting `SET "updatedAt" =
|
|
327
|
+
* CURRENT_TIMESTAMP` alone would take a row lock to write a value nobody
|
|
328
|
+
* asked for — a pointless contender in exactly the place contention is
|
|
329
|
+
* the problem.
|
|
330
|
+
*/
|
|
331
|
+
it("issues no statement, and reports no write, for an empty payload", async () => {
|
|
332
|
+
await expect(
|
|
333
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks({
|
|
334
|
+
id: SERVICE_ID,
|
|
335
|
+
data: {},
|
|
336
|
+
}),
|
|
337
|
+
).resolves.toBe(false);
|
|
338
|
+
|
|
339
|
+
expect(captured).toHaveLength(0);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import LogDropFilterService from "../../../Server/Services/LogDropFilterService";
|
|
2
2
|
import TraceDropFilterService from "../../../Server/Services/TraceDropFilterService";
|
|
3
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
3
4
|
import LogDropFilter from "../../../Models/DatabaseModels/LogDropFilter";
|
|
4
5
|
import TraceDropFilter from "../../../Models/DatabaseModels/TraceDropFilter";
|
|
5
6
|
import LogDropFilterAction from "../../../Types/Log/LogDropFilterAction";
|
|
6
7
|
import TraceDropFilterAction from "../../../Types/Trace/TraceDropFilterAction";
|
|
8
|
+
import { coerceNumericColumnsInJSON } from "../../../Types/Database/NumericColumnValue";
|
|
7
9
|
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
8
10
|
import ObjectID from "../../../Types/ObjectID";
|
|
9
11
|
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
@@ -52,6 +54,7 @@ const VALID_QUERY: string = "severityText = 'Debug'";
|
|
|
52
54
|
const SUITES: Array<{
|
|
53
55
|
name: string;
|
|
54
56
|
service: any;
|
|
57
|
+
modelType: any;
|
|
55
58
|
makeModel: () => any;
|
|
56
59
|
sampleAction: string;
|
|
57
60
|
dropAction: string;
|
|
@@ -60,6 +63,7 @@ const SUITES: Array<{
|
|
|
60
63
|
{
|
|
61
64
|
name: "LogDropFilterService",
|
|
62
65
|
service: LogDropFilterService,
|
|
66
|
+
modelType: LogDropFilter,
|
|
63
67
|
makeModel: (): LogDropFilter => {
|
|
64
68
|
return new LogDropFilter();
|
|
65
69
|
},
|
|
@@ -70,6 +74,7 @@ const SUITES: Array<{
|
|
|
70
74
|
{
|
|
71
75
|
name: "TraceDropFilterService",
|
|
72
76
|
service: TraceDropFilterService,
|
|
77
|
+
modelType: TraceDropFilter,
|
|
73
78
|
makeModel: (): TraceDropFilter => {
|
|
74
79
|
return new TraceDropFilter();
|
|
75
80
|
},
|
|
@@ -396,3 +401,243 @@ describe.each(SUITES)(
|
|
|
396
401
|
});
|
|
397
402
|
},
|
|
398
403
|
);
|
|
404
|
+
|
|
405
|
+
/*
|
|
406
|
+
* The reported failure, driven the way the product actually drives it.
|
|
407
|
+
*
|
|
408
|
+
* The suites above hand the hook a model built in TypeScript, where a number
|
|
409
|
+
* is a number by construction. Nothing reaches the hook that way in
|
|
410
|
+
* production: BaseAPI.createItem rebuilds the model from the request body
|
|
411
|
+
* with BaseModel.fromJSON, and that body comes from a dashboard form whose
|
|
412
|
+
* "Sample Percentage" field is an `<input type="number">` — a DOM input
|
|
413
|
+
* whose value is a *string*. So the hook was handed "10", the check read
|
|
414
|
+
* `typeof samplePercentage !== "number"`, and a user who had filled the form
|
|
415
|
+
* in correctly got:
|
|
416
|
+
*
|
|
417
|
+
* HTTP 400 — Sample percentage is required when the action is "Sample".
|
|
418
|
+
* Enter the percentage of matching logs to keep, between 1 and 99.
|
|
419
|
+
*
|
|
420
|
+
* github.com/OneUptime/oneuptime/issues/3027
|
|
421
|
+
*
|
|
422
|
+
* These go through fromJSON on purpose. A test that constructs the model
|
|
423
|
+
* directly cannot fail on this bug, which is precisely why the existing
|
|
424
|
+
* suite passed while the feature was unusable.
|
|
425
|
+
*/
|
|
426
|
+
describe.each(SUITES)(
|
|
427
|
+
"$name create from a dashboard request body",
|
|
428
|
+
({ service, modelType, sampleAction, dropAction }: any) => {
|
|
429
|
+
function createByFromRequestBody(body: Record<string, unknown>): any {
|
|
430
|
+
return {
|
|
431
|
+
data: BaseModel.fromJSON(body as any, modelType),
|
|
432
|
+
props: { isRoot: true },
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
it("accepts the sample percentage as the number input sends it", async () => {
|
|
437
|
+
await expect(
|
|
438
|
+
service.onBeforeCreate(
|
|
439
|
+
createByFromRequestBody({
|
|
440
|
+
name: "Sample healthcheck logs",
|
|
441
|
+
filterQuery: VALID_QUERY,
|
|
442
|
+
action: sampleAction,
|
|
443
|
+
samplePercentage: "10",
|
|
444
|
+
sortOrder: "1",
|
|
445
|
+
isEnabled: true,
|
|
446
|
+
}),
|
|
447
|
+
),
|
|
448
|
+
).resolves.toBeDefined();
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("stores it as a number, not as the string it arrived as", async () => {
|
|
452
|
+
const createBy: any = createByFromRequestBody({
|
|
453
|
+
filterQuery: VALID_QUERY,
|
|
454
|
+
action: sampleAction,
|
|
455
|
+
samplePercentage: "10",
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
await service.onBeforeCreate(createBy);
|
|
459
|
+
|
|
460
|
+
expect(createBy.data.samplePercentage).toBe(10);
|
|
461
|
+
expect(typeof createBy.data.samplePercentage).toBe("number");
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("accepts every percentage the form allows", async () => {
|
|
465
|
+
for (const percentage of ["1", "5", "10", "50", "99"]) {
|
|
466
|
+
await expect(
|
|
467
|
+
service.onBeforeCreate(
|
|
468
|
+
createByFromRequestBody({
|
|
469
|
+
filterQuery: VALID_QUERY,
|
|
470
|
+
action: sampleAction,
|
|
471
|
+
samplePercentage: percentage,
|
|
472
|
+
}),
|
|
473
|
+
),
|
|
474
|
+
).resolves.toBeDefined();
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
/*
|
|
479
|
+
* Coercion must not become a way around the range check — "0" and "100"
|
|
480
|
+
* are still the two values that mean something the Sample action cannot
|
|
481
|
+
* express.
|
|
482
|
+
*/
|
|
483
|
+
it("still rejects an out-of-range percentage that arrives as a string", async () => {
|
|
484
|
+
for (const percentage of ["0", "100", "-1", "250"]) {
|
|
485
|
+
await expect(
|
|
486
|
+
service.onBeforeCreate(
|
|
487
|
+
createByFromRequestBody({
|
|
488
|
+
filterQuery: VALID_QUERY,
|
|
489
|
+
action: sampleAction,
|
|
490
|
+
samplePercentage: percentage,
|
|
491
|
+
}),
|
|
492
|
+
),
|
|
493
|
+
).rejects.toThrow(BadDataException);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("still rejects a sample filter whose percentage field was left blank", async () => {
|
|
498
|
+
await expect(
|
|
499
|
+
service.onBeforeCreate(
|
|
500
|
+
createByFromRequestBody({
|
|
501
|
+
filterQuery: VALID_QUERY,
|
|
502
|
+
action: sampleAction,
|
|
503
|
+
samplePercentage: "",
|
|
504
|
+
}),
|
|
505
|
+
),
|
|
506
|
+
).rejects.toThrow(BadDataException);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it("still rejects a percentage that is not a number at all", async () => {
|
|
510
|
+
await expect(
|
|
511
|
+
service.onBeforeCreate(
|
|
512
|
+
createByFromRequestBody({
|
|
513
|
+
filterQuery: VALID_QUERY,
|
|
514
|
+
action: sampleAction,
|
|
515
|
+
samplePercentage: "abc",
|
|
516
|
+
}),
|
|
517
|
+
),
|
|
518
|
+
).rejects.toThrow(BadDataException);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("accepts the drop payload, which carries no percentage at all", async () => {
|
|
522
|
+
await expect(
|
|
523
|
+
service.onBeforeCreate(
|
|
524
|
+
createByFromRequestBody({
|
|
525
|
+
name: "Drop debug",
|
|
526
|
+
filterQuery: VALID_QUERY,
|
|
527
|
+
action: dropAction,
|
|
528
|
+
sortOrder: "1",
|
|
529
|
+
isEnabled: true,
|
|
530
|
+
}),
|
|
531
|
+
),
|
|
532
|
+
).resolves.toBeDefined();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
/*
|
|
536
|
+
* The other half of the same create. `droppedCount` is ingest-owned and
|
|
537
|
+
* never present in a create body, so it has to stay unset all the way to
|
|
538
|
+
* the INSERT — where the column's DEFAULT 0 applies. A create that
|
|
539
|
+
* carried an explicit null instead is what made this a 500 rather than a
|
|
540
|
+
* 400. github.com/OneUptime/oneuptime/issues/3026
|
|
541
|
+
*/
|
|
542
|
+
it("leaves the ingest-owned counter unset for the column default", async () => {
|
|
543
|
+
const createBy: any = createByFromRequestBody({
|
|
544
|
+
name: "Drop debug",
|
|
545
|
+
filterQuery: VALID_QUERY,
|
|
546
|
+
action: dropAction,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
await service.onBeforeCreate(createBy);
|
|
550
|
+
|
|
551
|
+
expect(createBy.data.droppedCount).toBeUndefined();
|
|
552
|
+
expect(createBy.data.lastDroppedAt).toBeUndefined();
|
|
553
|
+
});
|
|
554
|
+
},
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
/*
|
|
558
|
+
* The same request body, sent as an edit rather than a create.
|
|
559
|
+
*
|
|
560
|
+
* `BaseAPI.updateItem` does not build a model — it deserializes the body
|
|
561
|
+
* into a partial entity — so it normalizes number columns itself. Without
|
|
562
|
+
* that, changing a saved filter's percentage in the dashboard failed the
|
|
563
|
+
* same way creating one did, and only on the update verb.
|
|
564
|
+
*/
|
|
565
|
+
describe.each(SUITES)(
|
|
566
|
+
"$name update from a dashboard request body",
|
|
567
|
+
({ service, makeModel, sampleAction, dropAction }: any) => {
|
|
568
|
+
afterEach(() => {
|
|
569
|
+
jest.restoreAllMocks();
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
function updateByFromRequestBody(body: Record<string, unknown>): any {
|
|
573
|
+
return {
|
|
574
|
+
query: { _id: FILTER_ID.toString() },
|
|
575
|
+
data: coerceNumericColumnsInJSON(body as any, makeModel()),
|
|
576
|
+
props: { isRoot: true },
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function mockStoredRow(row: StoredRow): void {
|
|
581
|
+
const model: any = makeModel();
|
|
582
|
+
model._id = FILTER_ID.toString();
|
|
583
|
+
model.action = row.action;
|
|
584
|
+
model.filterQuery = row.filterQuery;
|
|
585
|
+
if (row.samplePercentage !== undefined) {
|
|
586
|
+
model.samplePercentage = row.samplePercentage;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
jest
|
|
590
|
+
.spyOn(service, "findBy")
|
|
591
|
+
.mockImplementation(async (): Promise<Array<any>> => {
|
|
592
|
+
return [model];
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
it("accepts a percentage change sent as a string", async () => {
|
|
597
|
+
mockStoredRow({
|
|
598
|
+
action: sampleAction,
|
|
599
|
+
samplePercentage: 10,
|
|
600
|
+
filterQuery: VALID_QUERY,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
await expect(
|
|
604
|
+
service.onBeforeUpdate(
|
|
605
|
+
updateByFromRequestBody({ samplePercentage: "25" }),
|
|
606
|
+
),
|
|
607
|
+
).resolves.toBeDefined();
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it("accepts flipping a drop filter to sample with a string percentage", async () => {
|
|
611
|
+
mockStoredRow({
|
|
612
|
+
action: dropAction,
|
|
613
|
+
samplePercentage: undefined,
|
|
614
|
+
filterQuery: VALID_QUERY,
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
await expect(
|
|
618
|
+
service.onBeforeUpdate(
|
|
619
|
+
updateByFromRequestBody({
|
|
620
|
+
action: sampleAction,
|
|
621
|
+
samplePercentage: "5",
|
|
622
|
+
}),
|
|
623
|
+
),
|
|
624
|
+
).resolves.toBeDefined();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("still rejects an out-of-range percentage sent as a string", async () => {
|
|
628
|
+
mockStoredRow({
|
|
629
|
+
action: sampleAction,
|
|
630
|
+
samplePercentage: 10,
|
|
631
|
+
filterQuery: VALID_QUERY,
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
for (const percentage of ["0", "100"]) {
|
|
635
|
+
await expect(
|
|
636
|
+
service.onBeforeUpdate(
|
|
637
|
+
updateByFromRequestBody({ samplePercentage: percentage }),
|
|
638
|
+
),
|
|
639
|
+
).rejects.toThrow(BadDataException);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
);
|