@oneuptime/common 12.0.1 → 12.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/Host.ts +12 -4
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +39 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/EnvironmentConfig.ts +31 -1
- package/Server/Infrastructure/GlobalCache.ts +115 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +27 -45
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +24 -42
- package/Server/Services/DatabaseService.ts +198 -6
- package/Server/Services/DockerHostService.ts +20 -38
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +28 -46
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +47 -57
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +20 -38
- package/Server/Services/KubernetesClusterService.ts +18 -36
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +153 -1
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +146 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/PodmanHostService.ts +20 -38
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +24 -42
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +20 -38
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +25 -43
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- package/Server/Utils/Monitor/MonitorResource.ts +63 -36
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
- package/Server/Utils/SingleFlight.ts +83 -0
- package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
- package/Server/Utils/Telemetry/Telemetry.ts +351 -122
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
- package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
- package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +658 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
- package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +636 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
- package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
- package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
- package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
- package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
- package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
- package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
- package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
- package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
- package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
- package/Tests/Types/Decimal.test.ts +37 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
- package/Types/Dashboard/DashboardTemplates.ts +397 -0
- package/Types/Database/BigIntColumnTransformer.ts +74 -0
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Date.ts +112 -0
- package/Types/Decimal.ts +16 -4
- package/Types/Events/Recurring.ts +99 -1
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +39 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +27 -1
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -39
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -36
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +155 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -32
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -40
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -64
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -32
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -30
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +127 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MetricTypeService.js +74 -0
- package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +128 -9
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -32
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -36
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -32
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +24 -37
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +236 -61
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
- package/build/dist/Server/Utils/SingleFlight.js +76 -0
- package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
- package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
- package/build/dist/Types/Database/DatabaseProperty.js +25 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/NumericColumnValue.js +97 -0
- package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
- package/build/dist/Types/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Port.js +15 -5
- package/build/dist/Types/Port.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import CephClusterService from "../../../Server/Services/CephClusterService";
|
|
2
|
+
import CloudResourceService from "../../../Server/Services/CloudResourceService";
|
|
3
|
+
import DockerHostService from "../../../Server/Services/DockerHostService";
|
|
4
|
+
import DockerSwarmClusterService from "../../../Server/Services/DockerSwarmClusterService";
|
|
5
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
6
|
+
import HostService from "../../../Server/Services/HostService";
|
|
7
|
+
import IoTFleetService from "../../../Server/Services/IoTFleetService";
|
|
8
|
+
import KubernetesClusterService from "../../../Server/Services/KubernetesClusterService";
|
|
9
|
+
import PodmanHostService from "../../../Server/Services/PodmanHostService";
|
|
10
|
+
import ProxmoxClusterService from "../../../Server/Services/ProxmoxClusterService";
|
|
11
|
+
import RumApplicationService from "../../../Server/Services/RumApplicationService";
|
|
12
|
+
import ServerlessFunctionService from "../../../Server/Services/ServerlessFunctionService";
|
|
13
|
+
import SingleFlight from "../../../Server/Utils/SingleFlight";
|
|
14
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
15
|
+
import {
|
|
16
|
+
afterEach,
|
|
17
|
+
beforeEach,
|
|
18
|
+
describe,
|
|
19
|
+
expect,
|
|
20
|
+
jest,
|
|
21
|
+
test,
|
|
22
|
+
} from "@jest/globals";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Every resource type's liveness heartbeat, pinned together.
|
|
26
|
+
*
|
|
27
|
+
* Eleven services write the same two liveness columns (lastSeenAt +
|
|
28
|
+
* otelCollectorStatus) alongside optional columns harvested from
|
|
29
|
+
* OpenTelemetry resource attributes, and each has its own markDisconnected*
|
|
30
|
+
* job that flips the resource to "disconnected" 15 minutes after lastSeenAt
|
|
31
|
+
* stops advancing. They now share one implementation — ResourceHeartbeat —
|
|
32
|
+
* because eleven hand-copied versions meant eleven copies of the same defect.
|
|
33
|
+
*
|
|
34
|
+
* WHAT THE DEFECT WAS. The throttle keyed on the row id but STORED a
|
|
35
|
+
* fingerprint of the incoming payload, skipping only on a match. That gives
|
|
36
|
+
* zero throttling whenever the fingerprinted payload changes as fast as the
|
|
37
|
+
* batches arrive, and on these paths it does:
|
|
38
|
+
*
|
|
39
|
+
* - Cluster and fleet snapshots hash per-scrape GAUGES into the
|
|
40
|
+
* fingerprint — runningTaskCount, osdUpCount, onlineDeviceCount,
|
|
41
|
+
* capacityUsedPercent. A float utilisation percentage changes on
|
|
42
|
+
* literally every scrape, so the fingerprint never repeated and the
|
|
43
|
+
* window never engaged.
|
|
44
|
+
* - Four of these paths (Proxmox, Ceph, IoTFleet and DockerSwarm snapshot
|
|
45
|
+
* writebacks) have NO upstream shouldRunMaintenance fence either, so that
|
|
46
|
+
* broken throttle was the only thing standing between the ingest firehose
|
|
47
|
+
* and a row shared by every node in the cluster.
|
|
48
|
+
*
|
|
49
|
+
* Result: one UPDATE per batch per shared row, which is the row-lock convoy
|
|
50
|
+
* that took production down.
|
|
51
|
+
*
|
|
52
|
+
* WHAT IS PINNED HERE, per service:
|
|
53
|
+
*
|
|
54
|
+
* 1. Liveness is gated on key PRESENCE, so no payload — however fast it
|
|
55
|
+
* churns — can force a second heartbeat inside a window. This is the
|
|
56
|
+
* assertion that would have caught the outage.
|
|
57
|
+
* 2. Enrichment is bounded to one write per window even under continuous
|
|
58
|
+
* change.
|
|
59
|
+
* 3. Liveness survives a failing enriched write (issue #3006), a Redis
|
|
60
|
+
* outage (fail open), and losing the row-lock race.
|
|
61
|
+
*
|
|
62
|
+
* Everything external is mocked — no Postgres, no Redis.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
type WriteCall = { id: ObjectID; data: Record<string, unknown> };
|
|
66
|
+
|
|
67
|
+
type ServiceCase = {
|
|
68
|
+
/** Name of the service, used as the describe() title. */
|
|
69
|
+
name: string;
|
|
70
|
+
service: {
|
|
71
|
+
updateLastSeen(id: ObjectID, extra?: never): Promise<void>;
|
|
72
|
+
updateColumnsByIdIfUnlockedWithoutHooks(input: {
|
|
73
|
+
id: ObjectID;
|
|
74
|
+
data: unknown;
|
|
75
|
+
}): Promise<boolean>;
|
|
76
|
+
};
|
|
77
|
+
/** The Redis namespace this model's gates live under. */
|
|
78
|
+
namespace: string;
|
|
79
|
+
/** A metadata payload this service accepts, and a different one. */
|
|
80
|
+
extra: Record<string, unknown>;
|
|
81
|
+
otherExtra: Record<string, unknown>;
|
|
82
|
+
/** A string column whose value the collector supplies. */
|
|
83
|
+
oversizedExtra: Record<string, unknown>;
|
|
84
|
+
/**
|
|
85
|
+
* A payload whose values move on every scrape, if this service has one.
|
|
86
|
+
* These are what defeated the old throttle — see the header.
|
|
87
|
+
*/
|
|
88
|
+
churningExtras?: Array<Record<string, unknown>>;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Longer than any bounded column on any of these models. */
|
|
92
|
+
const OVERSIZED: string = "x".repeat(5000);
|
|
93
|
+
|
|
94
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
95
|
+
const SERVICE_CASES: Array<ServiceCase> = [
|
|
96
|
+
{
|
|
97
|
+
name: "DockerHostService",
|
|
98
|
+
service: DockerHostService as any,
|
|
99
|
+
namespace: "docker-host-last-seen",
|
|
100
|
+
extra: { osType: "linux" },
|
|
101
|
+
otherExtra: { osType: "windows" },
|
|
102
|
+
oversizedExtra: { osVersion: OVERSIZED },
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "PodmanHostService",
|
|
106
|
+
service: PodmanHostService as any,
|
|
107
|
+
namespace: "podman-host-last-seen",
|
|
108
|
+
extra: { osType: "linux" },
|
|
109
|
+
otherExtra: { osType: "windows" },
|
|
110
|
+
oversizedExtra: { osVersion: OVERSIZED },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "KubernetesClusterService",
|
|
114
|
+
service: KubernetesClusterService as any,
|
|
115
|
+
namespace: "k8s-cluster-last-seen",
|
|
116
|
+
extra: { agentVersion: "1.2.3" },
|
|
117
|
+
otherExtra: { agentVersion: "1.2.4" },
|
|
118
|
+
oversizedExtra: { agentVersion: OVERSIZED },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "ProxmoxClusterService",
|
|
122
|
+
service: ProxmoxClusterService as any,
|
|
123
|
+
namespace: "proxmox-cluster-last-seen",
|
|
124
|
+
extra: { pveVersion: "8.1.4" },
|
|
125
|
+
otherExtra: { pveVersion: "8.2.0" },
|
|
126
|
+
oversizedExtra: { pveVersion: OVERSIZED },
|
|
127
|
+
churningExtras: [
|
|
128
|
+
{ pveVersion: "8.1.4", onlineNodeCount: 11, guestCount: 240 },
|
|
129
|
+
{ pveVersion: "8.1.4", onlineNodeCount: 12, guestCount: 241 },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "IoTFleetService",
|
|
134
|
+
service: IoTFleetService as any,
|
|
135
|
+
namespace: "iot-fleet-last-seen",
|
|
136
|
+
extra: { agentVersion: "1.2.3" },
|
|
137
|
+
otherExtra: { agentVersion: "1.2.4" },
|
|
138
|
+
oversizedExtra: { agentVersion: OVERSIZED },
|
|
139
|
+
churningExtras: [
|
|
140
|
+
{ agentVersion: "1.2.3", onlineDeviceCount: 8014 },
|
|
141
|
+
{ agentVersion: "1.2.3", onlineDeviceCount: 8015 },
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "DockerSwarmClusterService",
|
|
146
|
+
service: DockerSwarmClusterService as any,
|
|
147
|
+
namespace: "docker-swarm-cluster-last-seen",
|
|
148
|
+
extra: { dockerVersion: "25.0.3" },
|
|
149
|
+
otherExtra: { dockerVersion: "26.0.0" },
|
|
150
|
+
oversizedExtra: { swarmId: OVERSIZED },
|
|
151
|
+
churningExtras: [
|
|
152
|
+
{ dockerVersion: "25.0.3", runningTaskCount: 512, taskCount: 530 },
|
|
153
|
+
{ dockerVersion: "25.0.3", runningTaskCount: 513, taskCount: 530 },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "CephClusterService",
|
|
158
|
+
service: CephClusterService as any,
|
|
159
|
+
namespace: "ceph-cluster-last-seen",
|
|
160
|
+
extra: { cephVersion: "18.2.2" },
|
|
161
|
+
otherExtra: { cephVersion: "19.0.0" },
|
|
162
|
+
oversizedExtra: { fsid: OVERSIZED },
|
|
163
|
+
/*
|
|
164
|
+
* The worst of them: a float utilisation percentage moves on every single
|
|
165
|
+
* scrape, so the old fingerprint was guaranteed never to repeat.
|
|
166
|
+
*/
|
|
167
|
+
churningExtras: [
|
|
168
|
+
{ cephVersion: "18.2.2", capacityUsedPercent: 61.4, osdUpCount: 48 },
|
|
169
|
+
{ cephVersion: "18.2.2", capacityUsedPercent: 61.41, osdUpCount: 48 },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "ServerlessFunctionService",
|
|
174
|
+
service: ServerlessFunctionService as any,
|
|
175
|
+
namespace: "serverless-function-last-seen",
|
|
176
|
+
extra: { runtimeName: "nodejs" },
|
|
177
|
+
otherExtra: { runtimeName: "python" },
|
|
178
|
+
oversizedExtra: { functionVersion: OVERSIZED },
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "CloudResourceService",
|
|
182
|
+
service: CloudResourceService as any,
|
|
183
|
+
namespace: "cloud-resource-last-seen",
|
|
184
|
+
extra: { cloudProvider: "aws" },
|
|
185
|
+
otherExtra: { cloudProvider: "gcp" },
|
|
186
|
+
oversizedExtra: { cloudRegion: OVERSIZED },
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "RumApplicationService",
|
|
190
|
+
service: RumApplicationService as any,
|
|
191
|
+
namespace: "rum-application-last-seen",
|
|
192
|
+
extra: { sdkLanguage: "webjs" },
|
|
193
|
+
otherExtra: { sdkLanguage: "swift" },
|
|
194
|
+
oversizedExtra: { clientType: OVERSIZED },
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
/*
|
|
198
|
+
* Host rides the same table so the eleven paths stay pinned together —
|
|
199
|
+
* its own richer regression coverage lives in
|
|
200
|
+
* HostServiceUpdateLastSeen.test.ts.
|
|
201
|
+
*/
|
|
202
|
+
name: "HostService",
|
|
203
|
+
service: HostService as any,
|
|
204
|
+
namespace: "host-last-seen",
|
|
205
|
+
extra: { osType: "linux" },
|
|
206
|
+
otherExtra: { osType: "windows" },
|
|
207
|
+
oversizedExtra: { hostId: OVERSIZED },
|
|
208
|
+
churningExtras: [
|
|
209
|
+
{ osType: "linux", processCount: 412, totalMemoryBytes: 17179869184 },
|
|
210
|
+
{ osType: "linux", processCount: 413, totalMemoryBytes: 17179869184 },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
215
|
+
|
|
216
|
+
describe.each(SERVICE_CASES)(
|
|
217
|
+
"$name.updateLastSeen",
|
|
218
|
+
({
|
|
219
|
+
service,
|
|
220
|
+
namespace,
|
|
221
|
+
extra,
|
|
222
|
+
otherExtra,
|
|
223
|
+
oversizedExtra,
|
|
224
|
+
churningExtras,
|
|
225
|
+
}: ServiceCase) => {
|
|
226
|
+
let writes: Array<WriteCall>;
|
|
227
|
+
let cache: Map<string, string>;
|
|
228
|
+
let deletedCacheKeys: Array<string>;
|
|
229
|
+
/** Set false to simulate the row being held by another writer. */
|
|
230
|
+
let writeLands: boolean;
|
|
231
|
+
|
|
232
|
+
const RESOURCE_ID: ObjectID = ObjectID.generate();
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A faithful in-memory Redis. SET NX succeeds only on an absent key and
|
|
236
|
+
* compare-and-claim only on a differing value — stubbing either to a
|
|
237
|
+
* constant would make this suite agree with a broken implementation.
|
|
238
|
+
*/
|
|
239
|
+
function mockCache(): void {
|
|
240
|
+
jest
|
|
241
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
242
|
+
.mockImplementation(async (ns: string, key: string, value: string) => {
|
|
243
|
+
const full: string = `${ns}:${key}`;
|
|
244
|
+
if (cache.has(full)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
cache.set(full, value);
|
|
248
|
+
return true;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
jest
|
|
252
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
253
|
+
.mockImplementation(async (ns: string, key: string, value: string) => {
|
|
254
|
+
const full: string = `${ns}:${key}`;
|
|
255
|
+
if (cache.get(full) === value) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
cache.set(full, value);
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
jest
|
|
263
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
264
|
+
.mockImplementation(async (ns: string, key: string) => {
|
|
265
|
+
const full: string = `${ns}:${key}`;
|
|
266
|
+
deletedCacheKeys.push(full);
|
|
267
|
+
cache.delete(full);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Records every hook-free UPDATE instead of issuing it. */
|
|
272
|
+
function mockWritesSucceeding(): void {
|
|
273
|
+
jest
|
|
274
|
+
.spyOn(service, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
275
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
276
|
+
writes.push({
|
|
277
|
+
id: input.id,
|
|
278
|
+
data: { ...(input.data as Record<string, unknown>) },
|
|
279
|
+
});
|
|
280
|
+
return writeLands;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Fails any write carrying more than the two liveness columns — the
|
|
286
|
+
* shape of a Postgres rejection caused by one bad optional column.
|
|
287
|
+
*/
|
|
288
|
+
function mockEnrichedWriteFailing(): void {
|
|
289
|
+
jest
|
|
290
|
+
.spyOn(service, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
291
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
292
|
+
const data: Record<string, unknown> = {
|
|
293
|
+
...(input.data as Record<string, unknown>),
|
|
294
|
+
};
|
|
295
|
+
writes.push({ id: input.id, data: data });
|
|
296
|
+
if (Object.keys(data).length > 2) {
|
|
297
|
+
throw new Error("value too long for type character varying(100)");
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function lastWrite(): Record<string, unknown> {
|
|
304
|
+
return writes[writes.length - 1]!.data;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Re-opens the liveness window without touching the enrichment gates. */
|
|
308
|
+
function expireLivenessWindow(): void {
|
|
309
|
+
cache.delete(`${namespace}:${RESOURCE_ID.toString()}`);
|
|
310
|
+
SingleFlight.clear();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
beforeEach(() => {
|
|
314
|
+
writes = [];
|
|
315
|
+
cache = new Map<string, string>();
|
|
316
|
+
deletedCacheKeys = [];
|
|
317
|
+
writeLands = true;
|
|
318
|
+
SingleFlight.clear();
|
|
319
|
+
mockCache();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
afterEach(() => {
|
|
323
|
+
jest.restoreAllMocks();
|
|
324
|
+
SingleFlight.clear();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("liveness columns", () => {
|
|
328
|
+
beforeEach(() => {
|
|
329
|
+
mockWritesSucceeding();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("always writes lastSeenAt and marks the collector connected", async () => {
|
|
333
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
334
|
+
|
|
335
|
+
expect(writes).toHaveLength(1);
|
|
336
|
+
expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
|
|
337
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("writes to the resource it was given", async () => {
|
|
341
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
342
|
+
|
|
343
|
+
expect(writes[0]!.id.toString()).toBe(RESOURCE_ID.toString());
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("skips the write when the same metadata was written recently", async () => {
|
|
347
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
348
|
+
SingleFlight.clear();
|
|
349
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
350
|
+
|
|
351
|
+
expect(writes).toHaveLength(1);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("passes collector metadata through to the write path", async () => {
|
|
355
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
356
|
+
|
|
357
|
+
for (const [column, value] of Object.entries(extra)) {
|
|
358
|
+
expect(lastWrite()[column]).toBe(value);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
/*
|
|
363
|
+
* A changed attribute still lands — but in the next window, not on the
|
|
364
|
+
* very next batch. That is a deliberate narrowing of the old contract:
|
|
365
|
+
* change detection cannot tell a real update apart from a gauge that
|
|
366
|
+
* moves every scrape, both present as "it changed", and only one of
|
|
367
|
+
* those is bounded. Bounding both is what removes the convoy. The delay
|
|
368
|
+
* is at most one throttle window and `lastSeenAt` is unaffected, since
|
|
369
|
+
* it rides the separate liveness gate.
|
|
370
|
+
*/
|
|
371
|
+
test("a metadata change lands in the next window", async () => {
|
|
372
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
373
|
+
expireLivenessWindow();
|
|
374
|
+
cache.delete(`${namespace}-write-window:${RESOURCE_ID.toString()}`);
|
|
375
|
+
|
|
376
|
+
await service.updateLastSeen(RESOURCE_ID, otherExtra as never);
|
|
377
|
+
|
|
378
|
+
expect(writes).toHaveLength(2);
|
|
379
|
+
for (const [column, value] of Object.entries(otherExtra)) {
|
|
380
|
+
expect(lastWrite()[column]).toBe(value);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
/*
|
|
385
|
+
* THE OUTAGE, reproduced per service. Continuously-changing metadata
|
|
386
|
+
* must not be able to buy more than one write per window. Under the old
|
|
387
|
+
* throttle each of these calls issued its own UPDATE against a row
|
|
388
|
+
* shared by every node reporting into it.
|
|
389
|
+
*/
|
|
390
|
+
test("continuously changing metadata cannot force a write per batch", async () => {
|
|
391
|
+
for (let batch: number = 0; batch < 20; batch++) {
|
|
392
|
+
SingleFlight.clear();
|
|
393
|
+
await service.updateLastSeen(RESOURCE_ID, {
|
|
394
|
+
...extra,
|
|
395
|
+
...(batch % 2 === 0 ? otherExtra : {}),
|
|
396
|
+
} as never);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
expect(writes.length).toBeLessThanOrEqual(2);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (churningExtras) {
|
|
403
|
+
/*
|
|
404
|
+
* The concrete production shape for this service: a payload whose
|
|
405
|
+
* gauges move on every scrape (task counts, online device counts,
|
|
406
|
+
* capacity percentages). These are hashed into the fingerprint, so
|
|
407
|
+
* the old throttle never engaged for exactly the resources producing
|
|
408
|
+
* the most batches.
|
|
409
|
+
*/
|
|
410
|
+
test("per-scrape gauges cannot defeat the throttle", async () => {
|
|
411
|
+
for (let scrape: number = 0; scrape < 20; scrape++) {
|
|
412
|
+
SingleFlight.clear();
|
|
413
|
+
await service.updateLastSeen(
|
|
414
|
+
RESOURCE_ID,
|
|
415
|
+
churningExtras[scrape % churningExtras.length] as never,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
expect(writes.length).toBeLessThanOrEqual(2);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("the gauges are still written when a write happens", async () => {
|
|
423
|
+
await service.updateLastSeen(RESOURCE_ID, churningExtras[0] as never);
|
|
424
|
+
|
|
425
|
+
for (const [column, value] of Object.entries(churningExtras[0]!)) {
|
|
426
|
+
expect(lastWrite()[column]).toBe(value);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe("fallback when the enriched write fails", () => {
|
|
433
|
+
beforeEach(() => {
|
|
434
|
+
mockEnrichedWriteFailing();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("does not throw — ingest keeps going", async () => {
|
|
438
|
+
await expect(
|
|
439
|
+
service.updateLastSeen(RESOURCE_ID, oversizedExtra as never),
|
|
440
|
+
).resolves.toBeUndefined();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("retries with liveness only, so lastSeenAt still advances", async () => {
|
|
444
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
445
|
+
|
|
446
|
+
expect(writes).toHaveLength(2);
|
|
447
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
448
|
+
"lastSeenAt",
|
|
449
|
+
"otelCollectorStatus",
|
|
450
|
+
]);
|
|
451
|
+
expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
|
|
452
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("the retry targets the same resource", async () => {
|
|
456
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
457
|
+
|
|
458
|
+
expect(writes[1]!.id.toString()).toBe(RESOURCE_ID.toString());
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
/*
|
|
462
|
+
* The enrichment gates were claimed before the write that failed, so
|
|
463
|
+
* without releasing them the next batch would short-circuit and the
|
|
464
|
+
* retry would never happen.
|
|
465
|
+
*/
|
|
466
|
+
test("re-opens the enrichment gates so the next batch retries", async () => {
|
|
467
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
468
|
+
|
|
469
|
+
expect(deletedCacheKeys).toContain(
|
|
470
|
+
`${namespace}-fingerprint:${RESOURCE_ID.toString()}`,
|
|
471
|
+
);
|
|
472
|
+
expect(deletedCacheKeys).toContain(
|
|
473
|
+
`${namespace}-write-window:${RESOURCE_ID.toString()}`,
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("the next batch retries rather than short-circuiting", async () => {
|
|
478
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
479
|
+
writes = [];
|
|
480
|
+
expireLivenessWindow();
|
|
481
|
+
|
|
482
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
483
|
+
|
|
484
|
+
// Enriched attempt + liveness fallback, not a silent no-op.
|
|
485
|
+
expect(writes).toHaveLength(2);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("a failing cache delete does not stop the liveness retry", async () => {
|
|
489
|
+
jest
|
|
490
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
491
|
+
.mockRejectedValue(new Error("redis down"));
|
|
492
|
+
|
|
493
|
+
await service.updateLastSeen(RESOURCE_ID, oversizedExtra as never);
|
|
494
|
+
|
|
495
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
496
|
+
"lastSeenAt",
|
|
497
|
+
"otelCollectorStatus",
|
|
498
|
+
]);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("surfaces a genuinely broken database instead of hiding it", async () => {
|
|
502
|
+
// Both attempts fail — the caller must find out.
|
|
503
|
+
jest
|
|
504
|
+
.spyOn(service, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
505
|
+
.mockRejectedValue(new Error("connection terminated"));
|
|
506
|
+
|
|
507
|
+
await expect(
|
|
508
|
+
service.updateLastSeen(RESOURCE_ID, extra as never),
|
|
509
|
+
).rejects.toThrow("connection terminated");
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
describe("cache outages fail open", () => {
|
|
514
|
+
beforeEach(() => {
|
|
515
|
+
mockWritesSucceeding();
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
/*
|
|
519
|
+
* Never mark a live resource disconnected because the cache went down.
|
|
520
|
+
* Affordable only because the write cannot queue — see the SKIP LOCKED
|
|
521
|
+
* note on the write primitive.
|
|
522
|
+
*/
|
|
523
|
+
test("a liveness gate failure still writes", async () => {
|
|
524
|
+
jest
|
|
525
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
526
|
+
.mockRejectedValue(new Error("redis down"));
|
|
527
|
+
|
|
528
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
529
|
+
|
|
530
|
+
expect(writes).toHaveLength(1);
|
|
531
|
+
expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("keeps writing liveness for as long as the cache is down", async () => {
|
|
535
|
+
jest
|
|
536
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
537
|
+
.mockRejectedValue(new Error("redis down"));
|
|
538
|
+
|
|
539
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
540
|
+
SingleFlight.clear();
|
|
541
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
542
|
+
|
|
543
|
+
expect(writes).toHaveLength(2);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
/*
|
|
547
|
+
* Enrichment gets the OPPOSITE treatment: opening that gate during an
|
|
548
|
+
* outage would restore the per-batch write storm, and nothing is
|
|
549
|
+
* mis-reported by writing a descriptive column a little later.
|
|
550
|
+
*/
|
|
551
|
+
test("a change-detection failure suppresses enrichment, not liveness", async () => {
|
|
552
|
+
jest
|
|
553
|
+
.spyOn(GlobalCache, "setStringIfChanged")
|
|
554
|
+
.mockRejectedValue(new Error("redis down"));
|
|
555
|
+
|
|
556
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
557
|
+
|
|
558
|
+
expect(writes).toHaveLength(1);
|
|
559
|
+
expect(Object.keys(lastWrite()).sort()).toEqual([
|
|
560
|
+
"lastSeenAt",
|
|
561
|
+
"otelCollectorStatus",
|
|
562
|
+
]);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
describe("losing the row-lock race", () => {
|
|
567
|
+
beforeEach(() => {
|
|
568
|
+
mockWritesSucceeding();
|
|
569
|
+
writeLands = false;
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("uses the non-blocking write primitive", async () => {
|
|
573
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
574
|
+
|
|
575
|
+
expect(
|
|
576
|
+
service.updateColumnsByIdIfUnlockedWithoutHooks,
|
|
577
|
+
).toHaveBeenCalled();
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/*
|
|
581
|
+
* A skipped liveness bump is a non-event — the writer holding the lock
|
|
582
|
+
* is storing the same timestamp. Retrying it would generate pointless
|
|
583
|
+
* statements against a row that is demonstrably being written.
|
|
584
|
+
*/
|
|
585
|
+
test("a skipped liveness-only write is dropped silently", async () => {
|
|
586
|
+
await service.updateLastSeen(RESOURCE_ID);
|
|
587
|
+
|
|
588
|
+
expect(deletedCacheKeys).toHaveLength(0);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
/*
|
|
592
|
+
* Enrichment is different: the lock holder may be writing an OLDER
|
|
593
|
+
* payload, and the gates this call claimed would suppress the newer one
|
|
594
|
+
* for the rest of the window.
|
|
595
|
+
*/
|
|
596
|
+
test("a skipped enrichment write re-opens the gates", async () => {
|
|
597
|
+
await service.updateLastSeen(RESOURCE_ID, extra as never);
|
|
598
|
+
|
|
599
|
+
expect(deletedCacheKeys).toContain(
|
|
600
|
+
`${namespace}-fingerprint:${RESOURCE_ID.toString()}`,
|
|
601
|
+
);
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
describe("liveness fallback coverage", () => {
|
|
608
|
+
test("covers every service that writes liveness on the hook-free path", () => {
|
|
609
|
+
/*
|
|
610
|
+
* If a twelfth resource type grows an updateLastSeen, it belongs in
|
|
611
|
+
* SERVICE_CASES — that is the whole point of this suite. Bumping this
|
|
612
|
+
* number without adding the case defeats it.
|
|
613
|
+
*/
|
|
614
|
+
expect(SERVICE_CASES).toHaveLength(11);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test("no service is listed twice", () => {
|
|
618
|
+
const names: Array<string> = SERVICE_CASES.map((c: ServiceCase) => {
|
|
619
|
+
return c.name;
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
expect(new Set(names).size).toBe(names.length);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
/*
|
|
626
|
+
* The namespace in each case must match the one the service actually uses,
|
|
627
|
+
* or the gate assertions above silently check keys nobody writes.
|
|
628
|
+
*/
|
|
629
|
+
test("no namespace is shared between services", () => {
|
|
630
|
+
const namespaces: Array<string> = SERVICE_CASES.map((c: ServiceCase) => {
|
|
631
|
+
return c.namespace;
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
expect(new Set(namespaces).size).toBe(namespaces.length);
|
|
635
|
+
});
|
|
636
|
+
});
|