@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
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
2
|
+
import ProjectService from "../../../Server/Services/ProjectService";
|
|
3
|
+
import ServiceService from "../../../Server/Services/ServiceService";
|
|
4
|
+
import OTelIngestService from "../../../Server/Services/OpenTelemetryIngestService";
|
|
5
|
+
import Service from "../../../Models/DatabaseModels/Service";
|
|
6
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
7
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
8
|
+
import {
|
|
9
|
+
afterEach,
|
|
10
|
+
beforeEach,
|
|
11
|
+
describe,
|
|
12
|
+
expect,
|
|
13
|
+
jest,
|
|
14
|
+
test,
|
|
15
|
+
} from "@jest/globals";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Regression suite for the Postgres CPU incident.
|
|
19
|
+
*
|
|
20
|
+
* `findOrCreateTelemetryService` resolves an OTLP resource's `service.name` to
|
|
21
|
+
* a Service row, and it was the one ungated operation on the hottest read path
|
|
22
|
+
* in the product. Three multipliers stack on it, none of which the code
|
|
23
|
+
* guarded:
|
|
24
|
+
*
|
|
25
|
+
* 1. It runs once per OTLP *resource*, not per request — the shipped
|
|
26
|
+
* host-metrics collector config produces hundreds per batch.
|
|
27
|
+
* 2. It runs again for every signal type a collector exports (metrics, logs,
|
|
28
|
+
* traces, profiles re-resolve the same resource set).
|
|
29
|
+
* 3. It runs from every ingest pod, concurrently.
|
|
30
|
+
*
|
|
31
|
+
* And the query it issued was the most expensive shape available.
|
|
32
|
+
* `QueryHelper.findWithSameText` emits `LOWER("Service"."name") = $2`, while
|
|
33
|
+
* every live index on Service is over the RAW name column — so Postgres uses
|
|
34
|
+
* only the `projectId` prefix as an index qual and then heap-fetches and
|
|
35
|
+
* filters every service in the project. Cost per call is O(services in
|
|
36
|
+
* project), not O(log n). Production sat at 100% CPU with a 100% buffer cache
|
|
37
|
+
* hit ratio and ~110 B/s of reads: pure in-memory CPU burn.
|
|
38
|
+
*
|
|
39
|
+
* What is pinned here:
|
|
40
|
+
*
|
|
41
|
+
* 1. A REPEATED RESOLUTION DOES NOT REACH POSTGRES. This is the assertion
|
|
42
|
+
* that would have prevented the incident.
|
|
43
|
+
* 2. THE CACHE KEY FOLDS CASE AND WHITESPACE exactly as the SQL predicate
|
|
44
|
+
* does. If it did not, two spellings of one service.name would occupy two
|
|
45
|
+
* entries for one row and the cache would miss what it just wrote.
|
|
46
|
+
* 3. PROJECTS ARE ISOLATED. A shared service.name across tenants must never
|
|
47
|
+
* resolve to another tenant's row.
|
|
48
|
+
* 4. EVERY path that learns a Service — cold read, create, and the
|
|
49
|
+
* create-race refetch — populates the cache. The refetch emits SQL
|
|
50
|
+
* byte-identical to the cold read, so leaving it uncached would keep a
|
|
51
|
+
* first-contact stampede hitting Postgres.
|
|
52
|
+
* 5. A CACHE OUTAGE NEVER FAILS INGEST. Telemetry must keep flowing when
|
|
53
|
+
* Redis is unavailable.
|
|
54
|
+
*
|
|
55
|
+
* Everything external is mocked — no Postgres, no Redis.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
jest.mock("../../../Server/Infrastructure/GlobalCache");
|
|
59
|
+
jest.mock("../../../Server/Services/ServiceService");
|
|
60
|
+
jest.mock("../../../Server/Services/ProjectService");
|
|
61
|
+
jest.mock("../../../Server/Services/LabelService");
|
|
62
|
+
jest.mock("../../../Server/Utils/Telemetry/EntityRegistry", () => {
|
|
63
|
+
return {
|
|
64
|
+
__esModule: true,
|
|
65
|
+
reconcileEntityRegistryThrottled: jest.fn(),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const SERVICE_RESOLUTION_NAMESPACE: string = "service-resolution";
|
|
70
|
+
|
|
71
|
+
// Stands in for Redis so the two cache layers can be exercised independently.
|
|
72
|
+
let fakeRedis: Map<string, JSONObject>;
|
|
73
|
+
|
|
74
|
+
type CacheKeyFunction = (namespace: string, key: string) => string;
|
|
75
|
+
const cacheKey: CacheKeyFunction = (namespace: string, key: string): string => {
|
|
76
|
+
return `${namespace}::${key}`;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
type ResolutionKeyFunction = (projectId: ObjectID, name: string) => string;
|
|
80
|
+
const resolutionKey: ResolutionKeyFunction = (
|
|
81
|
+
projectId: ObjectID,
|
|
82
|
+
name: string,
|
|
83
|
+
): string => {
|
|
84
|
+
return `${projectId.toString()}:${name.toLowerCase().trim()}`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type BuildServiceFunction = (overrides?: {
|
|
88
|
+
retainTelemetryDataForDays?: number | null;
|
|
89
|
+
}) => Service;
|
|
90
|
+
|
|
91
|
+
const buildService: BuildServiceFunction = (overrides?: {
|
|
92
|
+
retainTelemetryDataForDays?: number | null;
|
|
93
|
+
}): Service => {
|
|
94
|
+
const service: Service = new Service();
|
|
95
|
+
service._id = ObjectID.generate().toString();
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
overrides?.retainTelemetryDataForDays !== undefined &&
|
|
99
|
+
overrides.retainTelemetryDataForDays !== null
|
|
100
|
+
) {
|
|
101
|
+
service.retainTelemetryDataForDays = overrides.retainTelemetryDataForDays;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return service;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
describe("OTelIngestService service-resolution cache", () => {
|
|
108
|
+
let findOneBy: jest.Mock;
|
|
109
|
+
let create: jest.Mock;
|
|
110
|
+
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
fakeRedis = new Map<string, JSONObject>();
|
|
113
|
+
|
|
114
|
+
(GlobalCache.getJSONObject as unknown as jest.Mock).mockImplementation(
|
|
115
|
+
async (namespace: string, key: string): Promise<JSONObject | null> => {
|
|
116
|
+
return fakeRedis.get(cacheKey(namespace, key)) ?? null;
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
(GlobalCache.setJSON as unknown as jest.Mock).mockImplementation(
|
|
120
|
+
async (
|
|
121
|
+
namespace: string,
|
|
122
|
+
key: string,
|
|
123
|
+
value: JSONObject,
|
|
124
|
+
): Promise<void> => {
|
|
125
|
+
fakeRedis.set(cacheKey(namespace, key), value);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
findOneBy = ServiceService.findOneBy as unknown as jest.Mock;
|
|
130
|
+
create = ServiceService.create as unknown as jest.Mock;
|
|
131
|
+
|
|
132
|
+
findOneBy.mockReset();
|
|
133
|
+
create.mockReset();
|
|
134
|
+
|
|
135
|
+
(ServiceService.updateLastSeen as unknown as jest.Mock).mockResolvedValue(
|
|
136
|
+
undefined,
|
|
137
|
+
);
|
|
138
|
+
(ServiceService.attachLabels as unknown as jest.Mock).mockResolvedValue(
|
|
139
|
+
undefined,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
(ProjectService.findOneById as unknown as jest.Mock).mockImplementation(
|
|
143
|
+
async (): Promise<JSONObject> => {
|
|
144
|
+
return {
|
|
145
|
+
defaultTelemetryRetentionInDays: 15,
|
|
146
|
+
telemetryRetentionConfig: null,
|
|
147
|
+
} as unknown as JSONObject;
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
afterEach(() => {
|
|
153
|
+
jest.clearAllMocks();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* THE assertion. Hundreds of resolutions per batch collapsing to one
|
|
158
|
+
* Postgres read is the entire point of the change.
|
|
159
|
+
*/
|
|
160
|
+
test("resolves a repeated service.name without touching Postgres again", async () => {
|
|
161
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
162
|
+
const service: Service = buildService();
|
|
163
|
+
findOneBy.mockResolvedValue(service);
|
|
164
|
+
|
|
165
|
+
for (let i: number = 0; i < 50; i++) {
|
|
166
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
167
|
+
serviceName: "checkout-api",
|
|
168
|
+
projectId,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
expect(findOneBy).toHaveBeenCalledTimes(1);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("returns the same primaryEntityId from cache as from Postgres", async () => {
|
|
176
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
177
|
+
const service: Service = buildService();
|
|
178
|
+
findOneBy.mockResolvedValue(service);
|
|
179
|
+
|
|
180
|
+
const first: { primaryEntityId: ObjectID } =
|
|
181
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
182
|
+
serviceName: "checkout-api",
|
|
183
|
+
projectId,
|
|
184
|
+
});
|
|
185
|
+
const second: { primaryEntityId: ObjectID } =
|
|
186
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
187
|
+
serviceName: "checkout-api",
|
|
188
|
+
projectId,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(second.primaryEntityId.toString()).toBe(
|
|
192
|
+
first.primaryEntityId.toString(),
|
|
193
|
+
);
|
|
194
|
+
expect(second.primaryEntityId.toString()).toBe(service._id);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("preserves the service-level retention override across a cache hit", async () => {
|
|
198
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
199
|
+
findOneBy.mockResolvedValue(
|
|
200
|
+
buildService({ retainTelemetryDataForDays: 45 }),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
204
|
+
serviceName: "retention-svc",
|
|
205
|
+
projectId,
|
|
206
|
+
});
|
|
207
|
+
const cached: {
|
|
208
|
+
serviceRetentionInDays: number | null;
|
|
209
|
+
dataRententionInDays: number;
|
|
210
|
+
} = await OTelIngestService.telemetryServiceFromName({
|
|
211
|
+
serviceName: "retention-svc",
|
|
212
|
+
projectId,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Would silently fall back to the project default (15) if dropped.
|
|
216
|
+
expect(cached.serviceRetentionInDays).toBe(45);
|
|
217
|
+
expect(cached.dataRententionInDays).toBe(45);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
/*
|
|
221
|
+
* The cache key must fold exactly what LOWER()/trim folds in the predicate.
|
|
222
|
+
* Otherwise a batch could miss the entry the previous batch just wrote.
|
|
223
|
+
*/
|
|
224
|
+
test("folds case and surrounding whitespace into one cache entry", async () => {
|
|
225
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
226
|
+
findOneBy.mockResolvedValue(buildService());
|
|
227
|
+
|
|
228
|
+
for (const spelling of [
|
|
229
|
+
"Payments",
|
|
230
|
+
"payments",
|
|
231
|
+
"PAYMENTS",
|
|
232
|
+
" Payments ",
|
|
233
|
+
"\tpayments\n",
|
|
234
|
+
]) {
|
|
235
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
236
|
+
serviceName: spelling,
|
|
237
|
+
projectId,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
expect(findOneBy).toHaveBeenCalledTimes(1);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("keeps distinct service names apart", async () => {
|
|
245
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
246
|
+
findOneBy.mockResolvedValue(buildService());
|
|
247
|
+
|
|
248
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
249
|
+
serviceName: "svc-alpha",
|
|
250
|
+
projectId,
|
|
251
|
+
});
|
|
252
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
253
|
+
serviceName: "svc-beta",
|
|
254
|
+
projectId,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(findOneBy).toHaveBeenCalledTimes(2);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
* Cross-tenant isolation. Service names are not globally unique, so a
|
|
262
|
+
* project-blind key would leak one tenant's service id into another's rows.
|
|
263
|
+
*/
|
|
264
|
+
test("never shares a cache entry across projects", async () => {
|
|
265
|
+
const projectA: ObjectID = ObjectID.generate();
|
|
266
|
+
const projectB: ObjectID = ObjectID.generate();
|
|
267
|
+
const serviceA: Service = buildService();
|
|
268
|
+
const serviceB: Service = buildService();
|
|
269
|
+
|
|
270
|
+
findOneBy.mockResolvedValueOnce(serviceA).mockResolvedValueOnce(serviceB);
|
|
271
|
+
|
|
272
|
+
const first: { primaryEntityId: ObjectID } =
|
|
273
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
274
|
+
serviceName: "api",
|
|
275
|
+
projectId: projectA,
|
|
276
|
+
});
|
|
277
|
+
const second: { primaryEntityId: ObjectID } =
|
|
278
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
279
|
+
serviceName: "api",
|
|
280
|
+
projectId: projectB,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(findOneBy).toHaveBeenCalledTimes(2);
|
|
284
|
+
expect(first.primaryEntityId.toString()).toBe(serviceA._id);
|
|
285
|
+
expect(second.primaryEntityId.toString()).toBe(serviceB._id);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/*
|
|
289
|
+
* L2 is what makes the cache work across pods. A key this process has never
|
|
290
|
+
* seen must still resolve without Postgres if another pod warmed Redis.
|
|
291
|
+
*/
|
|
292
|
+
test("serves a cold in-process cache from Redis without hitting Postgres", async () => {
|
|
293
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
294
|
+
const serviceId: string = ObjectID.generate().toString();
|
|
295
|
+
|
|
296
|
+
fakeRedis.set(
|
|
297
|
+
cacheKey(
|
|
298
|
+
SERVICE_RESOLUTION_NAMESPACE,
|
|
299
|
+
resolutionKey(projectId, "warmed-by-another-pod"),
|
|
300
|
+
),
|
|
301
|
+
{
|
|
302
|
+
serviceId,
|
|
303
|
+
retainTelemetryDataForDays: 7,
|
|
304
|
+
telemetryRetentionConfig: null,
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const result: {
|
|
309
|
+
primaryEntityId: ObjectID;
|
|
310
|
+
serviceRetentionInDays: number | null;
|
|
311
|
+
} = await OTelIngestService.telemetryServiceFromName({
|
|
312
|
+
serviceName: "warmed-by-another-pod",
|
|
313
|
+
projectId,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(findOneBy).not.toHaveBeenCalled();
|
|
317
|
+
expect(result.primaryEntityId.toString()).toBe(serviceId);
|
|
318
|
+
expect(result.serviceRetentionInDays).toBe(7);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("writes through to Redis so other pods benefit", async () => {
|
|
322
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
323
|
+
const service: Service = buildService();
|
|
324
|
+
findOneBy.mockResolvedValue(service);
|
|
325
|
+
|
|
326
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
327
|
+
serviceName: "write-through",
|
|
328
|
+
projectId,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const stored: JSONObject | undefined = fakeRedis.get(
|
|
332
|
+
cacheKey(
|
|
333
|
+
SERVICE_RESOLUTION_NAMESPACE,
|
|
334
|
+
resolutionKey(projectId, "write-through"),
|
|
335
|
+
),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
expect(stored).toBeDefined();
|
|
339
|
+
expect(stored!["serviceId"]).toBe(service._id);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("treats a cached entry with no serviceId as a miss", async () => {
|
|
343
|
+
/*
|
|
344
|
+
* A malformed entry must not stamp rows with a broken primaryEntityId —
|
|
345
|
+
* re-resolve instead.
|
|
346
|
+
*/
|
|
347
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
348
|
+
const service: Service = buildService();
|
|
349
|
+
findOneBy.mockResolvedValue(service);
|
|
350
|
+
|
|
351
|
+
fakeRedis.set(
|
|
352
|
+
cacheKey(
|
|
353
|
+
SERVICE_RESOLUTION_NAMESPACE,
|
|
354
|
+
resolutionKey(projectId, "malformed"),
|
|
355
|
+
),
|
|
356
|
+
{ retainTelemetryDataForDays: 7, telemetryRetentionConfig: null },
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const result: { primaryEntityId: ObjectID } =
|
|
360
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
361
|
+
serviceName: "malformed",
|
|
362
|
+
projectId,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(findOneBy).toHaveBeenCalledTimes(1);
|
|
366
|
+
expect(result.primaryEntityId.toString()).toBe(service._id);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
/*
|
|
370
|
+
* A newly created service must be cached too, or first contact with a busy
|
|
371
|
+
* new deployment keeps re-reading Postgres for every resource in the batch.
|
|
372
|
+
*/
|
|
373
|
+
test("caches a service it just created", async () => {
|
|
374
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
375
|
+
const created: Service = buildService();
|
|
376
|
+
|
|
377
|
+
findOneBy.mockResolvedValue(null);
|
|
378
|
+
create.mockResolvedValue(created);
|
|
379
|
+
|
|
380
|
+
const first: { primaryEntityId: ObjectID } =
|
|
381
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
382
|
+
serviceName: "brand-new",
|
|
383
|
+
projectId,
|
|
384
|
+
});
|
|
385
|
+
const second: { primaryEntityId: ObjectID } =
|
|
386
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
387
|
+
serviceName: "brand-new",
|
|
388
|
+
projectId,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
expect(create).toHaveBeenCalledTimes(1);
|
|
392
|
+
expect(findOneBy).toHaveBeenCalledTimes(1);
|
|
393
|
+
expect(second.primaryEntityId.toString()).toBe(
|
|
394
|
+
first.primaryEntityId.toString(),
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
/*
|
|
399
|
+
* The create-race refetch emits SQL byte-identical to the cold read and is
|
|
400
|
+
* indistinguishable from it in pg_stat_activity. A first-contact stampede is
|
|
401
|
+
* exactly when it runs hot, so it must cache too.
|
|
402
|
+
*/
|
|
403
|
+
test("caches the winner after losing a create race", async () => {
|
|
404
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
405
|
+
const winner: Service = buildService();
|
|
406
|
+
|
|
407
|
+
findOneBy.mockResolvedValueOnce(null).mockResolvedValueOnce(winner);
|
|
408
|
+
create.mockRejectedValue(new Error("duplicate key value violates unique"));
|
|
409
|
+
|
|
410
|
+
const first: { primaryEntityId: ObjectID } =
|
|
411
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
412
|
+
serviceName: "raced",
|
|
413
|
+
projectId,
|
|
414
|
+
});
|
|
415
|
+
const second: { primaryEntityId: ObjectID } =
|
|
416
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
417
|
+
serviceName: "raced",
|
|
418
|
+
projectId,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
expect(first.primaryEntityId.toString()).toBe(winner._id);
|
|
422
|
+
expect(second.primaryEntityId.toString()).toBe(winner._id);
|
|
423
|
+
// Cold read + refetch, then nothing further.
|
|
424
|
+
expect(findOneBy).toHaveBeenCalledTimes(2);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("does not cache a failure — a later call can still resolve", async () => {
|
|
428
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
429
|
+
const service: Service = buildService();
|
|
430
|
+
|
|
431
|
+
findOneBy.mockResolvedValueOnce(null);
|
|
432
|
+
create.mockRejectedValueOnce(new Error("boom"));
|
|
433
|
+
findOneBy.mockResolvedValueOnce(null);
|
|
434
|
+
|
|
435
|
+
await expect(
|
|
436
|
+
OTelIngestService.telemetryServiceFromName({
|
|
437
|
+
serviceName: "transient",
|
|
438
|
+
projectId,
|
|
439
|
+
}),
|
|
440
|
+
).rejects.toThrow();
|
|
441
|
+
|
|
442
|
+
findOneBy.mockResolvedValue(service);
|
|
443
|
+
|
|
444
|
+
const recovered: { primaryEntityId: ObjectID } =
|
|
445
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
446
|
+
serviceName: "transient",
|
|
447
|
+
projectId,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
expect(recovered.primaryEntityId.toString()).toBe(service._id);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
/*
|
|
454
|
+
* Ingest must survive a Redis outage. Losing the cache costs CPU; failing
|
|
455
|
+
* the request drops telemetry.
|
|
456
|
+
*/
|
|
457
|
+
test("falls back to Postgres when the cache read throws", async () => {
|
|
458
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
459
|
+
const service: Service = buildService();
|
|
460
|
+
findOneBy.mockResolvedValue(service);
|
|
461
|
+
(GlobalCache.getJSONObject as unknown as jest.Mock).mockRejectedValue(
|
|
462
|
+
new Error("redis is down"),
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const result: { primaryEntityId: ObjectID } =
|
|
466
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
467
|
+
serviceName: "redis-down-read",
|
|
468
|
+
projectId,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(result.primaryEntityId.toString()).toBe(service._id);
|
|
472
|
+
expect(findOneBy).toHaveBeenCalledTimes(1);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("still serves ingest when the cache write throws", async () => {
|
|
476
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
477
|
+
const service: Service = buildService();
|
|
478
|
+
findOneBy.mockResolvedValue(service);
|
|
479
|
+
(GlobalCache.setJSON as unknown as jest.Mock).mockRejectedValue(
|
|
480
|
+
new Error("redis is down"),
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const result: { primaryEntityId: ObjectID } =
|
|
484
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
485
|
+
serviceName: "redis-down-write",
|
|
486
|
+
projectId,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
expect(result.primaryEntityId.toString()).toBe(service._id);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("still serves the L1 memo when Redis is unavailable", async () => {
|
|
493
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
494
|
+
findOneBy.mockResolvedValue(buildService());
|
|
495
|
+
(GlobalCache.getJSONObject as unknown as jest.Mock).mockRejectedValue(
|
|
496
|
+
new Error("redis is down"),
|
|
497
|
+
);
|
|
498
|
+
(GlobalCache.setJSON as unknown as jest.Mock).mockRejectedValue(
|
|
499
|
+
new Error("redis is down"),
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
503
|
+
serviceName: "l1-only",
|
|
504
|
+
projectId,
|
|
505
|
+
});
|
|
506
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
507
|
+
serviceName: "l1-only",
|
|
508
|
+
projectId,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// L1 is in-process and unaffected by the outage.
|
|
512
|
+
expect(findOneBy).toHaveBeenCalledTimes(1);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
/*
|
|
516
|
+
* The cache must not change WHICH row the cold path selects. Pins the query
|
|
517
|
+
* shape that the (still missing) LOWER(name) expression index has to serve.
|
|
518
|
+
*/
|
|
519
|
+
test("leaves the cold-path query shape untouched", async () => {
|
|
520
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
521
|
+
findOneBy.mockResolvedValue(buildService());
|
|
522
|
+
|
|
523
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
524
|
+
serviceName: "query-shape",
|
|
525
|
+
projectId,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const args: {
|
|
529
|
+
select: JSONObject;
|
|
530
|
+
sort: JSONObject;
|
|
531
|
+
props: JSONObject;
|
|
532
|
+
} = (findOneBy.mock.calls[0] as Array<unknown>)[0] as {
|
|
533
|
+
select: JSONObject;
|
|
534
|
+
sort: JSONObject;
|
|
535
|
+
props: JSONObject;
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
expect(args.select).toEqual({
|
|
539
|
+
_id: true,
|
|
540
|
+
retainTelemetryDataForDays: true,
|
|
541
|
+
telemetryRetentionConfig: true,
|
|
542
|
+
});
|
|
543
|
+
// Oldest-wins remains the tiebreak if duplicates ever exist.
|
|
544
|
+
expect(args.sort).toEqual({ createdAt: "ASC" });
|
|
545
|
+
expect(args.props).toEqual({ isRoot: true });
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("still heartbeats lastSeen on a cache hit", async () => {
|
|
549
|
+
/*
|
|
550
|
+
* The cache removes the READ, not the liveness write. A service whose
|
|
551
|
+
* telemetry is still arriving must not go stale just because its
|
|
552
|
+
* resolution was memoized.
|
|
553
|
+
*/
|
|
554
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
555
|
+
findOneBy.mockResolvedValue(buildService());
|
|
556
|
+
|
|
557
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
558
|
+
serviceName: "heartbeat",
|
|
559
|
+
projectId,
|
|
560
|
+
});
|
|
561
|
+
await OTelIngestService.telemetryServiceFromName({
|
|
562
|
+
serviceName: "heartbeat",
|
|
563
|
+
projectId,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
expect(ServiceService.updateLastSeen).toHaveBeenCalledTimes(2);
|
|
567
|
+
});
|
|
568
|
+
});
|