@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
2
2
|
import HostService from "../../../Server/Services/HostService";
|
|
3
|
+
import SingleFlight from "../../../Server/Utils/SingleFlight";
|
|
3
4
|
import Host from "../../../Models/DatabaseModels/Host";
|
|
4
5
|
import ObjectID from "../../../Types/ObjectID";
|
|
5
6
|
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
@@ -50,24 +51,43 @@ type UpdateLastSeenExtra = Parameters<typeof HostService.updateLastSeen>[1];
|
|
|
50
51
|
|
|
51
52
|
/** The argument to the hook-free UPDATE the service ultimately issues. */
|
|
52
53
|
type ColumnWrite = Parameters<
|
|
53
|
-
typeof HostService.
|
|
54
|
+
typeof HostService.updateColumnsByIdIfUnlockedWithoutHooks
|
|
54
55
|
>[0];
|
|
55
56
|
|
|
56
57
|
let writes: Array<WriteCall> = [];
|
|
57
58
|
let cache: Map<string, string> = new Map<string, string>();
|
|
58
59
|
let deletedCacheKeys: Array<string> = [];
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A faithful in-memory Redis for the two atomic gate primitives: SET NX
|
|
63
|
+
* succeeds only on an absent key, compare-and-claim only on a differing
|
|
64
|
+
* value. Stubbing either to a constant would let this suite agree with a
|
|
65
|
+
* throttle that does not actually throttle — which is the bug that took
|
|
66
|
+
* production down.
|
|
67
|
+
*/
|
|
60
68
|
function mockCache(): void {
|
|
61
69
|
jest
|
|
62
|
-
.spyOn(GlobalCache, "
|
|
63
|
-
.mockImplementation(
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
71
|
+
.mockImplementation(
|
|
72
|
+
async (namespace: string, key: string, value: string) => {
|
|
73
|
+
const full: string = `${namespace}:${key}`;
|
|
74
|
+
if (cache.has(full)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
cache.set(full, value);
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
80
|
+
);
|
|
66
81
|
jest
|
|
67
|
-
.spyOn(GlobalCache, "
|
|
82
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
68
83
|
.mockImplementation(
|
|
69
84
|
async (namespace: string, key: string, value: string) => {
|
|
70
|
-
|
|
85
|
+
const full: string = `${namespace}:${key}`;
|
|
86
|
+
if (cache.get(full) === value) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
cache.set(full, value);
|
|
90
|
+
return true;
|
|
71
91
|
},
|
|
72
92
|
);
|
|
73
93
|
jest
|
|
@@ -78,6 +98,18 @@ function mockCache(): void {
|
|
|
78
98
|
});
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
/** Re-opens the liveness window, as its TTL would. */
|
|
102
|
+
function expireLivenessWindow(): void {
|
|
103
|
+
cache.delete(`host-last-seen:${HOST_ID.toString()}`);
|
|
104
|
+
SingleFlight.clear();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Re-opens the enrichment rate-limit window, as its TTL would. */
|
|
108
|
+
function expireEnrichmentWindow(): void {
|
|
109
|
+
cache.delete(`host-last-seen-write-window:${HOST_ID.toString()}`);
|
|
110
|
+
SingleFlight.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
81
113
|
function recordWrite(input: {
|
|
82
114
|
id: ObjectID;
|
|
83
115
|
data: unknown;
|
|
@@ -92,9 +124,11 @@ function recordWrite(input: {
|
|
|
92
124
|
/** Records every hook-free UPDATE instead of issuing it. */
|
|
93
125
|
function mockWritesSucceeding(): void {
|
|
94
126
|
jest
|
|
95
|
-
.spyOn(HostService, "
|
|
127
|
+
.spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
96
128
|
.mockImplementation(async (input: ColumnWrite) => {
|
|
97
129
|
recordWrite(input);
|
|
130
|
+
// The SKIP LOCKED primitive resolves true when the row was updated.
|
|
131
|
+
return true;
|
|
98
132
|
});
|
|
99
133
|
}
|
|
100
134
|
|
|
@@ -106,12 +140,13 @@ function mockEnrichedWriteFailing(
|
|
|
106
140
|
message: string = `value too long for type character varying(100)`,
|
|
107
141
|
): void {
|
|
108
142
|
jest
|
|
109
|
-
.spyOn(HostService, "
|
|
143
|
+
.spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
110
144
|
.mockImplementation(async (input: ColumnWrite) => {
|
|
111
145
|
const data: Record<string, unknown> = recordWrite(input);
|
|
112
146
|
if (Object.keys(data).length > 2) {
|
|
113
147
|
throw new Error(message);
|
|
114
148
|
}
|
|
149
|
+
return true;
|
|
115
150
|
});
|
|
116
151
|
}
|
|
117
152
|
|
|
@@ -160,7 +195,8 @@ function mockRealWritePath(): void {
|
|
|
160
195
|
data: bound,
|
|
161
196
|
});
|
|
162
197
|
bound = {};
|
|
163
|
-
|
|
198
|
+
// One affected row: the SKIP LOCKED write reports success via RETURNING.
|
|
199
|
+
return [{ _id: String(params[params.length - 1]) }];
|
|
164
200
|
},
|
|
165
201
|
},
|
|
166
202
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -176,11 +212,13 @@ describe("HostService.updateLastSeen", () => {
|
|
|
176
212
|
writes = [];
|
|
177
213
|
cache = new Map<string, string>();
|
|
178
214
|
deletedCacheKeys = [];
|
|
215
|
+
SingleFlight.clear();
|
|
179
216
|
mockCache();
|
|
180
217
|
});
|
|
181
218
|
|
|
182
219
|
afterEach(() => {
|
|
183
220
|
jest.restoreAllMocks();
|
|
221
|
+
SingleFlight.clear();
|
|
184
222
|
});
|
|
185
223
|
|
|
186
224
|
describe("liveness columns", () => {
|
|
@@ -203,17 +241,118 @@ describe("HostService.updateLastSeen", () => {
|
|
|
203
241
|
|
|
204
242
|
test("skips the write when the same metadata was written recently", async () => {
|
|
205
243
|
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
244
|
+
SingleFlight.clear();
|
|
206
245
|
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
207
246
|
|
|
208
247
|
expect(writes).toHaveLength(1);
|
|
209
248
|
});
|
|
210
249
|
|
|
211
|
-
|
|
250
|
+
/*
|
|
251
|
+
* A changed attribute lands in the NEXT window, not on the very next
|
|
252
|
+
* batch. That narrows the old "writes again immediately" contract on
|
|
253
|
+
* purpose: change detection cannot tell a real update apart from a gauge
|
|
254
|
+
* that moves every scrape — both present as "it changed" — and only one
|
|
255
|
+
* of the two is bounded. Bounding both is what removes the convoy, and
|
|
256
|
+
* the cost is at most one window of staleness on a descriptive column.
|
|
257
|
+
* `lastSeenAt` is unaffected; it rides the separate liveness gate.
|
|
258
|
+
*/
|
|
259
|
+
test("a metadata change lands in the next window", async () => {
|
|
212
260
|
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
261
|
+
expireLivenessWindow();
|
|
262
|
+
expireEnrichmentWindow();
|
|
263
|
+
|
|
213
264
|
await HostService.updateLastSeen(HOST_ID, { osType: "windows" });
|
|
214
265
|
|
|
266
|
+
expect(writes).toHaveLength(2);
|
|
267
|
+
expect(lastWrite()["osType"]).toBe("windows");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("a metadata change cannot write twice inside one window", async () => {
|
|
271
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
|
|
272
|
+
SingleFlight.clear();
|
|
273
|
+
await HostService.updateLastSeen(HOST_ID, { osType: "windows" });
|
|
274
|
+
|
|
275
|
+
expect(writes).toHaveLength(1);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
/*
|
|
279
|
+
* Per-scrape gauges must NOT participate in the throttle fingerprint.
|
|
280
|
+
*
|
|
281
|
+
* cpuCores, totalMemoryBytes and processCount used to be hashed into it,
|
|
282
|
+
* and `system.processes.count` is partitioned by process.status and
|
|
283
|
+
* summed — so it changes on essentially every scrape. The fingerprint
|
|
284
|
+
* differed every time and the 60s window never engaged, for exactly the
|
|
285
|
+
* hosts producing the most batches. OneUptime's own install guide enables
|
|
286
|
+
* the processes scraper at a 30s interval, so every host following the
|
|
287
|
+
* documentation hit this.
|
|
288
|
+
*
|
|
289
|
+
* They are still WRITTEN (see below) — they just ride along on whatever
|
|
290
|
+
* write the throttle admits instead of forcing one.
|
|
291
|
+
*/
|
|
292
|
+
test("a gauge-only change does not defeat the throttle", async () => {
|
|
293
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
294
|
+
osType: "linux",
|
|
295
|
+
cpuCores: 8,
|
|
296
|
+
totalMemoryBytes: 17179869184,
|
|
297
|
+
processCount: 412,
|
|
298
|
+
});
|
|
299
|
+
expireEnrichmentWindow();
|
|
300
|
+
|
|
301
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
302
|
+
osType: "linux",
|
|
303
|
+
cpuCores: 8,
|
|
304
|
+
totalMemoryBytes: 17179869184,
|
|
305
|
+
processCount: 413,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
/*
|
|
309
|
+
* Both gates were re-opened, so the only thing that can suppress the
|
|
310
|
+
* second write is the fingerprint agreeing — which is the point: the
|
|
311
|
+
* gauges must not be part of it.
|
|
312
|
+
*/
|
|
313
|
+
expect(writes).toHaveLength(1);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("a churning process count over many scrapes still writes once", async () => {
|
|
317
|
+
for (let scrape: number = 0; scrape < 20; scrape++) {
|
|
318
|
+
expireEnrichmentWindow();
|
|
319
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
320
|
+
osType: "linux",
|
|
321
|
+
processCount: 400 + scrape,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
expect(writes).toHaveLength(1);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("a real metadata change alongside a churning gauge still writes", async () => {
|
|
329
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
330
|
+
osType: "linux",
|
|
331
|
+
processCount: 412,
|
|
332
|
+
});
|
|
333
|
+
expireLivenessWindow();
|
|
334
|
+
expireEnrichmentWindow();
|
|
335
|
+
|
|
336
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
337
|
+
osType: "windows",
|
|
338
|
+
processCount: 413,
|
|
339
|
+
});
|
|
340
|
+
|
|
215
341
|
expect(writes).toHaveLength(2);
|
|
216
342
|
});
|
|
343
|
+
|
|
344
|
+
test("the gauges are still written when a write happens", async () => {
|
|
345
|
+
await HostService.updateLastSeen(HOST_ID, {
|
|
346
|
+
osType: "linux",
|
|
347
|
+
cpuCores: 8,
|
|
348
|
+
totalMemoryBytes: 17179869184,
|
|
349
|
+
processCount: 412,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(lastWrite()["cpuCores"]).toBe(8);
|
|
353
|
+
expect(lastWrite()["totalMemoryBytes"]).toBe(17179869184);
|
|
354
|
+
expect(lastWrite()["processCount"]).toBe(412);
|
|
355
|
+
});
|
|
217
356
|
});
|
|
218
357
|
|
|
219
358
|
describe("oversized collector metadata", () => {
|
|
@@ -342,13 +481,22 @@ describe("HostService.updateLastSeen", () => {
|
|
|
342
481
|
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
343
482
|
});
|
|
344
483
|
|
|
345
|
-
|
|
484
|
+
/*
|
|
485
|
+
* The enrichment gates were claimed before the write that failed, so
|
|
486
|
+
* without releasing them the next batch would short-circuit and the retry
|
|
487
|
+
* would never happen. The liveness gate is deliberately NOT released: the
|
|
488
|
+
* fallback write below already refreshed lastSeenAt, so re-opening it
|
|
489
|
+
* would buy a second heartbeat inside one window for nothing.
|
|
490
|
+
*/
|
|
491
|
+
test("busts the enrichment gates so the next batch is not skipped", async () => {
|
|
346
492
|
await HostService.updateLastSeen(HOST_ID, {
|
|
347
493
|
hostIpAddresses: HUGE_IP_LIST,
|
|
348
494
|
});
|
|
349
495
|
|
|
350
|
-
expect(deletedCacheKeys).
|
|
351
|
-
|
|
496
|
+
expect(deletedCacheKeys).toEqual([
|
|
497
|
+
`host-last-seen-fingerprint:${HOST_ID.toString()}`,
|
|
498
|
+
`host-last-seen-write-window:${HOST_ID.toString()}`,
|
|
499
|
+
]);
|
|
352
500
|
});
|
|
353
501
|
|
|
354
502
|
test("the next batch retries rather than short-circuiting on a cache hit", async () => {
|
|
@@ -383,7 +531,7 @@ describe("HostService.updateLastSeen", () => {
|
|
|
383
531
|
test("surfaces a genuinely broken database instead of hiding it", async () => {
|
|
384
532
|
// Both attempts fail — the caller must find out.
|
|
385
533
|
jest
|
|
386
|
-
.spyOn(HostService, "
|
|
534
|
+
.spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
387
535
|
.mockRejectedValue(new Error("connection terminated"));
|
|
388
536
|
|
|
389
537
|
await expect(
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import MetricTypeService from "../../../Server/Services/MetricTypeService";
|
|
2
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
3
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
4
|
+
import { getJestSpyOn } from "../../Spy";
|
|
5
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Pins the SQL that associates services with a metric type.
|
|
9
|
+
*
|
|
10
|
+
* The association used to be expressed by handing `updateOneById` the whole
|
|
11
|
+
* `services` array. Because `services` is an EntityArray, its mere PRESENCE as
|
|
12
|
+
* a key routes DatabaseService to `getRepository().save()` — a real
|
|
13
|
+
* BEGIN/COMMIT that reloads the entity, loads relation ids, bumps `version`,
|
|
14
|
+
* and DELETEs then re-INSERTs junction rows, holding the MetricType row's
|
|
15
|
+
* write lock across all of it. On a path that runs per metric name per batch
|
|
16
|
+
* with no backpressure, that was the only multi-round-trip lock hold in the
|
|
17
|
+
* pipeline.
|
|
18
|
+
*
|
|
19
|
+
* It was also losing data. save() deletes any association missing from the
|
|
20
|
+
* array it is handed, and an ingest worker only knows the services in ITS
|
|
21
|
+
* batch — so two workers deleted each other's associations and re-inserted
|
|
22
|
+
* them on the next batch. Permanent junction churn, with real service-to-metric
|
|
23
|
+
* links missing from the UI in between.
|
|
24
|
+
*
|
|
25
|
+
* The assertions below are about the emitted statement, because that is where
|
|
26
|
+
* both properties live: it must be a single additive INSERT, and it must never
|
|
27
|
+
* be able to express a delete.
|
|
28
|
+
*
|
|
29
|
+
* No Postgres — the repository is faked and the SQL is captured.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const METRIC_TYPE_ID: ObjectID = new ObjectID(
|
|
33
|
+
"44444444-4444-4444-8444-444444444444",
|
|
34
|
+
);
|
|
35
|
+
const SERVICE_A: ObjectID = new ObjectID(
|
|
36
|
+
"11111111-1111-4111-8111-111111111111",
|
|
37
|
+
);
|
|
38
|
+
const SERVICE_B: ObjectID = new ObjectID(
|
|
39
|
+
"22222222-2222-4222-8222-222222222222",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
interface CapturedQuery {
|
|
43
|
+
sql: string;
|
|
44
|
+
params: Array<unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("MetricTypeService.attachServices", () => {
|
|
48
|
+
let captured: Array<CapturedQuery>;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
jest.restoreAllMocks();
|
|
52
|
+
captured = [];
|
|
53
|
+
|
|
54
|
+
getJestSpyOn(MetricTypeService, "getRepository").mockReturnValue({
|
|
55
|
+
metadata: {
|
|
56
|
+
findRelationWithPropertyPath: (path: string) => {
|
|
57
|
+
if (path !== "services") {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
junctionEntityMetadata: {
|
|
62
|
+
tableName: "MetricTypeService",
|
|
63
|
+
columns: [
|
|
64
|
+
{ databaseName: "metricTypeId" },
|
|
65
|
+
{ databaseName: "serviceId" },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
manager: {
|
|
72
|
+
query: async (
|
|
73
|
+
sql: string,
|
|
74
|
+
params: Array<unknown>,
|
|
75
|
+
): Promise<Array<unknown>> => {
|
|
76
|
+
captured.push({ sql, params });
|
|
77
|
+
return [];
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
+
} as any);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("writes the associations in one statement", async () => {
|
|
85
|
+
await MetricTypeService.attachServices({
|
|
86
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
87
|
+
serviceIds: [SERVICE_A, SERVICE_B],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(captured).toHaveLength(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("inserts additively, tolerating a concurrent writer adding the same pair", async () => {
|
|
94
|
+
await MetricTypeService.attachServices({
|
|
95
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
96
|
+
serviceIds: [SERVICE_A],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(captured[0]!.sql).toContain(
|
|
100
|
+
`INSERT INTO "MetricTypeService" ("metricTypeId", "serviceId")`,
|
|
101
|
+
);
|
|
102
|
+
expect(captured[0]!.sql).toContain("ON CONFLICT DO NOTHING");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* The load-bearing negative. If a DELETE can ever be emitted here, the
|
|
107
|
+
* data-loss bug is expressible again.
|
|
108
|
+
*/
|
|
109
|
+
it("can never emit a DELETE", async () => {
|
|
110
|
+
await MetricTypeService.attachServices({
|
|
111
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
112
|
+
serviceIds: [SERVICE_A, SERVICE_B],
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(captured[0]!.sql.toUpperCase()).not.toContain("DELETE");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("never opens a transaction", async () => {
|
|
119
|
+
await MetricTypeService.attachServices({
|
|
120
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
121
|
+
serviceIds: [SERVICE_A],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(captured[0]!.sql.toUpperCase()).not.toContain("BEGIN");
|
|
125
|
+
expect(captured[0]!.sql.toUpperCase()).not.toContain("COMMIT");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("never touches the optimistic-lock version column", async () => {
|
|
129
|
+
await MetricTypeService.attachServices({
|
|
130
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
131
|
+
serviceIds: [SERVICE_A],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(captured[0]!.sql).not.toContain("version");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("binds the ids as parameters, never as literals", async () => {
|
|
138
|
+
await MetricTypeService.attachServices({
|
|
139
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
140
|
+
serviceIds: [SERVICE_A, SERVICE_B],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(captured[0]!.params[0]).toBe(METRIC_TYPE_ID.toString());
|
|
144
|
+
expect(captured[0]!.sql).not.toContain(METRIC_TYPE_ID.toString());
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
* Sorting is not cosmetic. Two workers inserting overlapping sets acquire
|
|
149
|
+
* their row locks in the order the rows appear; a consistent order is what
|
|
150
|
+
* keeps them from deadlocking each other.
|
|
151
|
+
*/
|
|
152
|
+
it("sorts the ids so concurrent writers lock in a consistent order", async () => {
|
|
153
|
+
const ids: Array<ObjectID> = [SERVICE_B, SERVICE_A];
|
|
154
|
+
const expected: Array<string> = [
|
|
155
|
+
SERVICE_A.toString(),
|
|
156
|
+
SERVICE_B.toString(),
|
|
157
|
+
].sort();
|
|
158
|
+
|
|
159
|
+
await MetricTypeService.attachServices({
|
|
160
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
161
|
+
serviceIds: ids,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(captured[0]!.params[1]).toEqual(expected);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("deduplicates repeated ids", async () => {
|
|
168
|
+
await MetricTypeService.attachServices({
|
|
169
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
170
|
+
serviceIds: [SERVICE_A, SERVICE_A, SERVICE_A],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(captured[0]!.params[1]).toEqual([SERVICE_A.toString()]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("issues nothing for an empty set", async () => {
|
|
177
|
+
await MetricTypeService.attachServices({
|
|
178
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
179
|
+
serviceIds: [],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(captured).toHaveLength(0);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("requires a metric type id", async () => {
|
|
186
|
+
await expect(
|
|
187
|
+
MetricTypeService.attachServices({
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
189
|
+
metricTypeId: undefined as any,
|
|
190
|
+
serviceIds: [SERVICE_A],
|
|
191
|
+
}),
|
|
192
|
+
).rejects.toThrow(BadDataException);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
* The junction table and column names come from entity metadata, never from
|
|
197
|
+
* a caller and never hardcoded at the call site — so a schema rename cannot
|
|
198
|
+
* leave this writing to a table that no longer exists.
|
|
199
|
+
*/
|
|
200
|
+
it("fails loudly if the relation metadata is missing rather than guessing", async () => {
|
|
201
|
+
getJestSpyOn(MetricTypeService, "getRepository").mockReturnValue({
|
|
202
|
+
metadata: {
|
|
203
|
+
findRelationWithPropertyPath: () => {
|
|
204
|
+
return undefined;
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
manager: {
|
|
208
|
+
query: async (): Promise<Array<unknown>> => {
|
|
209
|
+
return [];
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
} as any);
|
|
214
|
+
|
|
215
|
+
await expect(
|
|
216
|
+
MetricTypeService.attachServices({
|
|
217
|
+
metricTypeId: METRIC_TYPE_ID,
|
|
218
|
+
serviceIds: [SERVICE_A],
|
|
219
|
+
}),
|
|
220
|
+
).rejects.toThrow(BadDataException);
|
|
221
|
+
});
|
|
222
|
+
});
|