@oneuptime/common 12.0.2 → 12.0.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/CephCluster.ts +14 -0
- package/Models/DatabaseModels/CodeRepository.ts +14 -6
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/DockerHost.ts +14 -0
- package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
- package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/IoTFleet.ts +14 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/NetworkDevice.ts +14 -0
- package/Models/DatabaseModels/NetworkInterface.ts +18 -0
- package/Models/DatabaseModels/Project.ts +16 -2
- package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
- package/Models/DatabaseModels/Service.ts +30 -0
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Models/DatabaseModels/User.ts +30 -0
- package/Server/API/AIAgentDataAPI.ts +31 -0
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +458 -12
- package/Server/API/GitHubAPI.ts +119 -283
- package/Server/API/UserAPI.ts +263 -1
- package/Server/EnvironmentConfig.ts +38 -4
- package/Server/Infrastructure/GlobalCache.ts +152 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/AnalyticsDatabaseService.ts +72 -1
- package/Server/Services/CephClusterService.ts +26 -86
- package/Server/Services/CloudResourceService.ts +23 -83
- package/Server/Services/CodeRepositoryService.ts +105 -2
- package/Server/Services/DatabaseService.ts +296 -2
- package/Server/Services/DockerHostService.ts +19 -79
- package/Server/Services/DockerSwarmClusterService.ts +27 -88
- package/Server/Services/HostService.ts +46 -99
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTFleetService.ts +19 -79
- package/Server/Services/KubernetesClusterService.ts +17 -78
- package/Server/Services/LabelService.ts +57 -10
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +89 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/OpenTelemetryIngestService.ts +230 -26
- package/Server/Services/PodmanHostService.ts +19 -79
- package/Server/Services/ProxmoxClusterService.ts +23 -83
- package/Server/Services/RumApplicationService.ts +19 -80
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +24 -85
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Services/UserService.ts +184 -0
- package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
- package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- 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/PasswordHash.ts +306 -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/OverviewCustomFields.test.tsx +333 -0
- package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -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/AIAgentDataRepositoryToken.test.ts +381 -0
- package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
- package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
- package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
- package/Tests/Server/API/Helpers.ts +6 -1
- package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
- package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
- package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
- package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
- package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
- 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/OpenTelemetryServiceResolutionCache.test.ts +568 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
- package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
- package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -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/UserServiceFirstMasterAdminElection.test.ts +885 -0
- package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
- package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
- package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -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/PasswordHash.test.ts +555 -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/ColumnLength.test.ts +7 -1
- 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/HashedStringPerUserSalt.test.ts +476 -0
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Tests/Utils/TeamMembersByProject.test.ts +655 -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/ColumnLength.ts +9 -1
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Database/TableColumn.ts +11 -0
- package/Types/Database/UnsynchronizedIndex.ts +48 -0
- package/Types/Decimal.ts +16 -4
- package/Types/HashedString.ts +139 -4
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/UI/Components/Navbar/NavBar.tsx +12 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
- package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/TeamMembersByProject.ts +237 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- 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/DockerHost.js +16 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.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/IoTFleet.js +16 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.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/NetworkDevice.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +16 -2
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +28 -1
- package/build/dist/Models/DatabaseModels/Service.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/StatusPagePrivateUser.js +32 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- 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/Models/DatabaseModels/User.js +32 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
- package/build/dist/Server/API/AIAgentDataAPI.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 +343 -10
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +94 -167
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +187 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +34 -4
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +120 -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/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/1786018109307-AddPerUserPasswordSalt.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.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/AnalyticsDatabaseService.js +57 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -76
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -73
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
- package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +246 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -69
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -102
- 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/IoTFleetService.js +19 -69
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +42 -7
- 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 +81 -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/OpenTelemetryIngestService.js +155 -25
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -69
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -70
- 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 -75
- package/build/dist/Server/Services/ServerlessFunctionService.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/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/Services/UserService.js +165 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
- 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/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/PasswordHash.js +237 -0
- package/build/dist/Server/Utils/PasswordHash.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/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/ColumnLength.js +9 -1
- package/build/dist/Types/Database/ColumnLength.js.map +1 -1
- 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/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
- package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/HashedString.js +95 -5
- package/build/dist/Types/HashedString.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/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- 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/Navbar/NavBar.js +3 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
- package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.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/TeamMembersByProject.js +145 -0
- package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ type MockClient = {
|
|
|
18
18
|
expire: jest.Mock;
|
|
19
19
|
get: jest.Mock;
|
|
20
20
|
del: jest.Mock;
|
|
21
|
+
eval: jest.Mock;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
describe("GlobalCache.setString", () => {
|
|
@@ -29,6 +30,7 @@ describe("GlobalCache.setString", () => {
|
|
|
29
30
|
expire: jest.fn().mockResolvedValue(1),
|
|
30
31
|
get: jest.fn(),
|
|
31
32
|
del: jest.fn().mockResolvedValue(1),
|
|
33
|
+
eval: jest.fn().mockResolvedValue(1),
|
|
32
34
|
};
|
|
33
35
|
(Redis.getClient as jest.Mock).mockReturnValue(client);
|
|
34
36
|
(Redis.isConnected as jest.Mock).mockReturnValue(true);
|
|
@@ -110,6 +112,7 @@ describe("GlobalCache.deleteKey", () => {
|
|
|
110
112
|
expire: jest.fn().mockResolvedValue(1),
|
|
111
113
|
get: jest.fn(),
|
|
112
114
|
del: jest.fn().mockResolvedValue(1),
|
|
115
|
+
eval: jest.fn().mockResolvedValue(1),
|
|
113
116
|
};
|
|
114
117
|
(Redis.getClient as jest.Mock).mockReturnValue(client);
|
|
115
118
|
(Redis.isConnected as jest.Mock).mockReturnValue(true);
|
|
@@ -141,3 +144,354 @@ describe("GlobalCache.deleteKey", () => {
|
|
|
141
144
|
expect(client.del).not.toHaveBeenCalled();
|
|
142
145
|
});
|
|
143
146
|
});
|
|
147
|
+
|
|
148
|
+
/*
|
|
149
|
+
* The atomic fence primitives.
|
|
150
|
+
*
|
|
151
|
+
* These exist because `getString()` followed by `setString()` is
|
|
152
|
+
* check-then-act, and the OneUptime ingest path is precisely the workload
|
|
153
|
+
* that turns that race from theoretical into load-bearing: when thousands of
|
|
154
|
+
* concurrent jobs across ~100 worker pods resolve the same row in the same
|
|
155
|
+
* instant, every one of them reads the same miss and every one of them
|
|
156
|
+
* proceeds. A fence written to admit ONE writer per window admitted all of
|
|
157
|
+
* them, and the row-lock convoy that followed took production down (1,017
|
|
158
|
+
* active connections, 892 parked on row locks, the tail waiting 3.7 hours).
|
|
159
|
+
*
|
|
160
|
+
* The assertions below are about atomicity — that exactly one Redis command
|
|
161
|
+
* is issued, and that it carries the NX / compare-and-claim semantics. A
|
|
162
|
+
* refactor that "simplifies" either of these back into a read plus a write
|
|
163
|
+
* would keep every other test in the repo passing and reintroduce the outage.
|
|
164
|
+
*/
|
|
165
|
+
describe("GlobalCache.setStringIfNotExists", () => {
|
|
166
|
+
let client: MockClient;
|
|
167
|
+
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
client = {
|
|
170
|
+
set: jest.fn().mockResolvedValue("OK"),
|
|
171
|
+
expire: jest.fn().mockResolvedValue(1),
|
|
172
|
+
get: jest.fn(),
|
|
173
|
+
del: jest.fn().mockResolvedValue(1),
|
|
174
|
+
eval: jest.fn().mockResolvedValue(1),
|
|
175
|
+
};
|
|
176
|
+
(Redis.getClient as jest.Mock).mockReturnValue(client);
|
|
177
|
+
(Redis.isConnected as jest.Mock).mockReturnValue(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
jest.clearAllMocks();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("claims the fence with a single atomic SET ... EX ... NX", async () => {
|
|
185
|
+
const acquired: boolean = await GlobalCache.setStringIfNotExists(
|
|
186
|
+
"ns",
|
|
187
|
+
"key",
|
|
188
|
+
"1",
|
|
189
|
+
{ expiresInSeconds: 60 },
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(acquired).toBe(true);
|
|
193
|
+
expect(client.set).toHaveBeenCalledTimes(1);
|
|
194
|
+
expect(client.set).toHaveBeenCalledWith("ns-key", "1", "EX", 60, "NX");
|
|
195
|
+
// No read: a GET here would be the check-then-act race coming back.
|
|
196
|
+
expect(client.get).not.toHaveBeenCalled();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("returns false when another caller already holds the fence", async () => {
|
|
200
|
+
client.set.mockResolvedValue(null);
|
|
201
|
+
|
|
202
|
+
await expect(
|
|
203
|
+
GlobalCache.setStringIfNotExists("ns", "key", "1", {
|
|
204
|
+
expiresInSeconds: 60,
|
|
205
|
+
}),
|
|
206
|
+
).resolves.toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/*
|
|
210
|
+
* Only a literal "OK" means the key was created. Anything else — a driver
|
|
211
|
+
* change, a proxy rewriting the reply — must read as "did not acquire":
|
|
212
|
+
* losing a fence costs one redundant write, wrongly winning one costs the
|
|
213
|
+
* mutual exclusion the fence exists to provide.
|
|
214
|
+
*/
|
|
215
|
+
test("treats any non-OK reply as not acquired", async () => {
|
|
216
|
+
client.set.mockResolvedValue("QUEUED");
|
|
217
|
+
|
|
218
|
+
await expect(
|
|
219
|
+
GlobalCache.setStringIfNotExists("ns", "key", "1"),
|
|
220
|
+
).resolves.toBe(false);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("throws when the cache is not connected", async () => {
|
|
224
|
+
(Redis.isConnected as jest.Mock).mockReturnValue(false);
|
|
225
|
+
|
|
226
|
+
await expect(
|
|
227
|
+
GlobalCache.setStringIfNotExists("ns", "key", "1"),
|
|
228
|
+
).rejects.toThrow(DatabaseNotConnectedException);
|
|
229
|
+
expect(client.set).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("defaults the TTL to 30 days when no option is passed", async () => {
|
|
233
|
+
await GlobalCache.setStringIfNotExists("ns", "key", "1");
|
|
234
|
+
|
|
235
|
+
expect(client.set).toHaveBeenCalledWith(
|
|
236
|
+
"ns-key",
|
|
237
|
+
"1",
|
|
238
|
+
"EX",
|
|
239
|
+
OneUptimeDate.getSecondsInDays(30),
|
|
240
|
+
"NX",
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("GlobalCache.setStringIfChanged", () => {
|
|
246
|
+
let client: MockClient;
|
|
247
|
+
|
|
248
|
+
beforeEach(() => {
|
|
249
|
+
client = {
|
|
250
|
+
set: jest.fn().mockResolvedValue("OK"),
|
|
251
|
+
expire: jest.fn().mockResolvedValue(1),
|
|
252
|
+
get: jest.fn(),
|
|
253
|
+
del: jest.fn().mockResolvedValue(1),
|
|
254
|
+
eval: jest.fn().mockResolvedValue(1),
|
|
255
|
+
};
|
|
256
|
+
(Redis.getClient as jest.Mock).mockReturnValue(client);
|
|
257
|
+
(Redis.isConnected as jest.Mock).mockReturnValue(true);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
afterEach(() => {
|
|
261
|
+
jest.clearAllMocks();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/*
|
|
265
|
+
* Plain NX is the wrong primitive for a fingerprint throttle: it fails
|
|
266
|
+
* whenever the key exists, including when it holds a STALE fingerprint, so
|
|
267
|
+
* a genuinely changed payload (a new service.version after a deploy) would
|
|
268
|
+
* be suppressed for the whole window and never persisted at all.
|
|
269
|
+
*/
|
|
270
|
+
test("claims the window in one atomic EVAL, with no separate read", async () => {
|
|
271
|
+
const claimed: boolean = await GlobalCache.setStringIfChanged(
|
|
272
|
+
"ns",
|
|
273
|
+
"key",
|
|
274
|
+
"fingerprint",
|
|
275
|
+
{ expiresInSeconds: 300 },
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(claimed).toBe(true);
|
|
279
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
280
|
+
expect(client.get).not.toHaveBeenCalled();
|
|
281
|
+
expect(client.set).not.toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("returns false when the stored value already matches", async () => {
|
|
285
|
+
client.eval.mockResolvedValue(0);
|
|
286
|
+
|
|
287
|
+
await expect(
|
|
288
|
+
GlobalCache.setStringIfChanged("ns", "key", "fingerprint"),
|
|
289
|
+
).resolves.toBe(false);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
/*
|
|
293
|
+
* The key must travel as KEYS[1], not be interpolated into the script body
|
|
294
|
+
* — Redis Cluster routes a script by its declared keys, and an inlined key
|
|
295
|
+
* makes the script run on an arbitrary node.
|
|
296
|
+
*/
|
|
297
|
+
test("declares exactly one key, passed as KEYS[1]", async () => {
|
|
298
|
+
await GlobalCache.setStringIfChanged("ns", "key", "fingerprint", {
|
|
299
|
+
expiresInSeconds: 300,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const args: Array<unknown> = client.eval.mock.calls[0] as Array<unknown>;
|
|
303
|
+
const script: string = args[0] as string;
|
|
304
|
+
|
|
305
|
+
expect(args[1]).toBe(1);
|
|
306
|
+
expect(args[2]).toBe("ns-key");
|
|
307
|
+
expect(args[3]).toBe("fingerprint");
|
|
308
|
+
expect(args[4]).toBe("300");
|
|
309
|
+
expect(script).toContain("KEYS[1]");
|
|
310
|
+
expect(script).not.toContain("ns-key");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("throws when the cache is not connected", async () => {
|
|
314
|
+
(Redis.isConnected as jest.Mock).mockReturnValue(false);
|
|
315
|
+
|
|
316
|
+
await expect(
|
|
317
|
+
GlobalCache.setStringIfChanged("ns", "key", "fingerprint"),
|
|
318
|
+
).rejects.toThrow(DatabaseNotConnectedException);
|
|
319
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
/*
|
|
324
|
+
* A fixed TTL lets a fleet-wide event — a deploy, a scale-up, a Redis restart
|
|
325
|
+
* — align every row's throttle window. They then all expire in the same
|
|
326
|
+
* second and the herd re-forms on a fixed period, which is the convoy back
|
|
327
|
+
* again on a timer. Jitter breaks the alignment permanently: windows drift
|
|
328
|
+
* apart after the first expiry and stay apart.
|
|
329
|
+
*/
|
|
330
|
+
/*
|
|
331
|
+
* deleteKeyIfValue is the release half of a lease taken with
|
|
332
|
+
* setStringIfNotExists (see InstanceHealthLock). The comparison is the whole
|
|
333
|
+
* point: a plain DEL lets a holder that overran its TTL delete the lease a
|
|
334
|
+
* DIFFERENT worker has since legitimately acquired, which hands the same lease
|
|
335
|
+
* to a third worker and defeats the mutual exclusion entirely. These tests pin
|
|
336
|
+
* the atomicity and the compare, because neither is visible at the call site.
|
|
337
|
+
*/
|
|
338
|
+
describe("GlobalCache.deleteKeyIfValue", () => {
|
|
339
|
+
let client: MockClient;
|
|
340
|
+
|
|
341
|
+
beforeEach(() => {
|
|
342
|
+
client = {
|
|
343
|
+
set: jest.fn().mockResolvedValue("OK"),
|
|
344
|
+
expire: jest.fn().mockResolvedValue(1),
|
|
345
|
+
get: jest.fn(),
|
|
346
|
+
del: jest.fn().mockResolvedValue(1),
|
|
347
|
+
eval: jest.fn().mockResolvedValue(1),
|
|
348
|
+
};
|
|
349
|
+
(Redis.getClient as jest.Mock).mockReturnValue(client);
|
|
350
|
+
(Redis.isConnected as jest.Mock).mockReturnValue(true);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
afterEach(() => {
|
|
354
|
+
jest.clearAllMocks();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("compares and deletes in ONE eval — never GET then DEL", async () => {
|
|
358
|
+
await GlobalCache.deleteKeyIfValue("ns", "key", "token-a");
|
|
359
|
+
|
|
360
|
+
expect(client.eval).toHaveBeenCalledTimes(1);
|
|
361
|
+
// A separate GET/DEL pair would reintroduce the race the script removes.
|
|
362
|
+
expect(client.get).not.toHaveBeenCalled();
|
|
363
|
+
expect(client.del).not.toHaveBeenCalled();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("passes the key as KEYS[1] so it stays correct on Redis Cluster", async () => {
|
|
367
|
+
await GlobalCache.deleteKeyIfValue("ns", "key", "token-a");
|
|
368
|
+
|
|
369
|
+
const call: Array<unknown> = client.eval.mock.calls[0] as Array<unknown>;
|
|
370
|
+
|
|
371
|
+
// numkeys = 1, then the key itself, then the holder token as ARGV[1].
|
|
372
|
+
expect(call[1]).toBe(1);
|
|
373
|
+
expect(call[2]).toBe("ns-key");
|
|
374
|
+
expect(call[3]).toBe("token-a");
|
|
375
|
+
|
|
376
|
+
const script: string = call[0] as string;
|
|
377
|
+
expect(script).toContain("KEYS[1]");
|
|
378
|
+
expect(script).toContain("ARGV[1]");
|
|
379
|
+
// The key must never be interpolated into the script body.
|
|
380
|
+
expect(script).not.toContain("ns-key");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("returns true when this holder's own value was deleted", async () => {
|
|
384
|
+
client.eval.mockResolvedValue(1);
|
|
385
|
+
|
|
386
|
+
await expect(
|
|
387
|
+
GlobalCache.deleteKeyIfValue("ns", "key", "token-a"),
|
|
388
|
+
).resolves.toBe(true);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("returns false when the key holds a DIFFERENT holder's token", async () => {
|
|
392
|
+
// Redis DEL never ran because the GET comparison failed.
|
|
393
|
+
client.eval.mockResolvedValue(0);
|
|
394
|
+
|
|
395
|
+
await expect(
|
|
396
|
+
GlobalCache.deleteKeyIfValue("ns", "key", "token-a"),
|
|
397
|
+
).resolves.toBe(false);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("returns false when the key has already expired", async () => {
|
|
401
|
+
client.eval.mockResolvedValue(0);
|
|
402
|
+
|
|
403
|
+
await expect(
|
|
404
|
+
GlobalCache.deleteKeyIfValue("ns", "key", "token-a"),
|
|
405
|
+
).resolves.toBe(false);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("treats any non-1 reply as 'did not delete'", async () => {
|
|
409
|
+
/*
|
|
410
|
+
* Losing a release is safe (the lease still expires on its own); wrongly
|
|
411
|
+
* reporting a release is not. Anything unexpected must fail closed.
|
|
412
|
+
*/
|
|
413
|
+
for (const reply of [null, undefined, "1", 2, "OK", {}]) {
|
|
414
|
+
client.eval.mockResolvedValue(reply);
|
|
415
|
+
|
|
416
|
+
await expect(
|
|
417
|
+
GlobalCache.deleteKeyIfValue("ns", "key", "token-a"),
|
|
418
|
+
).resolves.toBe(false);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("throws when the cache is not connected", async () => {
|
|
423
|
+
(Redis.isConnected as jest.Mock).mockReturnValue(false);
|
|
424
|
+
|
|
425
|
+
await expect(
|
|
426
|
+
GlobalCache.deleteKeyIfValue("ns", "key", "token-a"),
|
|
427
|
+
).rejects.toThrow(DatabaseNotConnectedException);
|
|
428
|
+
expect(client.eval).not.toHaveBeenCalled();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("throws when there is no client at all", async () => {
|
|
432
|
+
(Redis.getClient as jest.Mock).mockReturnValue(null);
|
|
433
|
+
|
|
434
|
+
await expect(
|
|
435
|
+
GlobalCache.deleteKeyIfValue("ns", "key", "token-a"),
|
|
436
|
+
).rejects.toThrow(DatabaseNotConnectedException);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("namespaces the key exactly as the setters do", async () => {
|
|
440
|
+
/*
|
|
441
|
+
* Release must target the same key acquire created. If the two ever
|
|
442
|
+
* disagreed the lease would be unreleasable and every tick would block
|
|
443
|
+
* for a full TTL.
|
|
444
|
+
*/
|
|
445
|
+
await GlobalCache.setStringIfNotExists("instance-health", "pg", "tok", {
|
|
446
|
+
expiresInSeconds: 60,
|
|
447
|
+
});
|
|
448
|
+
await GlobalCache.deleteKeyIfValue("instance-health", "pg", "tok");
|
|
449
|
+
|
|
450
|
+
const setKey: unknown = (client.set.mock.calls[0] as Array<unknown>)[0];
|
|
451
|
+
const evalKey: unknown = (client.eval.mock.calls[0] as Array<unknown>)[2];
|
|
452
|
+
|
|
453
|
+
expect(setKey).toBe("instance-health-pg");
|
|
454
|
+
expect(evalKey).toBe(setKey);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
describe("GlobalCache.withJitter", () => {
|
|
459
|
+
test("never returns less than the requested TTL", () => {
|
|
460
|
+
for (let i: number = 0; i < 500; i++) {
|
|
461
|
+
expect(GlobalCache.withJitter(60)).toBeGreaterThanOrEqual(60);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
/*
|
|
466
|
+
* The ceiling is load-bearing. Every markDisconnected* sweep runs at 15
|
|
467
|
+
* minutes against a 5-minute fence; +25% takes that to 6.25 minutes, still
|
|
468
|
+
* 2.4x clear. Widening this without re-checking those thresholds would flap
|
|
469
|
+
* healthy resources between connected and disconnected.
|
|
470
|
+
*/
|
|
471
|
+
test("never exceeds the requested TTL by more than 25%", () => {
|
|
472
|
+
for (let i: number = 0; i < 500; i++) {
|
|
473
|
+
expect(GlobalCache.withJitter(300)).toBeLessThanOrEqual(375);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("actually spreads values rather than returning a constant", () => {
|
|
478
|
+
const seen: Set<number> = new Set<number>();
|
|
479
|
+
|
|
480
|
+
for (let i: number = 0; i < 200; i++) {
|
|
481
|
+
seen.add(GlobalCache.withJitter(600));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
expect(seen.size).toBeGreaterThan(1);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("returns integers — Redis EX rejects fractional seconds", () => {
|
|
488
|
+
for (let i: number = 0; i < 100; i++) {
|
|
489
|
+
expect(Number.isInteger(GlobalCache.withJitter(37))).toBe(true);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("passes through non-positive TTLs unchanged", () => {
|
|
494
|
+
expect(GlobalCache.withJitter(0)).toBe(0);
|
|
495
|
+
expect(GlobalCache.withJitter(-1)).toBe(-1);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The Postgres connection deadlines.
|
|
5
|
+
*
|
|
6
|
+
* `lock_timeout` was missing entirely, and its absence is what let the row-lock
|
|
7
|
+
* convoy run for hours. It is a distinct thing from `statement_timeout`: the
|
|
8
|
+
* latter caps how long a statement RUNS, the former caps how long it WAITS for
|
|
9
|
+
* a lock. Without it, contention on a hot row degrades into a strictly-ordered
|
|
10
|
+
* queue in which every waiter pins a backend for the sum of everyone ahead of
|
|
11
|
+
* it — 892 connections parked on locks with a 3.7-hour tail, on a database that
|
|
12
|
+
* was never short of capacity.
|
|
13
|
+
*
|
|
14
|
+
* Two subtleties this suite pins, both of which are easy to get wrong and
|
|
15
|
+
* silent when wrong:
|
|
16
|
+
*
|
|
17
|
+
* 1. The MIGRATION path must be exempt. App/Migrate.ts loads these same
|
|
18
|
+
* options and connects directly to the backend, where startup parameters
|
|
19
|
+
* really do apply. A 3s lock_timeout there would abort ACCESS EXCLUSIVE
|
|
20
|
+
* DDL against any table with live traffic. Only the two migrations that
|
|
21
|
+
* set their own `SET LOCAL lock_timeout` expect to fail that way.
|
|
22
|
+
*
|
|
23
|
+
* 2. The deadlines must be ORDERED so the server wins. The client-side
|
|
24
|
+
* `query_timeout` does not cancel anything — it abandons the query while
|
|
25
|
+
* the backend keeps running and keeps its place in the lock queue. That is
|
|
26
|
+
* how killed and scaled-down workers left orphaned statements holding the
|
|
27
|
+
* queue through pgbouncer. It used to be set equal to statement_timeout,
|
|
28
|
+
* and since the client timer starts before the packet reaches the backend,
|
|
29
|
+
* the client always won by a round trip and the server-side timeout never
|
|
30
|
+
* fired at all.
|
|
31
|
+
*
|
|
32
|
+
* The module reads env at import time, so each case re-imports it in isolation.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
interface DataSourceExtra {
|
|
36
|
+
lock_timeout?: number;
|
|
37
|
+
statement_timeout?: number;
|
|
38
|
+
query_timeout?: number;
|
|
39
|
+
idle_in_transaction_session_timeout?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadExtra(env: Record<string, string | undefined>): DataSourceExtra {
|
|
43
|
+
let extra: DataSourceExtra = {};
|
|
44
|
+
|
|
45
|
+
jest.isolateModules(() => {
|
|
46
|
+
const previous: Record<string, string | undefined> = {};
|
|
47
|
+
|
|
48
|
+
for (const [key, value] of Object.entries(env)) {
|
|
49
|
+
previous[key] = process.env[key];
|
|
50
|
+
if (value === undefined) {
|
|
51
|
+
delete process.env[key];
|
|
52
|
+
} else {
|
|
53
|
+
process.env[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
59
|
+
const options: {
|
|
60
|
+
default: { extra: DataSourceExtra };
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
|
+
} = require("../../../Server/Infrastructure/Postgres/DataSourceOptions");
|
|
63
|
+
/* eslint-enable @typescript-eslint/no-var-requires */
|
|
64
|
+
|
|
65
|
+
extra = options.default.extra;
|
|
66
|
+
} finally {
|
|
67
|
+
for (const [key, value] of Object.entries(previous)) {
|
|
68
|
+
if (value === undefined) {
|
|
69
|
+
delete process.env[key];
|
|
70
|
+
} else {
|
|
71
|
+
process.env[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return extra;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
describe("Postgres connection deadlines", () => {
|
|
81
|
+
describe("lock_timeout", () => {
|
|
82
|
+
test("is applied on runtime pods", () => {
|
|
83
|
+
const extra: DataSourceExtra = loadExtra({
|
|
84
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
85
|
+
DATABASE_LOCK_TIMEOUT_MS: undefined,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(extra.lock_timeout).toBe(3000);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("is configurable", () => {
|
|
92
|
+
const extra: DataSourceExtra = loadExtra({
|
|
93
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
94
|
+
DATABASE_LOCK_TIMEOUT_MS: "1500",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(extra.lock_timeout).toBe(1500);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* The exemption is load-bearing, not a nicety. A migration that cannot
|
|
102
|
+
* wait for a lock cannot take one on a busy table at all.
|
|
103
|
+
*/
|
|
104
|
+
test("is NOT applied when this process runs migrations", () => {
|
|
105
|
+
const extra: DataSourceExtra = loadExtra({
|
|
106
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "true",
|
|
107
|
+
DATABASE_LOCK_TIMEOUT_MS: undefined,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(extra.lock_timeout).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("can be disabled outright with 0", () => {
|
|
114
|
+
const extra: DataSourceExtra = loadExtra({
|
|
115
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
116
|
+
DATABASE_LOCK_TIMEOUT_MS: "0",
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(extra.lock_timeout).toBeUndefined();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("deadline ordering", () => {
|
|
124
|
+
/*
|
|
125
|
+
* lock_timeout < statement_timeout, so a lock wait surfaces as
|
|
126
|
+
* `lock_not_available` rather than a generic statement timeout. The two
|
|
127
|
+
* want very different handling: one is contention worth retrying, the
|
|
128
|
+
* other is a query that needs fixing.
|
|
129
|
+
*/
|
|
130
|
+
test("a lock wait gives up well before the statement does", () => {
|
|
131
|
+
const extra: DataSourceExtra = loadExtra({
|
|
132
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(extra.lock_timeout!).toBeLessThan(extra.statement_timeout!);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* statement_timeout < query_timeout, so the SERVER cancels first. If the
|
|
140
|
+
* client wins, the statement is only abandoned — the backend keeps
|
|
141
|
+
* running, keeps its lock, and keeps its place in the queue.
|
|
142
|
+
*/
|
|
143
|
+
test("the server cancels before the client gives up", () => {
|
|
144
|
+
const extra: DataSourceExtra = loadExtra({
|
|
145
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(extra.statement_timeout!).toBeLessThan(extra.query_timeout!);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("an explicitly configured query_timeout is still honoured", () => {
|
|
152
|
+
const extra: DataSourceExtra = loadExtra({
|
|
153
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
154
|
+
DATABASE_QUERY_TIMEOUT_MS: "45000",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(extra.query_timeout).toBe(45000);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("the default query_timeout tracks a raised statement_timeout", () => {
|
|
161
|
+
const extra: DataSourceExtra = loadExtra({
|
|
162
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
163
|
+
DATABASE_STATEMENT_TIMEOUT_MS: "60000",
|
|
164
|
+
DATABASE_QUERY_TIMEOUT_MS: undefined,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(extra.statement_timeout).toBe(60000);
|
|
168
|
+
expect(extra.query_timeout!).toBeGreaterThan(60000);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("existing deadlines are unchanged", () => {
|
|
173
|
+
test("idle-in-transaction is still bounded", () => {
|
|
174
|
+
const extra: DataSourceExtra = loadExtra({
|
|
175
|
+
RUN_DATABASE_MIGRATIONS_ON_BOOT: "false",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(extra.idle_in_transaction_session_timeout).toBe(60000);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|