@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,598 @@
|
|
|
1
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
2
|
+
import MetricTypeService from "../../../Server/Services/MetricTypeService";
|
|
3
|
+
import TelemetryUtil from "../../../Server/Utils/Telemetry/Telemetry";
|
|
4
|
+
import MetricType from "../../../Models/DatabaseModels/MetricType";
|
|
5
|
+
import Service from "../../../Models/DatabaseModels/Service";
|
|
6
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
7
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
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 metric-catalog writer on the ingest hot path.
|
|
19
|
+
*
|
|
20
|
+
* `indexMetricNameServiceNameMap` reconciles one MetricType row per (project,
|
|
21
|
+
* metric name). It runs once per distinct metric name per batch, and it was
|
|
22
|
+
* the only per-batch Postgres writer in the pipeline with no fence, no cache
|
|
23
|
+
* and no dedup of any kind. Two independent defects made it write on EVERY
|
|
24
|
+
* batch, forever:
|
|
25
|
+
*
|
|
26
|
+
* 1. THE DIFF WAS STRUCTURALLY UNSATISFIABLE. Rows are created with
|
|
27
|
+
* `description || ""`, so the stored value is `""` when OTLP omits the
|
|
28
|
+
* field — but the incoming value for that same case is `undefined`,
|
|
29
|
+
* because protobuf omits unset scalars. `"" !== undefined` is true, so
|
|
30
|
+
* "has anything changed?" answered YES on every batch for every metric
|
|
31
|
+
* without a description or unit. The counter-semantics fields right below
|
|
32
|
+
* already had the correct `!== undefined` guard; description and unit were
|
|
33
|
+
* simply left behind.
|
|
34
|
+
*
|
|
35
|
+
* 2. THE WRITE SHIPPED THE WHOLE RELATION. `services` is an EntityArray, and
|
|
36
|
+
* its mere presence as a key routes DatabaseService to
|
|
37
|
+
* `getRepository().save()` — a real BEGIN/COMMIT that reloads the entity,
|
|
38
|
+
* bumps `version`, and DELETEs then re-INSERTs junction rows, holding the
|
|
39
|
+
* row's write lock across every round trip. It was the only multi-
|
|
40
|
+
* round-trip lock hold on the ingest path.
|
|
41
|
+
*
|
|
42
|
+
* Defect 2 was also silently LOSING DATA: save() deletes any association not
|
|
43
|
+
* present in the array it is given, and each worker only knows the services in
|
|
44
|
+
* its own batch. Two workers therefore deleted each other's associations and
|
|
45
|
+
* re-inserted them next batch — permanent churn, with real service-to-metric
|
|
46
|
+
* links missing from the UI in between.
|
|
47
|
+
*
|
|
48
|
+
* Everything external is mocked — no Postgres, no Redis.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const PROJECT_ID: ObjectID = ObjectID.generate();
|
|
52
|
+
const METRIC_TYPE_ID: ObjectID = ObjectID.generate();
|
|
53
|
+
const SERVICE_A: ObjectID = ObjectID.generate();
|
|
54
|
+
const SERVICE_B: ObjectID = ObjectID.generate();
|
|
55
|
+
|
|
56
|
+
type ScalarUpdate = { id: ObjectID; data: Record<string, unknown> };
|
|
57
|
+
type AttachCall = { metricTypeId: ObjectID; serviceIds: Array<ObjectID> };
|
|
58
|
+
|
|
59
|
+
let cache: Map<string, string>;
|
|
60
|
+
let scalarUpdates: Array<ScalarUpdate>;
|
|
61
|
+
let attachCalls: Array<AttachCall>;
|
|
62
|
+
let creates: Array<MetricType>;
|
|
63
|
+
let saveUpdates: Array<unknown>;
|
|
64
|
+
let existingRow: MetricType | null;
|
|
65
|
+
|
|
66
|
+
/** A stored row as findOneBy would return it. */
|
|
67
|
+
function storedRow(overrides?: Partial<MetricType>): MetricType {
|
|
68
|
+
const row: MetricType = new MetricType();
|
|
69
|
+
row.id = METRIC_TYPE_ID;
|
|
70
|
+
row._id = METRIC_TYPE_ID.toString();
|
|
71
|
+
row.name = "http.server.duration";
|
|
72
|
+
// Created via `description || ""` — this is the value that is really stored.
|
|
73
|
+
row.description = "";
|
|
74
|
+
row.unit = "";
|
|
75
|
+
row.services = [];
|
|
76
|
+
return Object.assign(row, overrides || {});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The batch's view of a metric, as the ingest pipeline builds it. */
|
|
80
|
+
function incoming(overrides?: Partial<MetricType>): MetricType {
|
|
81
|
+
const metric: MetricType = new MetricType();
|
|
82
|
+
metric.services = [];
|
|
83
|
+
return Object.assign(metric, overrides || {});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function serviceRef(id: ObjectID): Service {
|
|
87
|
+
const service: Service = new Service();
|
|
88
|
+
service.id = id;
|
|
89
|
+
return service;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function batch(metric: MetricType): Dictionary<MetricType> {
|
|
93
|
+
return { "http.server.duration": metric };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
cache = new Map<string, string>();
|
|
98
|
+
scalarUpdates = [];
|
|
99
|
+
attachCalls = [];
|
|
100
|
+
creates = [];
|
|
101
|
+
saveUpdates = [];
|
|
102
|
+
existingRow = storedRow();
|
|
103
|
+
|
|
104
|
+
jest
|
|
105
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
106
|
+
.mockImplementation(
|
|
107
|
+
async (namespace: string, key: string, value: string) => {
|
|
108
|
+
const full: string = `${namespace}:${key}`;
|
|
109
|
+
if (cache.get(full) === value) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
cache.set(full, value);
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
jest
|
|
118
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
119
|
+
.mockImplementation(async (namespace: string, key: string) => {
|
|
120
|
+
cache.delete(`${namespace}:${key}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
jest.spyOn(MetricTypeService, "findOneBy").mockImplementation(async () => {
|
|
124
|
+
return existingRow;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
jest
|
|
128
|
+
.spyOn(MetricTypeService, "updateColumnsByIdWithoutHooks")
|
|
129
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
130
|
+
scalarUpdates.push({
|
|
131
|
+
id: input.id,
|
|
132
|
+
data: { ...(input.data as Record<string, unknown>) },
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
jest
|
|
137
|
+
.spyOn(MetricTypeService, "attachServices")
|
|
138
|
+
.mockImplementation(
|
|
139
|
+
async (input: {
|
|
140
|
+
metricTypeId: ObjectID;
|
|
141
|
+
serviceIds: Array<ObjectID>;
|
|
142
|
+
}) => {
|
|
143
|
+
attachCalls.push({
|
|
144
|
+
metricTypeId: input.metricTypeId,
|
|
145
|
+
serviceIds: [...input.serviceIds],
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
jest
|
|
151
|
+
.spyOn(MetricTypeService, "create")
|
|
152
|
+
.mockImplementation(async (input: { data: MetricType }) => {
|
|
153
|
+
creates.push(input.data);
|
|
154
|
+
return input.data;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* The transactional relation-writing path. Nothing on the ingest path may
|
|
159
|
+
* reach it — if this records a call, the multi-round-trip lock hold is back.
|
|
160
|
+
*/
|
|
161
|
+
jest
|
|
162
|
+
.spyOn(MetricTypeService, "updateOneById")
|
|
163
|
+
.mockImplementation(async (input: unknown) => {
|
|
164
|
+
saveUpdates.push(input);
|
|
165
|
+
return 1;
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
afterEach(() => {
|
|
170
|
+
jest.restoreAllMocks();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("the diff converges instead of re-firing every batch", () => {
|
|
174
|
+
/*
|
|
175
|
+
* The headline regression. A metric with no description and no unit — the
|
|
176
|
+
* overwhelmingly common case — used to report a change on every single
|
|
177
|
+
* batch, forever.
|
|
178
|
+
*/
|
|
179
|
+
test("a metric with no description or unit writes nothing", async () => {
|
|
180
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
181
|
+
projectId: PROJECT_ID,
|
|
182
|
+
metricNameServiceNameMap: batch(incoming()),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(scalarUpdates).toHaveLength(0);
|
|
186
|
+
expect(attachCalls).toHaveLength(0);
|
|
187
|
+
expect(saveUpdates).toHaveLength(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
/*
|
|
191
|
+
* The specific comparison that was wrong: stored `""` against incoming
|
|
192
|
+
* `undefined`. Checked with the fence disabled so it is the DIFF being
|
|
193
|
+
* tested, not the throttle in front of it.
|
|
194
|
+
*/
|
|
195
|
+
test("stored empty-string against incoming undefined is not a change", async () => {
|
|
196
|
+
jest.spyOn(GlobalCache, "setStringIfChanged").mockResolvedValue(true);
|
|
197
|
+
|
|
198
|
+
for (let i: number = 0; i < 5; i++) {
|
|
199
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
200
|
+
projectId: PROJECT_ID,
|
|
201
|
+
metricNameServiceNameMap: batch(incoming()),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
expect(scalarUpdates).toHaveLength(0);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("an unchanged description is not a change", async () => {
|
|
209
|
+
jest.spyOn(GlobalCache, "setStringIfChanged").mockResolvedValue(true);
|
|
210
|
+
existingRow = storedRow({ description: "Duration", unit: "ms" });
|
|
211
|
+
|
|
212
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
213
|
+
projectId: PROJECT_ID,
|
|
214
|
+
metricNameServiceNameMap: batch(
|
|
215
|
+
incoming({ description: "Duration", unit: "ms" }),
|
|
216
|
+
),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(scalarUpdates).toHaveLength(0);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("a genuinely new description is written", async () => {
|
|
223
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
224
|
+
projectId: PROJECT_ID,
|
|
225
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(scalarUpdates).toHaveLength(1);
|
|
229
|
+
expect(scalarUpdates[0]!.data["description"]).toBe("Duration");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
* Absent means "this producer didn't say", not "clear it" — the same rule
|
|
234
|
+
* the counter-semantics fields have always used. A producer that omits the
|
|
235
|
+
* description must not wipe one another producer supplied, or the two flip-
|
|
236
|
+
* flop the row forever.
|
|
237
|
+
*/
|
|
238
|
+
test("an omitted description never clears a stored one", async () => {
|
|
239
|
+
jest.spyOn(GlobalCache, "setStringIfChanged").mockResolvedValue(true);
|
|
240
|
+
existingRow = storedRow({ description: "Duration", unit: "ms" });
|
|
241
|
+
|
|
242
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
243
|
+
projectId: PROJECT_ID,
|
|
244
|
+
metricNameServiceNameMap: batch(incoming()),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(scalarUpdates).toHaveLength(0);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("counter semantics still converge", async () => {
|
|
251
|
+
jest.spyOn(GlobalCache, "setStringIfChanged").mockResolvedValue(true);
|
|
252
|
+
existingRow = storedRow({ isMonotonic: true });
|
|
253
|
+
|
|
254
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
255
|
+
projectId: PROJECT_ID,
|
|
256
|
+
metricNameServiceNameMap: batch(incoming({ isMonotonic: true })),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(scalarUpdates).toHaveLength(0);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("a changed isMonotonic is written", async () => {
|
|
263
|
+
existingRow = storedRow({ isMonotonic: false });
|
|
264
|
+
|
|
265
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
266
|
+
projectId: PROJECT_ID,
|
|
267
|
+
metricNameServiceNameMap: batch(incoming({ isMonotonic: true })),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(scalarUpdates[0]!.data["isMonotonic"]).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("the write never reopens the transactional relation path", () => {
|
|
275
|
+
/*
|
|
276
|
+
* `services` present as a key — even as an empty array — routes
|
|
277
|
+
* DatabaseService to save(). The scalar write must therefore carry only
|
|
278
|
+
* scalar columns.
|
|
279
|
+
*/
|
|
280
|
+
test("a scalar-only change takes the single-statement path", async () => {
|
|
281
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
282
|
+
projectId: PROJECT_ID,
|
|
283
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(saveUpdates).toHaveLength(0);
|
|
287
|
+
expect(scalarUpdates).toHaveLength(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("the scalar write carries no `services` key at all", async () => {
|
|
291
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
292
|
+
projectId: PROJECT_ID,
|
|
293
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(Object.keys(scalarUpdates[0]!.data)).not.toContain("services");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("the scalar write carries only the columns that changed", async () => {
|
|
300
|
+
existingRow = storedRow({ description: "Duration" });
|
|
301
|
+
|
|
302
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
303
|
+
projectId: PROJECT_ID,
|
|
304
|
+
metricNameServiceNameMap: batch(
|
|
305
|
+
incoming({ description: "Duration", unit: "ms" }),
|
|
306
|
+
),
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
expect(Object.keys(scalarUpdates[0]!.data)).toEqual(["unit"]);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
/*
|
|
313
|
+
* The data-loss fix. Associations are added, never replaced — a worker that
|
|
314
|
+
* only knows its own batch's services must not be able to express "and
|
|
315
|
+
* delete every other one".
|
|
316
|
+
*/
|
|
317
|
+
test("a new association is added additively", async () => {
|
|
318
|
+
existingRow = storedRow({ services: [serviceRef(SERVICE_A)] });
|
|
319
|
+
|
|
320
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
321
|
+
projectId: PROJECT_ID,
|
|
322
|
+
metricNameServiceNameMap: batch(
|
|
323
|
+
incoming({ services: [serviceRef(SERVICE_B)] }),
|
|
324
|
+
),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
expect(saveUpdates).toHaveLength(0);
|
|
328
|
+
expect(attachCalls).toHaveLength(1);
|
|
329
|
+
expect(
|
|
330
|
+
attachCalls[0]!.serviceIds.map((id: ObjectID) => {
|
|
331
|
+
return id.toString();
|
|
332
|
+
}),
|
|
333
|
+
).toEqual([SERVICE_B.toString()]);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("only the missing associations are sent", async () => {
|
|
337
|
+
existingRow = storedRow({ services: [serviceRef(SERVICE_A)] });
|
|
338
|
+
|
|
339
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
340
|
+
projectId: PROJECT_ID,
|
|
341
|
+
metricNameServiceNameMap: batch(
|
|
342
|
+
incoming({ services: [serviceRef(SERVICE_A), serviceRef(SERVICE_B)] }),
|
|
343
|
+
),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(
|
|
347
|
+
attachCalls[0]!.serviceIds.map((id: ObjectID) => {
|
|
348
|
+
return id.toString();
|
|
349
|
+
}),
|
|
350
|
+
).toEqual([SERVICE_B.toString()]);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("an already-associated service writes nothing", async () => {
|
|
354
|
+
jest.spyOn(GlobalCache, "setStringIfChanged").mockResolvedValue(true);
|
|
355
|
+
existingRow = storedRow({ services: [serviceRef(SERVICE_A)] });
|
|
356
|
+
|
|
357
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
358
|
+
projectId: PROJECT_ID,
|
|
359
|
+
metricNameServiceNameMap: batch(
|
|
360
|
+
incoming({ services: [serviceRef(SERVICE_A)] }),
|
|
361
|
+
),
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(attachCalls).toHaveLength(0);
|
|
365
|
+
expect(scalarUpdates).toHaveLength(0);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("the fence keeps the steady state off the database entirely", () => {
|
|
370
|
+
/*
|
|
371
|
+
* Fenced BEFORE the lookup, so a repeat batch costs no SELECT either — the
|
|
372
|
+
* read fan-out was as much of the pool pressure as the write.
|
|
373
|
+
*/
|
|
374
|
+
test("a repeated batch does not even read", async () => {
|
|
375
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
376
|
+
projectId: PROJECT_ID,
|
|
377
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const readsAfterFirst: number = (MetricTypeService.findOneBy as jest.Mock)
|
|
381
|
+
.mock.calls.length;
|
|
382
|
+
|
|
383
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
384
|
+
projectId: PROJECT_ID,
|
|
385
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
expect((MetricTypeService.findOneBy as jest.Mock).mock.calls.length).toBe(
|
|
389
|
+
readsAfterFirst,
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
/*
|
|
394
|
+
* The batch's service set must be part of the fence KEY. `servicesInMap`
|
|
395
|
+
* holds only the services in THIS batch, so keying on the metric name alone
|
|
396
|
+
* would let a batch from service A satisfy the fence and suppress the
|
|
397
|
+
* association a concurrent batch from service B still needs.
|
|
398
|
+
*/
|
|
399
|
+
test("a different service set is not suppressed by the fence", async () => {
|
|
400
|
+
existingRow = storedRow({ services: [] });
|
|
401
|
+
|
|
402
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
403
|
+
projectId: PROJECT_ID,
|
|
404
|
+
metricNameServiceNameMap: batch(
|
|
405
|
+
incoming({ services: [serviceRef(SERVICE_A)] }),
|
|
406
|
+
),
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
410
|
+
projectId: PROJECT_ID,
|
|
411
|
+
metricNameServiceNameMap: batch(
|
|
412
|
+
incoming({ services: [serviceRef(SERVICE_B)] }),
|
|
413
|
+
),
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(attachCalls).toHaveLength(2);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("a changed scalar re-opens the fence within the window", async () => {
|
|
420
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
421
|
+
projectId: PROJECT_ID,
|
|
422
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
existingRow = storedRow({ description: "Duration" });
|
|
426
|
+
|
|
427
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
428
|
+
projectId: PROJECT_ID,
|
|
429
|
+
metricNameServiceNameMap: batch(incoming({ description: "Latency" })),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
expect(scalarUpdates).toHaveLength(2);
|
|
433
|
+
expect(scalarUpdates[1]!.data["description"]).toBe("Latency");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("a cache outage fails open so the catalog still converges", async () => {
|
|
437
|
+
jest
|
|
438
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
439
|
+
.mockRejectedValue(new Error("redis down"));
|
|
440
|
+
|
|
441
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
442
|
+
projectId: PROJECT_ID,
|
|
443
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
expect(scalarUpdates).toHaveLength(1);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe("one bad metric cannot stop the rest of the batch", () => {
|
|
451
|
+
/*
|
|
452
|
+
* The loop used to let any failure escape, and the only caller just logs —
|
|
453
|
+
* so a single unwritable row silently stopped every REMAINING metric name in
|
|
454
|
+
* the batch from being indexed at all.
|
|
455
|
+
*/
|
|
456
|
+
test("a failure on one metric still indexes the others", async () => {
|
|
457
|
+
jest
|
|
458
|
+
.spyOn(MetricTypeService, "updateColumnsByIdWithoutHooks")
|
|
459
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
460
|
+
const data: Record<string, unknown> = input.data as Record<
|
|
461
|
+
string,
|
|
462
|
+
unknown
|
|
463
|
+
>;
|
|
464
|
+
if (data["description"] === "poison") {
|
|
465
|
+
throw new Error("value too long for type character varying(100)");
|
|
466
|
+
}
|
|
467
|
+
scalarUpdates.push({ id: input.id, data: { ...data } });
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
471
|
+
projectId: PROJECT_ID,
|
|
472
|
+
metricNameServiceNameMap: {
|
|
473
|
+
"metric.bad": incoming({ description: "poison" }),
|
|
474
|
+
"metric.good": incoming({ description: "fine" }),
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
expect(scalarUpdates).toHaveLength(1);
|
|
479
|
+
expect(scalarUpdates[0]!.data["description"]).toBe("fine");
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test("does not throw at the caller", async () => {
|
|
483
|
+
jest
|
|
484
|
+
.spyOn(MetricTypeService, "updateColumnsByIdWithoutHooks")
|
|
485
|
+
.mockRejectedValue(new Error("connection terminated"));
|
|
486
|
+
|
|
487
|
+
await expect(
|
|
488
|
+
TelemetryUtil.indexMetricNameServiceNameMap({
|
|
489
|
+
projectId: PROJECT_ID,
|
|
490
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
491
|
+
}),
|
|
492
|
+
).resolves.toBeUndefined();
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test("re-opens the fence after a failure so the next batch retries", async () => {
|
|
496
|
+
let fail: boolean = true;
|
|
497
|
+
jest
|
|
498
|
+
.spyOn(MetricTypeService, "updateColumnsByIdWithoutHooks")
|
|
499
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
500
|
+
if (fail) {
|
|
501
|
+
throw new Error("transient");
|
|
502
|
+
}
|
|
503
|
+
scalarUpdates.push({
|
|
504
|
+
id: input.id,
|
|
505
|
+
data: { ...(input.data as Record<string, unknown>) },
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
510
|
+
projectId: PROJECT_ID,
|
|
511
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
fail = false;
|
|
515
|
+
|
|
516
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
517
|
+
projectId: PROJECT_ID,
|
|
518
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
expect(scalarUpdates).toHaveLength(1);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe("first contact and the create race", () => {
|
|
526
|
+
test("creates the row when none exists", async () => {
|
|
527
|
+
existingRow = null;
|
|
528
|
+
|
|
529
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
530
|
+
projectId: PROJECT_ID,
|
|
531
|
+
metricNameServiceNameMap: batch(
|
|
532
|
+
incoming({
|
|
533
|
+
description: "Duration",
|
|
534
|
+
services: [serviceRef(SERVICE_A)],
|
|
535
|
+
}),
|
|
536
|
+
),
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
expect(creates).toHaveLength(1);
|
|
540
|
+
expect(creates[0]!.name).toBe("http.server.duration");
|
|
541
|
+
expect(creates[0]!.description).toBe("Duration");
|
|
542
|
+
expect(creates[0]!.services).toHaveLength(1);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
/*
|
|
546
|
+
* Uniqueness on (projectId, name) is enforced in application code, not by a
|
|
547
|
+
* database constraint, so two workers can both decide to create the same
|
|
548
|
+
* metric. The loser must reconcile against the winner rather than surfacing
|
|
549
|
+
* a spurious error and abandoning the rest of the batch.
|
|
550
|
+
*/
|
|
551
|
+
test("a lost create race reconciles against the winning row", async () => {
|
|
552
|
+
existingRow = null;
|
|
553
|
+
|
|
554
|
+
jest
|
|
555
|
+
.spyOn(MetricTypeService, "create")
|
|
556
|
+
.mockRejectedValue(
|
|
557
|
+
new Error("Metric Type with this name already exists"),
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
jest
|
|
561
|
+
.spyOn(MetricTypeService, "findOneBy")
|
|
562
|
+
.mockImplementationOnce(async () => {
|
|
563
|
+
return null;
|
|
564
|
+
})
|
|
565
|
+
.mockImplementation(async () => {
|
|
566
|
+
return storedRow({ services: [] });
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
570
|
+
projectId: PROJECT_ID,
|
|
571
|
+
metricNameServiceNameMap: batch(
|
|
572
|
+
incoming({
|
|
573
|
+
description: "Duration",
|
|
574
|
+
services: [serviceRef(SERVICE_A)],
|
|
575
|
+
}),
|
|
576
|
+
),
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
expect(scalarUpdates).toHaveLength(1);
|
|
580
|
+
expect(attachCalls).toHaveLength(1);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test("a create failure with no winner is reported, not swallowed silently", async () => {
|
|
584
|
+
existingRow = null;
|
|
585
|
+
|
|
586
|
+
jest
|
|
587
|
+
.spyOn(MetricTypeService, "create")
|
|
588
|
+
.mockRejectedValue(new Error("null value in column violates not-null"));
|
|
589
|
+
|
|
590
|
+
await TelemetryUtil.indexMetricNameServiceNameMap({
|
|
591
|
+
projectId: PROJECT_ID,
|
|
592
|
+
metricNameServiceNameMap: batch(incoming({ description: "Duration" })),
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Reported via the per-metric catch; the fence is re-opened for a retry.
|
|
596
|
+
expect(cache.size).toBe(0);
|
|
597
|
+
});
|
|
598
|
+
});
|