@oneuptime/common 14.0.2 → 14.0.5
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/Alert.ts +55 -0
- package/Models/DatabaseModels/AlertFeed.ts +2 -0
- package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
- package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
- package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
- package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
- package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
- package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
- package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/IncidentAlert.ts +540 -0
- package/Models/DatabaseModels/IncidentFeed.ts +2 -0
- package/Models/DatabaseModels/Index.ts +18 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Models/DatabaseModels/RumApplication.ts +52 -1
- package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/MultipartFormData.ts +1 -1
- package/Server/Middleware/TelemetryIngest.ts +15 -8
- package/Server/Services/AlertReminderRuleService.ts +100 -24
- package/Server/Services/DatabaseServerEndpointService.ts +895 -0
- package/Server/Services/DatabaseServerFeedService.ts +128 -0
- package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
- package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
- package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
- package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
- package/Server/Services/DatabaseServerService.ts +4546 -0
- package/Server/Services/DatabaseService.ts +14 -8
- package/Server/Services/DockerResourceService.ts +43 -2
- package/Server/Services/IncidentAlertService.ts +1811 -0
- package/Server/Services/IncidentReminderRuleService.ts +103 -23
- package/Server/Services/IncidentService.ts +210 -10
- package/Server/Services/IncidentStateTimelineService.ts +24 -0
- package/Server/Services/Index.ts +18 -0
- package/Server/Services/InventoryItemService.ts +113 -5
- package/Server/Services/IoTDeviceService.ts +145 -37
- package/Server/Services/OpenTelemetryIngestService.ts +19 -0
- package/Server/Services/PodmanResourceService.ts +43 -2
- package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
- package/Server/Services/ServiceService.ts +1 -1
- package/Server/Services/TelemetryUsageBillingService.ts +29 -2
- package/Server/Services/UserProjectSsoConsentService.ts +74 -0
- package/Server/Services/UserService.ts +14 -1
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
- package/Server/Types/Database/QueryHelper.ts +30 -1
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
- package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
- package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
- package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
- package/Server/Utils/Monitor/MonitorResource.ts +47 -11
- package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
- package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
- package/Server/Utils/StartServer.ts +26 -6
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
- package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
- package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
- package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
- package/Server/Utils/Telemetry/Telemetry.ts +16 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
- package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
- package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
- package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
- package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
- package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
- package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
- package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
- package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
- package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
- package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
- package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
- package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
- package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
- package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
- package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
- package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
- package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
- package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
- package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
- package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
- package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
- package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
- package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
- package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
- package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
- package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
- package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
- package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
- package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
- package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
- package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
- package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
- package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
- package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
- package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
- package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
- package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
- package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
- package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
- package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
- package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
- package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
- package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
- package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
- package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
- package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
- package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
- package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
- package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
- package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
- package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
- package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
- package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
- package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
- package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
- package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
- package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
- package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
- package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
- package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
- package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
- package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
- package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
- package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
- package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
- package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
- package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
- package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
- package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
- package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
- package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
- package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
- package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
- package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
- package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
- package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
- package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
- package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
- package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
- package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
- package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
- package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
- package/Tests/ClassStrings.ts +178 -0
- package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
- package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
- package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/RenderedMarkup.ts +518 -0
- package/Tests/ResponsiveVisibility.test.ts +490 -4
- package/Tests/ResponsiveVisibility.ts +505 -52
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
- package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
- package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
- package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
- package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
- package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
- package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
- package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
- package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
- package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
- package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
- package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
- package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
- package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
- package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
- package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
- package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
- package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
- package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
- package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
- package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
- package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
- package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
- package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
- package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
- package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
- package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
- package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
- package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
- package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
- package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
- package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
- package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
- package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
- package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
- package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
- package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
- package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
- package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
- package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
- package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
- package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
- package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
- package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
- package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
- package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
- package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
- package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
- package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
- package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
- package/Tests/Types/Rum/WebVitals.test.ts +173 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
- package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
- package/Tests/UI/Components/Card.test.tsx +93 -2
- package/Tests/UI/Components/FeedOptions.test.ts +6 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
- package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
- package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
- package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
- package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
- package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
- package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
- package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
- package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
- package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
- package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
- package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
- package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
- package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
- package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
- package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
- package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
- package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
- package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
- package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +80 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +23 -0
- package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
- package/Tests/UI/Utils/Navigation.test.ts +102 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
- package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
- package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
- package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
- package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
- package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
- package/Tests/Utils/ValueFormatter.test.ts +50 -0
- package/Tests/tsconfig.json +16 -0
- package/Types/AI/AIResourceContext.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +2 -1
- package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
- package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
- package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
- package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
- package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
- package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
- package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
- package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Incident/IncidentAlertLink.ts +38 -0
- package/Types/Monitor/CephAlertTemplates.ts +18 -0
- package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
- package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
- package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
- package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
- package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
- package/Types/Permission.ts +315 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
- package/Types/Rules/RuleRun.ts +12 -0
- package/Types/Rum/SessionReplay.ts +25 -6
- package/Types/Rum/WebVitals.ts +52 -0
- package/Types/Telemetry/EntityType.ts +23 -0
- package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
- package/Types/Telemetry/ServiceType.ts +8 -0
- package/UI/Components/Banner/Banner.tsx +1 -1
- package/UI/Components/Card/Card.tsx +1 -1
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
- package/UI/Components/Date/StartAndEndDate.tsx +5 -5
- package/UI/Components/Detail/FieldLabel.tsx +8 -0
- package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
- package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
- package/UI/Components/Forms/BasicForm.tsx +1 -1
- package/UI/Components/Header/Header.tsx +1 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
- package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
- package/UI/Components/InfoCard/InfoCard.tsx +47 -1
- package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
- package/UI/Components/Modal/ModalFooter.tsx +1 -1
- package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +99 -21
- package/UI/Components/ModelTable/Column.ts +5 -0
- package/UI/Components/Navbar/NavBar.tsx +9 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
- package/UI/Components/Page/Page.tsx +1 -1
- package/UI/Components/Pagination/Pagination.tsx +3 -3
- package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
- package/UI/Components/SideMenu/SideMenu.tsx +4 -1
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
- package/UI/Components/StatusPage/ResourceList.tsx +6 -6
- package/UI/Components/Table/TableHeader.tsx +47 -2
- package/UI/Components/Table/Types/Column.ts +7 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
- package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
- package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
- package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
- package/UI/Styles/Theme.css +8 -1
- package/UI/Utils/Navigation.ts +89 -42
- package/UI/Utils/PermissionGate.ts +11 -2
- package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
- package/UI/Utils/TelemetryService.ts +4 -3
- package/Utils/Incident/IncidentFromAlerts.ts +516 -0
- package/Utils/LabelRuleImportExport.ts +2 -0
- package/Utils/MetricUnitUtil.ts +103 -11
- package/Utils/Monitor/MetricValueFormatter.ts +66 -13
- package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
- package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
- package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
- package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
- package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
- package/Utils/Rum/SessionTraceState.ts +212 -0
- package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
- package/Utils/Telemetry/EntityKey.ts +176 -0
- package/Utils/ValueFormatter.ts +16 -0
- package/build/dist/Models/DatabaseModels/Alert.js +54 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js +54 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
- package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +18 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
- package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
- package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
- package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseService.js +8 -4
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +30 -3
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
- package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
- package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +164 -12
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +18 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +88 -5
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +111 -24
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +30 -3
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +1 -1
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
- package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
- package/build/dist/Server/Services/UserService.js +13 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +20 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
- package/build/dist/Types/AI/AIResourceContext.js +7 -0
- package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
- package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
- package/build/dist/Types/Permission.js +276 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +25 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +23 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +8 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/Banner/Banner.js +1 -1
- package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
- package/build/dist/UI/Components/Card/Card.js +1 -1
- package/build/dist/UI/Components/Card/Card.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
- package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
- package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
- package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
- package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +70 -18
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +1 -1
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +18 -2
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +66 -33
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +4 -2
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
- package/build/dist/UI/Utils/TelemetryService.js +3 -2
- package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
- package/build/dist/Utils/LabelRuleImportExport.js +2 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +77 -13
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
- package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
- package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/ValueFormatter.js +15 -0
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +1 -1
- package/tsconfig.tests.json +7 -5
|
@@ -0,0 +1,1777 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
jest,
|
|
8
|
+
test,
|
|
9
|
+
} from "@jest/globals";
|
|
10
|
+
import {
|
|
11
|
+
act,
|
|
12
|
+
cleanup,
|
|
13
|
+
fireEvent,
|
|
14
|
+
render,
|
|
15
|
+
screen,
|
|
16
|
+
within,
|
|
17
|
+
} from "@testing-library/react";
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* The (i) tooltips on the Ceph cluster pages: the overview, the OSD and
|
|
23
|
+
* pool lists and detail pages, the daemons table, the Insights rate charts
|
|
24
|
+
* and the Clusters list.
|
|
25
|
+
*
|
|
26
|
+
* Each page is rendered for real, with only the network, the charts and
|
|
27
|
+
* the heavy shared cards replaced, and every metric title is checked for an
|
|
28
|
+
* (i) whose tooltip is the matching CEPH_METRIC_DESCRIPTIONS entry - and
|
|
29
|
+
* pure metadata (names, hosts, versions, "Last Seen") is checked for having
|
|
30
|
+
* none. Where a description makes a claim about the data ("the last 10
|
|
31
|
+
* minutes", "counted twice", "left out when older than 15 minutes",
|
|
32
|
+
* "ignores the chart time range"), the page is fed data that tells the
|
|
33
|
+
* difference and the claim is checked against what it shows.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const CLUSTER_ID: string = "0193c0de-6666-4aaa-8bbb-000000000006";
|
|
37
|
+
const NOW: Date = new Date("2026-09-24T12:00:00.000Z");
|
|
38
|
+
const MINUTE: number = 60 * 1000;
|
|
39
|
+
const GIB: number = 1024 * 1024 * 1024;
|
|
40
|
+
// Where one sentence ends and the next begins.
|
|
41
|
+
const SENTENCE_BREAK: RegExp = /(?<=[.!?])\s+(?=[A-Z])/;
|
|
42
|
+
|
|
43
|
+
const modelGetItemMock: MockFunction = getJestMockFunction();
|
|
44
|
+
const modelGetListMock: MockFunction = getJestMockFunction();
|
|
45
|
+
const analyticsGetListMock: MockFunction = getJestMockFunction();
|
|
46
|
+
const analyticsAggregateMock: MockFunction = getJestMockFunction();
|
|
47
|
+
const rateChartMock: MockFunction = getJestMockFunction();
|
|
48
|
+
const modelTableMock: MockFunction = getJestMockFunction();
|
|
49
|
+
|
|
50
|
+
let mockLastParam: string = "osd.1";
|
|
51
|
+
|
|
52
|
+
jest.mock("react-i18next", () => {
|
|
53
|
+
return {
|
|
54
|
+
useTranslation: () => {
|
|
55
|
+
return {
|
|
56
|
+
t: (key: string, opts?: { defaultValue?: string }): string => {
|
|
57
|
+
return opts?.defaultValue ?? key;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
65
|
+
return {
|
|
66
|
+
__esModule: true,
|
|
67
|
+
default: {
|
|
68
|
+
getItem: (...args: Array<unknown>): unknown => {
|
|
69
|
+
return modelGetItemMock(...args);
|
|
70
|
+
},
|
|
71
|
+
getList: (...args: Array<unknown>): unknown => {
|
|
72
|
+
return modelGetListMock(...args);
|
|
73
|
+
},
|
|
74
|
+
count: (): Promise<number> => {
|
|
75
|
+
return Promise.resolve(1);
|
|
76
|
+
},
|
|
77
|
+
getCommonHeaders: (): Record<string, string> => {
|
|
78
|
+
return {};
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
|
|
85
|
+
return {
|
|
86
|
+
__esModule: true,
|
|
87
|
+
default: {
|
|
88
|
+
getList: (...args: Array<unknown>): unknown => {
|
|
89
|
+
return analyticsGetListMock(...args);
|
|
90
|
+
},
|
|
91
|
+
aggregate: (...args: Array<unknown>): unknown => {
|
|
92
|
+
return analyticsAggregateMock(...args);
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
99
|
+
return {
|
|
100
|
+
__esModule: true,
|
|
101
|
+
default: {
|
|
102
|
+
getLastParamAsObjectID: (): unknown => {
|
|
103
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
104
|
+
"../../../Types/ObjectID",
|
|
105
|
+
) as { default: new (id: string) => unknown };
|
|
106
|
+
return new ObjectIDType("0193c0de-6666-4aaa-8bbb-000000000006");
|
|
107
|
+
},
|
|
108
|
+
getLastParamAsString: (): string => {
|
|
109
|
+
return mockLastParam;
|
|
110
|
+
},
|
|
111
|
+
navigate: (): void => {},
|
|
112
|
+
isOnThisPage: (): boolean => {
|
|
113
|
+
return false;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
120
|
+
return {
|
|
121
|
+
__esModule: true,
|
|
122
|
+
default: {
|
|
123
|
+
getCurrentProjectId: (): unknown => {
|
|
124
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
125
|
+
"../../../Types/ObjectID",
|
|
126
|
+
) as { default: new (id: string) => unknown };
|
|
127
|
+
return new ObjectIDType("10000000-0000-4000-8000-000000000001");
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* The Golden Signals and Insights cards draw ChartGroup charts (which carry
|
|
135
|
+
* their own visible descriptions); only the rate-chart headers inside them
|
|
136
|
+
* are this suite's business, so the card renders its title and children and
|
|
137
|
+
* offers a way to change the range.
|
|
138
|
+
*/
|
|
139
|
+
jest.mock(
|
|
140
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Metrics/EmbeddedMetricCard",
|
|
141
|
+
() => {
|
|
142
|
+
return {
|
|
143
|
+
__esModule: true,
|
|
144
|
+
default: (props: {
|
|
145
|
+
title?: React.ReactNode;
|
|
146
|
+
children?: React.ReactNode;
|
|
147
|
+
onTimeRangeChange?: (range: unknown) => void;
|
|
148
|
+
}) => {
|
|
149
|
+
return (
|
|
150
|
+
<section data-testid="embedded-metric-card">
|
|
151
|
+
<div>{props.title}</div>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
onClick={() => {
|
|
155
|
+
props.onTimeRangeChange?.({ range: "Past 1 Day" });
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
Pick past day
|
|
159
|
+
</button>
|
|
160
|
+
{props.children}
|
|
161
|
+
</section>
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
jest.mock(
|
|
169
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Ceph/CephRateChart",
|
|
170
|
+
() => {
|
|
171
|
+
return {
|
|
172
|
+
__esModule: true,
|
|
173
|
+
default: (props: { series: Array<{ metricName: string }> }) => {
|
|
174
|
+
rateChartMock(props);
|
|
175
|
+
return (
|
|
176
|
+
<div data-testid="ceph-rate-chart">
|
|
177
|
+
{props.series
|
|
178
|
+
.map((s: { metricName: string }) => {
|
|
179
|
+
return s.metricName;
|
|
180
|
+
})
|
|
181
|
+
.join(",")}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
jest.mock(
|
|
190
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Infrastructure/ResourceMetricsTab",
|
|
191
|
+
() => {
|
|
192
|
+
return {
|
|
193
|
+
__esModule: true,
|
|
194
|
+
default: (props: {
|
|
195
|
+
renderExtraCharts?: (range: {
|
|
196
|
+
startValue: Date;
|
|
197
|
+
endValue: Date;
|
|
198
|
+
}) => React.ReactNode;
|
|
199
|
+
}) => {
|
|
200
|
+
return (
|
|
201
|
+
<div data-testid="resource-metrics-tab">
|
|
202
|
+
{props.renderExtraCharts
|
|
203
|
+
? props.renderExtraCharts({
|
|
204
|
+
startValue: new Date("2026-09-24T11:00:00.000Z"),
|
|
205
|
+
endValue: new Date("2026-09-24T12:00:00.000Z"),
|
|
206
|
+
})
|
|
207
|
+
: null}
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
jest.mock("../../../UI/Components/ModelDetail/CardModelDetail", () => {
|
|
216
|
+
return {
|
|
217
|
+
__esModule: true,
|
|
218
|
+
default: () => {
|
|
219
|
+
return <div data-testid="card-model-detail" />;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
jest.mock(
|
|
225
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/AutoRefreshControl",
|
|
226
|
+
() => {
|
|
227
|
+
return {
|
|
228
|
+
__esModule: true,
|
|
229
|
+
default: () => {
|
|
230
|
+
return <div data-testid="auto-refresh-control" />;
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
jest.mock(
|
|
237
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/useAutoRefresh",
|
|
238
|
+
() => {
|
|
239
|
+
return {
|
|
240
|
+
__esModule: true,
|
|
241
|
+
default: () => {
|
|
242
|
+
return {
|
|
243
|
+
autoRefreshInterval: "off",
|
|
244
|
+
setAutoRefreshInterval: () => {},
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
/*
|
|
252
|
+
* The Clusters list is a ModelTable. It is replaced by a recorder, so the
|
|
253
|
+
* columns the page hands it - titles, header tooltips and cell renderers -
|
|
254
|
+
* can be read and run without a server.
|
|
255
|
+
*/
|
|
256
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
257
|
+
return {
|
|
258
|
+
__esModule: true,
|
|
259
|
+
default: (props: unknown) => {
|
|
260
|
+
modelTableMock(props);
|
|
261
|
+
return <div data-testid="model-table" />;
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
jest.mock(
|
|
267
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
268
|
+
() => {
|
|
269
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
270
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
271
|
+
) as Record<string, unknown>;
|
|
272
|
+
return {
|
|
273
|
+
...actual,
|
|
274
|
+
__esModule: true,
|
|
275
|
+
default: () => {
|
|
276
|
+
return {
|
|
277
|
+
getOwnersForResource: () => {
|
|
278
|
+
return [];
|
|
279
|
+
},
|
|
280
|
+
isLoadingOwners: false,
|
|
281
|
+
onResourcesFetched: () => {},
|
|
282
|
+
filterBar: null,
|
|
283
|
+
mergeFiltersIntoQuery: (query: unknown) => {
|
|
284
|
+
return query;
|
|
285
|
+
},
|
|
286
|
+
facetSaveState: undefined,
|
|
287
|
+
restoreFacetState: () => {},
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkLabelActions", () => {
|
|
295
|
+
return {
|
|
296
|
+
__esModule: true,
|
|
297
|
+
default: () => {
|
|
298
|
+
return { bulkActions: [], modals: null };
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkOwnerActions", () => {
|
|
304
|
+
return {
|
|
305
|
+
__esModule: true,
|
|
306
|
+
default: () => {
|
|
307
|
+
return { bulkActions: [], modals: null };
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkArchiveActions", () => {
|
|
313
|
+
return {
|
|
314
|
+
__esModule: true,
|
|
315
|
+
default: () => {
|
|
316
|
+
return { archiveBulkActions: [] };
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
import CephClusterOverview, {
|
|
322
|
+
CephMetricTitle,
|
|
323
|
+
OsdMatrix,
|
|
324
|
+
OsdStateCell,
|
|
325
|
+
OsdStateCellView,
|
|
326
|
+
buildOsdStateCells,
|
|
327
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/Index";
|
|
328
|
+
import CephClusterOsds from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/Osds";
|
|
329
|
+
import CephClusterOsdDetail from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/OsdDetail";
|
|
330
|
+
import CephClusterPools from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/Pools";
|
|
331
|
+
import CephClusterPoolDetail from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/PoolDetail";
|
|
332
|
+
import CephClusterDaemons from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/Daemons";
|
|
333
|
+
import CephClusterInsights from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/View/Insights";
|
|
334
|
+
import CephClusters from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/Clusters";
|
|
335
|
+
import {
|
|
336
|
+
CEPH_METRIC_DESCRIPTIONS,
|
|
337
|
+
CephMetric,
|
|
338
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/CephMetricDescriptions";
|
|
339
|
+
import { METRIC_STALE_MS } from "../../../../App/FeatureSet/Dashboard/src/Pages/Ceph/Utils/CephResourceUtils";
|
|
340
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
341
|
+
import {
|
|
342
|
+
expectReadableDescriptionRecord,
|
|
343
|
+
expectTitleExplained,
|
|
344
|
+
} from "./MetricDescriptionRules";
|
|
345
|
+
|
|
346
|
+
const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
|
|
347
|
+
|
|
348
|
+
// ---------------------------------------------------------------- fixtures
|
|
349
|
+
|
|
350
|
+
function minutesAgo(minutes: number): Date {
|
|
351
|
+
return new Date(NOW.getTime() - minutes * MINUTE);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const CLUSTER: Record<string, unknown> = {
|
|
355
|
+
_id: CLUSTER_ID,
|
|
356
|
+
name: "ceph-prod",
|
|
357
|
+
description: "",
|
|
358
|
+
fsid: "3b0c1f7e-aaaa-bbbb-cccc-000000000001",
|
|
359
|
+
otelCollectorStatus: "connected",
|
|
360
|
+
lastSeenAt: minutesAgo(1),
|
|
361
|
+
cephVersion: "18.2.4",
|
|
362
|
+
agentVersion: "1.2.3",
|
|
363
|
+
// HEALTH_WARN, so the Active Health Checks card is shown.
|
|
364
|
+
healthStatus: 1,
|
|
365
|
+
monCount: 3,
|
|
366
|
+
osdCount: 4,
|
|
367
|
+
osdUpCount: 3,
|
|
368
|
+
osdInCount: 3,
|
|
369
|
+
poolCount: 3,
|
|
370
|
+
capacityUsedPercent: 12.34,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
type Row = Record<string, unknown>;
|
|
374
|
+
|
|
375
|
+
const OSD_ROWS: Array<Row> = [
|
|
376
|
+
{
|
|
377
|
+
kind: "Osd",
|
|
378
|
+
externalId: "osd.0",
|
|
379
|
+
hostname: "node-a",
|
|
380
|
+
deviceClass: "ssd",
|
|
381
|
+
isUp: true,
|
|
382
|
+
isIn: true,
|
|
383
|
+
statBytes: 1000 * GIB,
|
|
384
|
+
statBytesUsed: 250 * GIB,
|
|
385
|
+
pgCount: 97,
|
|
386
|
+
applyLatencyMs: 4,
|
|
387
|
+
commitLatencyMs: 5,
|
|
388
|
+
metricsUpdatedAt: minutesAgo(1),
|
|
389
|
+
createdAt: minutesAgo(60 * 24 * 3),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
kind: "Osd",
|
|
393
|
+
externalId: "osd.1",
|
|
394
|
+
hostname: "node-b",
|
|
395
|
+
deviceClass: "hdd",
|
|
396
|
+
isUp: true,
|
|
397
|
+
isIn: true,
|
|
398
|
+
statBytes: 2000 * GIB,
|
|
399
|
+
statBytesUsed: 500 * GIB,
|
|
400
|
+
pgCount: 101,
|
|
401
|
+
applyLatencyMs: 12,
|
|
402
|
+
commitLatencyMs: 13,
|
|
403
|
+
daemonVersion: "18.2.4",
|
|
404
|
+
lastSeenAt: minutesAgo(1),
|
|
405
|
+
metricsUpdatedAt: minutesAgo(1),
|
|
406
|
+
createdAt: minutesAgo(60 * 24 * 3),
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
kind: "Osd",
|
|
410
|
+
externalId: "osd.2",
|
|
411
|
+
hostname: "node-c",
|
|
412
|
+
deviceClass: "hdd",
|
|
413
|
+
isUp: true,
|
|
414
|
+
isIn: false,
|
|
415
|
+
statBytes: 2000 * GIB,
|
|
416
|
+
statBytesUsed: 10 * GIB,
|
|
417
|
+
pgCount: 0,
|
|
418
|
+
metricsUpdatedAt: minutesAgo(1),
|
|
419
|
+
createdAt: minutesAgo(60 * 24 * 3),
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
// Down + In, and its figures are 20 minutes old.
|
|
423
|
+
kind: "Osd",
|
|
424
|
+
externalId: "osd.3",
|
|
425
|
+
hostname: "node-d",
|
|
426
|
+
deviceClass: "hdd",
|
|
427
|
+
isUp: false,
|
|
428
|
+
isIn: true,
|
|
429
|
+
statBytes: 2000 * GIB,
|
|
430
|
+
statBytesUsed: 1900 * GIB,
|
|
431
|
+
pgCount: 88,
|
|
432
|
+
applyLatencyMs: 900,
|
|
433
|
+
commitLatencyMs: 950,
|
|
434
|
+
metricsUpdatedAt: minutesAgo(20),
|
|
435
|
+
createdAt: minutesAgo(60 * 24 * 3),
|
|
436
|
+
},
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
const POOL_ROWS: Array<Row> = [
|
|
440
|
+
{
|
|
441
|
+
kind: "Pool",
|
|
442
|
+
externalId: "1",
|
|
443
|
+
name: "rbd",
|
|
444
|
+
storedBytes: 300 * GIB,
|
|
445
|
+
maxAvailBytes: 700 * GIB,
|
|
446
|
+
objects: 76800,
|
|
447
|
+
metricsUpdatedAt: minutesAgo(1),
|
|
448
|
+
lastSeenAt: minutesAgo(1),
|
|
449
|
+
createdAt: minutesAgo(60 * 24 * 3),
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
kind: "Pool",
|
|
453
|
+
externalId: "2",
|
|
454
|
+
name: "cephfs_data",
|
|
455
|
+
storedBytes: 100 * GIB,
|
|
456
|
+
maxAvailBytes: 100 * GIB,
|
|
457
|
+
objects: 25600,
|
|
458
|
+
metricsUpdatedAt: minutesAgo(1),
|
|
459
|
+
lastSeenAt: minutesAgo(1),
|
|
460
|
+
createdAt: minutesAgo(60 * 24 * 3),
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
// The biggest and fullest pool - but its figures are 20 minutes old.
|
|
464
|
+
kind: "Pool",
|
|
465
|
+
externalId: "3",
|
|
466
|
+
name: "stale-archive",
|
|
467
|
+
storedBytes: 5000 * GIB,
|
|
468
|
+
maxAvailBytes: GIB,
|
|
469
|
+
objects: 999999,
|
|
470
|
+
metricsUpdatedAt: minutesAgo(20),
|
|
471
|
+
lastSeenAt: minutesAgo(20),
|
|
472
|
+
createdAt: minutesAgo(60 * 24 * 3),
|
|
473
|
+
},
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
const MON_ROWS: Array<Row> = [
|
|
477
|
+
{
|
|
478
|
+
kind: "Mon",
|
|
479
|
+
externalId: "mon.a",
|
|
480
|
+
hostname: "node-a",
|
|
481
|
+
daemonVersion: "18.2.4",
|
|
482
|
+
inQuorum: true,
|
|
483
|
+
lastSeenAt: minutesAgo(1),
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
kind: "Mon",
|
|
487
|
+
externalId: "mon.b",
|
|
488
|
+
hostname: "node-b",
|
|
489
|
+
daemonVersion: "18.2.4",
|
|
490
|
+
inQuorum: true,
|
|
491
|
+
lastSeenAt: minutesAgo(1),
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
// Last seen 20 minutes ago: Stale, whatever its quorum flag says.
|
|
495
|
+
kind: "Mon",
|
|
496
|
+
externalId: "mon.c",
|
|
497
|
+
hostname: "node-c",
|
|
498
|
+
daemonVersion: "18.2.4",
|
|
499
|
+
inQuorum: true,
|
|
500
|
+
lastSeenAt: minutesAgo(20),
|
|
501
|
+
},
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
const MGR_ROWS: Array<Row> = [
|
|
505
|
+
{
|
|
506
|
+
kind: "Mgr",
|
|
507
|
+
externalId: "mgr.x",
|
|
508
|
+
hostname: "node-a",
|
|
509
|
+
daemonVersion: "18.2.4",
|
|
510
|
+
lastSeenAt: minutesAgo(2),
|
|
511
|
+
},
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
const ROWS_BY_KIND: Record<string, Array<Row>> = {
|
|
515
|
+
Osd: OSD_ROWS,
|
|
516
|
+
Pool: POOL_ROWS,
|
|
517
|
+
Mon: MON_ROWS,
|
|
518
|
+
Mgr: MGR_ROWS,
|
|
519
|
+
Mds: [],
|
|
520
|
+
Rgw: [],
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
function perPool(values: Record<string, number>): Array<Row> {
|
|
524
|
+
return Object.entries(values).map(([poolId, value]: [string, number]) => {
|
|
525
|
+
return {
|
|
526
|
+
time: minutesAgo(1),
|
|
527
|
+
value: value,
|
|
528
|
+
attributes: { pool_id: poolId },
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/*
|
|
534
|
+
* PG counts per pool. Pool 1 has five groups that are BOTH degraded and
|
|
535
|
+
* undersized - the overview adds the two states, so they count twice.
|
|
536
|
+
*/
|
|
537
|
+
const PG_SERIES: Record<string, Array<Row>> = {
|
|
538
|
+
ceph_pg_total: perPool({ "1": 128, "2": 64 }),
|
|
539
|
+
ceph_pg_active: perPool({ "1": 128, "2": 60 }),
|
|
540
|
+
ceph_pg_clean: perPool({ "1": 120, "2": 60 }),
|
|
541
|
+
ceph_pg_degraded: perPool({ "1": 5, "2": 1 }),
|
|
542
|
+
ceph_pg_undersized: perPool({ "1": 5 }),
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
const HEALTH_DETAIL: Array<Row> = [
|
|
546
|
+
{
|
|
547
|
+
time: minutesAgo(1),
|
|
548
|
+
value: 1,
|
|
549
|
+
attributes: { name: "OSD_DOWN", severity: "HEALTH_WARN" },
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
time: minutesAgo(1),
|
|
553
|
+
value: 0,
|
|
554
|
+
attributes: { name: "MON_DOWN", severity: "HEALTH_WARN" },
|
|
555
|
+
},
|
|
556
|
+
];
|
|
557
|
+
|
|
558
|
+
interface CapturedQuery {
|
|
559
|
+
name: string;
|
|
560
|
+
start: Date;
|
|
561
|
+
end: Date;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function listQuery(args: unknown): CapturedQuery {
|
|
565
|
+
const query: {
|
|
566
|
+
name: string;
|
|
567
|
+
time: { startValue: Date; endValue: Date };
|
|
568
|
+
} = (args as { query: CapturedQuery & { time: never } }).query as never;
|
|
569
|
+
return {
|
|
570
|
+
name: query.name,
|
|
571
|
+
start: query.time.startValue,
|
|
572
|
+
end: query.time.endValue,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function aggregateQuery(args: unknown): CapturedQuery {
|
|
577
|
+
const aggregateBy: {
|
|
578
|
+
query: { name: string };
|
|
579
|
+
startTimestamp: Date;
|
|
580
|
+
endTimestamp: Date;
|
|
581
|
+
} = (args as { aggregateBy: never }).aggregateBy;
|
|
582
|
+
return {
|
|
583
|
+
name: aggregateBy.query.name,
|
|
584
|
+
start: aggregateBy.startTimestamp,
|
|
585
|
+
end: aggregateBy.endTimestamp,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function counterSeries(
|
|
590
|
+
poolId: string,
|
|
591
|
+
startValue: number,
|
|
592
|
+
perSecond: number,
|
|
593
|
+
): Array<Row> {
|
|
594
|
+
return [0, 1, 2].map((i: number) => {
|
|
595
|
+
return {
|
|
596
|
+
timestamp: minutesAgo(10 - i * 5),
|
|
597
|
+
value: startValue + perSecond * i * 5 * 60,
|
|
598
|
+
attributes: { pool_id: poolId },
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function arrange(): void {
|
|
604
|
+
modelGetItemMock.mockImplementation(async () => {
|
|
605
|
+
return CLUSTER;
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
modelGetListMock.mockImplementation(async (args: unknown) => {
|
|
609
|
+
const query: Record<string, unknown> = (
|
|
610
|
+
args as { query: Record<string, unknown> }
|
|
611
|
+
).query;
|
|
612
|
+
const rows: Array<Row> = ROWS_BY_KIND[String(query["kind"])] || [];
|
|
613
|
+
const filtered: Array<Row> = query["externalId"]
|
|
614
|
+
? rows.filter((row: Row) => {
|
|
615
|
+
return row["externalId"] === query["externalId"];
|
|
616
|
+
})
|
|
617
|
+
: rows;
|
|
618
|
+
return { data: filtered, count: filtered.length, skip: 0, limit: 100 };
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
analyticsGetListMock.mockImplementation(async (args: unknown) => {
|
|
622
|
+
const name: string = listQuery(args).name;
|
|
623
|
+
if (name === "ceph_health_detail") {
|
|
624
|
+
return { data: HEALTH_DETAIL, count: HEALTH_DETAIL.length };
|
|
625
|
+
}
|
|
626
|
+
if (name === "ceph_cluster_total_bytes") {
|
|
627
|
+
return {
|
|
628
|
+
data: [{ time: minutesAgo(1), value: 1000 * GIB, attributes: {} }],
|
|
629
|
+
count: 1,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
const rows: Array<Row> = PG_SERIES[name] || [];
|
|
633
|
+
return { data: rows, count: rows.length };
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
analyticsAggregateMock.mockImplementation(async (args: unknown) => {
|
|
637
|
+
const name: string = aggregateQuery(args).name;
|
|
638
|
+
if (name === "ceph_cluster_total_used_bytes") {
|
|
639
|
+
return {
|
|
640
|
+
data: [
|
|
641
|
+
{ timestamp: minutesAgo(120), value: 100 * GIB },
|
|
642
|
+
{ timestamp: minutesAgo(60), value: 200 * GIB },
|
|
643
|
+
{ timestamp: minutesAgo(0), value: 300 * GIB },
|
|
644
|
+
],
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (name === "ceph_pool_rd") {
|
|
648
|
+
return { data: counterSeries("1", 1000, 10) };
|
|
649
|
+
}
|
|
650
|
+
if (name === "ceph_pool_wr") {
|
|
651
|
+
return { data: counterSeries("1", 5000, 2.5) };
|
|
652
|
+
}
|
|
653
|
+
if (name === "ceph_pool_stored") {
|
|
654
|
+
return {
|
|
655
|
+
data: [
|
|
656
|
+
{ timestamp: minutesAgo(180), value: 100 * GIB },
|
|
657
|
+
{ timestamp: minutesAgo(120), value: 200 * GIB },
|
|
658
|
+
{ timestamp: minutesAgo(60), value: 250 * GIB },
|
|
659
|
+
{ timestamp: minutesAgo(0), value: 300 * GIB },
|
|
660
|
+
],
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
if (name === "ceph_pool_max_avail") {
|
|
664
|
+
return { data: [{ timestamp: minutesAgo(0), value: 700 * GIB }] };
|
|
665
|
+
}
|
|
666
|
+
return { data: [] };
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ----------------------------------------------------------------- helpers
|
|
671
|
+
|
|
672
|
+
async function flush(): Promise<void> {
|
|
673
|
+
for (let i: number = 0; i < 8; i++) {
|
|
674
|
+
await act(async () => {
|
|
675
|
+
await Promise.resolve();
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function infoButton(title: string): HTMLElement {
|
|
681
|
+
const found: Array<HTMLElement> = screen.getAllByRole("button", {
|
|
682
|
+
name: `About ${title}`,
|
|
683
|
+
});
|
|
684
|
+
expect(found).toHaveLength(1);
|
|
685
|
+
return found[0]!;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function allInfoButtons(): Array<HTMLElement> {
|
|
689
|
+
return screen.queryAllByRole("button", { name: /^About / });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function tooltipTextOf(button: HTMLElement): Promise<string> {
|
|
693
|
+
fireEvent.mouseEnter(button);
|
|
694
|
+
await act(async () => {
|
|
695
|
+
jest.advanceTimersByTime(200);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const describedBy: string | null = button.getAttribute("aria-describedby");
|
|
699
|
+
expect(describedBy).toBeTruthy();
|
|
700
|
+
|
|
701
|
+
const text: string =
|
|
702
|
+
document.getElementById(describedBy as string)?.textContent || "";
|
|
703
|
+
|
|
704
|
+
fireEvent.mouseLeave(button);
|
|
705
|
+
|
|
706
|
+
return text;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function expectExplained(title: string, key: CephMetric): Promise<void> {
|
|
710
|
+
expect(await tooltipTextOf(infoButton(title))).toBe(
|
|
711
|
+
CEPH_METRIC_DESCRIPTIONS[key],
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function expectNoInfo(title: string): void {
|
|
716
|
+
expect(
|
|
717
|
+
screen.queryByRole("button", { name: `About ${title}` }),
|
|
718
|
+
).not.toBeInTheDocument();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/*
|
|
722
|
+
* The (i) is a button: it must never sit inside another button or a link,
|
|
723
|
+
* where it would be invalid HTML and would navigate on click.
|
|
724
|
+
*/
|
|
725
|
+
function expectNoNestedInfoButtons(): void {
|
|
726
|
+
for (const button of allInfoButtons()) {
|
|
727
|
+
expect(button.parentElement?.closest("a, button")).toBeNull();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function tileByTitle(title: string): HTMLElement {
|
|
732
|
+
return infoButton(title).closest("div.rounded-xl") as HTMLElement;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function queriesFor(mock: MockFunction, name: string): Array<CapturedQuery> {
|
|
736
|
+
const read: (args: unknown) => CapturedQuery =
|
|
737
|
+
mock === analyticsAggregateMock ? aggregateQuery : listQuery;
|
|
738
|
+
return mock.mock.calls
|
|
739
|
+
.map((call: Array<unknown>) => {
|
|
740
|
+
return read(call[0]);
|
|
741
|
+
})
|
|
742
|
+
.filter((q: CapturedQuery) => {
|
|
743
|
+
return q.name === name;
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function windowMinutes(q: CapturedQuery): number {
|
|
748
|
+
return Math.round(
|
|
749
|
+
(new Date(q.end).getTime() - new Date(q.start).getTime()) / MINUTE,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
beforeEach(() => {
|
|
754
|
+
jest.useFakeTimers();
|
|
755
|
+
jest.setSystemTime(NOW);
|
|
756
|
+
mockLastParam = "osd.1";
|
|
757
|
+
for (const mock of [
|
|
758
|
+
modelGetItemMock,
|
|
759
|
+
modelGetListMock,
|
|
760
|
+
analyticsGetListMock,
|
|
761
|
+
analyticsAggregateMock,
|
|
762
|
+
rateChartMock,
|
|
763
|
+
modelTableMock,
|
|
764
|
+
]) {
|
|
765
|
+
mock.mockReset();
|
|
766
|
+
}
|
|
767
|
+
arrange();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
afterEach(() => {
|
|
771
|
+
cleanup();
|
|
772
|
+
jest.useRealTimers();
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// ------------------------------------------------------------------- texts
|
|
776
|
+
|
|
777
|
+
const OVERVIEW_TITLES: Array<[string, CephMetric]> = [
|
|
778
|
+
["Cluster inventory counts", "inventoryCounts"],
|
|
779
|
+
["Cluster health", "health"],
|
|
780
|
+
["Active Health Checks", "activeHealthChecks"],
|
|
781
|
+
["Capacity Used", "capacityUsed"],
|
|
782
|
+
["OSDs Up", "osdsUp"],
|
|
783
|
+
["Mons In Quorum", "monsInQuorum"],
|
|
784
|
+
["Pools", "pools"],
|
|
785
|
+
["Problem PGs", "problemPgs"],
|
|
786
|
+
["OSD States", "osdStates"],
|
|
787
|
+
["Up + In", "osdUpIn"],
|
|
788
|
+
["Up + Out", "osdUpOut"],
|
|
789
|
+
["Down + In", "osdDownIn"],
|
|
790
|
+
["Down + Out", "osdDownOut"],
|
|
791
|
+
["Placement Group States", "pgStates"],
|
|
792
|
+
["Client IOPS", "clientIops"],
|
|
793
|
+
["Client Throughput", "clientThroughput"],
|
|
794
|
+
["Largest Pools", "largestPools"],
|
|
795
|
+
["Fullest Pools", "fullestPools"],
|
|
796
|
+
];
|
|
797
|
+
|
|
798
|
+
const OSD_LIST_TITLES: Array<[string, CephMetric]> = [
|
|
799
|
+
["Status", "osdStatusColumn"],
|
|
800
|
+
["In / Out", "osdInOutColumn"],
|
|
801
|
+
["Used / Total", "osdUsedColumn"],
|
|
802
|
+
["PGs", "osdPgsColumn"],
|
|
803
|
+
["Apply / Commit Latency", "osdLatencyColumn"],
|
|
804
|
+
["Age", "osdAgeColumn"],
|
|
805
|
+
];
|
|
806
|
+
|
|
807
|
+
const OSD_DETAIL_TITLES: Array<[string, CephMetric]> = [
|
|
808
|
+
["Status", "osdStatus"],
|
|
809
|
+
["Placement", "osdPlacement"],
|
|
810
|
+
["Used / Total", "osdUsed"],
|
|
811
|
+
["Placement Groups", "osdPlacementGroups"],
|
|
812
|
+
["Apply / Commit Latency", "osdLatency"],
|
|
813
|
+
];
|
|
814
|
+
|
|
815
|
+
const POOL_LIST_TITLES: Array<[string, CephMetric]> = [
|
|
816
|
+
["Stored", "poolStoredColumn"],
|
|
817
|
+
["Max Avail", "poolMaxAvailColumn"],
|
|
818
|
+
["Used", "poolUsedColumn"],
|
|
819
|
+
["Objects", "poolObjectsColumn"],
|
|
820
|
+
["Read IOPS", "poolReadIopsColumn"],
|
|
821
|
+
["Write IOPS", "poolWriteIopsColumn"],
|
|
822
|
+
];
|
|
823
|
+
|
|
824
|
+
const POOL_DETAIL_TITLES: Array<[string, CephMetric]> = [
|
|
825
|
+
["Stored", "poolStored"],
|
|
826
|
+
["Max Available", "poolMaxAvail"],
|
|
827
|
+
["Used", "poolUsed"],
|
|
828
|
+
["Growth", "poolGrowth"],
|
|
829
|
+
["Objects", "poolObjects"],
|
|
830
|
+
];
|
|
831
|
+
|
|
832
|
+
const CLUSTER_LIST_TITLES: Array<[string, CephMetric]> = [
|
|
833
|
+
["Health", "health"],
|
|
834
|
+
["OSDs", "clusterOsds"],
|
|
835
|
+
["Mons", "clusterMons"],
|
|
836
|
+
["Pools", "pools"],
|
|
837
|
+
["Capacity", "clusterCapacity"],
|
|
838
|
+
];
|
|
839
|
+
|
|
840
|
+
describe("CEPH_METRIC_DESCRIPTIONS", () => {
|
|
841
|
+
test("every text is a short, finished, plain sentence and none repeats another", () => {
|
|
842
|
+
expectReadableDescriptionRecord(
|
|
843
|
+
CEPH_METRIC_DESCRIPTIONS,
|
|
844
|
+
"CEPH_METRIC_DESCRIPTIONS",
|
|
845
|
+
);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("every title on every Ceph page is explained by its own text", () => {
|
|
849
|
+
for (const [title, key] of [
|
|
850
|
+
...OVERVIEW_TITLES,
|
|
851
|
+
...OSD_LIST_TITLES,
|
|
852
|
+
...OSD_DETAIL_TITLES,
|
|
853
|
+
...POOL_LIST_TITLES,
|
|
854
|
+
...POOL_DETAIL_TITLES,
|
|
855
|
+
...CLUSTER_LIST_TITLES,
|
|
856
|
+
]) {
|
|
857
|
+
expectTitleExplained(title, CEPH_METRIC_DESCRIPTIONS[key]);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
test("every key is used by exactly the pages listed here", () => {
|
|
862
|
+
const used: Set<CephMetric> = new Set<CephMetric>(
|
|
863
|
+
[
|
|
864
|
+
...OVERVIEW_TITLES,
|
|
865
|
+
...OSD_LIST_TITLES,
|
|
866
|
+
...OSD_DETAIL_TITLES,
|
|
867
|
+
...POOL_LIST_TITLES,
|
|
868
|
+
...POOL_DETAIL_TITLES,
|
|
869
|
+
...CLUSTER_LIST_TITLES,
|
|
870
|
+
["Status", "daemonStatus"],
|
|
871
|
+
["Client IOPS", "poolClientIops"],
|
|
872
|
+
["Client Throughput", "poolClientThroughput"],
|
|
873
|
+
].map(([, key]: Array<string>) => {
|
|
874
|
+
return key as CephMetric;
|
|
875
|
+
}),
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
expect([...used].sort()).toEqual(
|
|
879
|
+
Object.keys(CEPH_METRIC_DESCRIPTIONS).sort(),
|
|
880
|
+
);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
/*
|
|
884
|
+
* Ceph jargon has to be spelled out where it is first met, not assumed.
|
|
885
|
+
*/
|
|
886
|
+
test.each([
|
|
887
|
+
["osdsUp", /daemons that store your data/],
|
|
888
|
+
["clusterOsds", /daemons that store data/],
|
|
889
|
+
["monsInQuorum", /majority \(a quorum\)/],
|
|
890
|
+
["problemPgs", /chunks Ceph splits each pool into/],
|
|
891
|
+
["osdPgsColumn", /chunks Ceph splits pools into/],
|
|
892
|
+
["osdPlacementGroups", /chunks Ceph splits pools into/],
|
|
893
|
+
["problemPgs", /degraded \(missing copies\)/],
|
|
894
|
+
["problemPgs", /undersized \(on fewer OSDs than the pool's copy count\)/],
|
|
895
|
+
["osdStates", /up means its daemon is running/],
|
|
896
|
+
["osdStates", /in means Ceph places data on it/],
|
|
897
|
+
["osdStates", /a daemon that stores data, usually one per disk/],
|
|
898
|
+
["pgStates", /chunks Ceph splits each pool into/],
|
|
899
|
+
["clientIops", /operations per second \(IOPS\)/],
|
|
900
|
+
["poolClientIops", /operations per second \(IOPS\)/],
|
|
901
|
+
["poolReadIopsColumn", /read operations per second \(IOPS\)/],
|
|
902
|
+
["poolWriteIopsColumn", /write operations per second \(IOPS\)/],
|
|
903
|
+
["poolObjectsColumn", /not a count of your files/],
|
|
904
|
+
["capacityUsed", /nearfull/],
|
|
905
|
+
] as Array<[CephMetric, RegExp]>)(
|
|
906
|
+
"%s explains its jargon (%s)",
|
|
907
|
+
(key: CephMetric, pattern: RegExp) => {
|
|
908
|
+
expect(CEPH_METRIC_DESCRIPTIONS[key]).toMatch(pattern);
|
|
909
|
+
},
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
test("pool STORED is described as the data before replication, never after", () => {
|
|
913
|
+
for (const key of [
|
|
914
|
+
"largestPools",
|
|
915
|
+
"poolStoredColumn",
|
|
916
|
+
"poolStored",
|
|
917
|
+
] as Array<CephMetric>) {
|
|
918
|
+
expect(CEPH_METRIC_DESCRIPTIONS[key]).toMatch(/before replication/);
|
|
919
|
+
}
|
|
920
|
+
for (const text of Object.values(CEPH_METRIC_DESCRIPTIONS)) {
|
|
921
|
+
expect(text).not.toMatch(/after replication/i);
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
test("raw capacity is described as counting every copy", () => {
|
|
926
|
+
for (const key of [
|
|
927
|
+
"capacityUsed",
|
|
928
|
+
"clusterCapacity",
|
|
929
|
+
] as Array<CephMetric>) {
|
|
930
|
+
expect(CEPH_METRIC_DESCRIPTIONS[key]).toMatch(
|
|
931
|
+
/raw disk space in use, counting every copy/,
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
test("the staleness cut-off the texts name is the one the pages use", () => {
|
|
937
|
+
expect(METRIC_STALE_MS).toBe(15 * MINUTE);
|
|
938
|
+
for (const key of [
|
|
939
|
+
"largestPools",
|
|
940
|
+
"osdUsedColumn",
|
|
941
|
+
"osdUsed",
|
|
942
|
+
"poolStoredColumn",
|
|
943
|
+
"poolStored",
|
|
944
|
+
"daemonStatus",
|
|
945
|
+
// The OSD figures blank to a dash on the same cut-off.
|
|
946
|
+
"osdPgsColumn",
|
|
947
|
+
"osdLatencyColumn",
|
|
948
|
+
"osdPlacementGroups",
|
|
949
|
+
"osdLatency",
|
|
950
|
+
] as Array<CephMetric>) {
|
|
951
|
+
expect(CEPH_METRIC_DESCRIPTIONS[key]).toMatch(/15 minutes/);
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
/*
|
|
956
|
+
* A tooltip is a glance, not a paragraph: one or two sentences each.
|
|
957
|
+
*/
|
|
958
|
+
test("every text is one or two sentences", () => {
|
|
959
|
+
const tooLong: Array<string> = Object.entries(CEPH_METRIC_DESCRIPTIONS)
|
|
960
|
+
.filter(([, text]: [string, string]) => {
|
|
961
|
+
return text.split(SENTENCE_BREAK).length > 2;
|
|
962
|
+
})
|
|
963
|
+
.map(([key]: [string, string]) => {
|
|
964
|
+
return key;
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
expect(tooLong).toEqual([]);
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
/*
|
|
971
|
+
* The health pill reads "Health OK / Warning / Error / Unknown" on the
|
|
972
|
+
* overview and "OK / WARN / ERR / Unknown" on the Clusters list; the one
|
|
973
|
+
* text behind both has to name every state either page can show.
|
|
974
|
+
*/
|
|
975
|
+
test("the health text names every state the pills can show", () => {
|
|
976
|
+
for (const state of [
|
|
977
|
+
/\bOK\b/,
|
|
978
|
+
/\bWarning\b/,
|
|
979
|
+
/\bWARN\b/,
|
|
980
|
+
/\bError\b/,
|
|
981
|
+
/\bERR\b/,
|
|
982
|
+
/\bUnknown\b/,
|
|
983
|
+
]) {
|
|
984
|
+
expect(CEPH_METRIC_DESCRIPTIONS.health).toMatch(state);
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
test("the placement-group bar is described as approximate, since states overlap", () => {
|
|
989
|
+
expect(CEPH_METRIC_DESCRIPTIONS.pgStates).toMatch(
|
|
990
|
+
/several states at once, so the segments are approximate/,
|
|
991
|
+
);
|
|
992
|
+
expect(CEPH_METRIC_DESCRIPTIONS.pgStates).toMatch(/Other is the rest/);
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
test("Active Health Checks are the checks still raised in the latest data, not every check seen", () => {
|
|
996
|
+
expect(CEPH_METRIC_DESCRIPTIONS.activeHealthChecks).toMatch(
|
|
997
|
+
/still raised in the latest data/,
|
|
998
|
+
);
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
// ------------------------------------------------ small exported helpers
|
|
1003
|
+
|
|
1004
|
+
describe("CephMetricTitle", () => {
|
|
1005
|
+
test("renders the title and an (i) that shows the description", async () => {
|
|
1006
|
+
render(
|
|
1007
|
+
<h2>
|
|
1008
|
+
<CephMetricTitle
|
|
1009
|
+
title="OSD States"
|
|
1010
|
+
description={CEPH_METRIC_DESCRIPTIONS.osdStates}
|
|
1011
|
+
/>
|
|
1012
|
+
</h2>,
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
expect(screen.getByRole("heading")).toHaveTextContent("OSD States");
|
|
1016
|
+
await expectExplained("OSD States", "osdStates");
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
test("renders no (i) when there is nothing to explain", () => {
|
|
1020
|
+
render(<CephMetricTitle title="OSD States" description="" />);
|
|
1021
|
+
|
|
1022
|
+
expect(screen.getByText("OSD States")).toBeInTheDocument();
|
|
1023
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
describe("OSD state cells", () => {
|
|
1028
|
+
const MATRIX: OsdMatrix = {
|
|
1029
|
+
upIn: 7,
|
|
1030
|
+
upOut: 2,
|
|
1031
|
+
downIn: 1,
|
|
1032
|
+
downOut: 3,
|
|
1033
|
+
total: 13,
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
test("build the four up/in quadrants in order, each with its own text", () => {
|
|
1037
|
+
const cells: Array<OsdStateCell> = buildOsdStateCells(MATRIX);
|
|
1038
|
+
|
|
1039
|
+
expect(
|
|
1040
|
+
cells.map((cell: OsdStateCell) => {
|
|
1041
|
+
return [cell.label, cell.count, cell.description];
|
|
1042
|
+
}),
|
|
1043
|
+
).toEqual([
|
|
1044
|
+
["Up + In", 7, CEPH_METRIC_DESCRIPTIONS.osdUpIn],
|
|
1045
|
+
["Up + Out", 2, CEPH_METRIC_DESCRIPTIONS.osdUpOut],
|
|
1046
|
+
["Down + In", 1, CEPH_METRIC_DESCRIPTIONS.osdDownIn],
|
|
1047
|
+
["Down + Out", 3, CEPH_METRIC_DESCRIPTIONS.osdDownOut],
|
|
1048
|
+
]);
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
test.each(buildOsdStateCells(MATRIX))(
|
|
1052
|
+
"$label renders its count, label, sublabel and (i)",
|
|
1053
|
+
async (cell: OsdStateCell) => {
|
|
1054
|
+
render(<OsdStateCellView cell={cell} />);
|
|
1055
|
+
|
|
1056
|
+
expect(screen.getByText(String(cell.count))).toBeInTheDocument();
|
|
1057
|
+
expect(screen.getByText(cell.label)).toBeInTheDocument();
|
|
1058
|
+
expect(screen.getByText(cell.sublabel)).toBeInTheDocument();
|
|
1059
|
+
expect(await tooltipTextOf(infoButton(cell.label))).toBe(
|
|
1060
|
+
cell.description,
|
|
1061
|
+
);
|
|
1062
|
+
},
|
|
1063
|
+
);
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// ---------------------------------------------------------------- overview
|
|
1067
|
+
|
|
1068
|
+
async function renderOverview(): Promise<void> {
|
|
1069
|
+
render(<CephClusterOverview {...PAGE_PROPS} />);
|
|
1070
|
+
await screen.findByText("ceph-prod");
|
|
1071
|
+
await flush();
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
describe("Ceph cluster overview", () => {
|
|
1075
|
+
test("every metric on the page has an (i), and there are no others", async () => {
|
|
1076
|
+
await renderOverview();
|
|
1077
|
+
|
|
1078
|
+
expect(
|
|
1079
|
+
allInfoButtons()
|
|
1080
|
+
.map((b: HTMLElement) => {
|
|
1081
|
+
return b.getAttribute("aria-label");
|
|
1082
|
+
})
|
|
1083
|
+
.sort(),
|
|
1084
|
+
).toEqual(
|
|
1085
|
+
OVERVIEW_TITLES.map(([title]: [string, CephMetric]) => {
|
|
1086
|
+
return `About ${title}`;
|
|
1087
|
+
}).sort(),
|
|
1088
|
+
);
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
test.each(OVERVIEW_TITLES)(
|
|
1092
|
+
"%s shows its CEPH_METRIC_DESCRIPTIONS text",
|
|
1093
|
+
async (title: string, key: CephMetric) => {
|
|
1094
|
+
await renderOverview();
|
|
1095
|
+
await expectExplained(title, key);
|
|
1096
|
+
},
|
|
1097
|
+
);
|
|
1098
|
+
|
|
1099
|
+
test("no (i) sits inside a link or another button", async () => {
|
|
1100
|
+
await renderOverview();
|
|
1101
|
+
expectNoNestedInfoButtons();
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
test("the five golden tiles carry their (i) beside the title", async () => {
|
|
1105
|
+
await renderOverview();
|
|
1106
|
+
|
|
1107
|
+
for (const title of [
|
|
1108
|
+
"Capacity Used",
|
|
1109
|
+
"OSDs Up",
|
|
1110
|
+
"Mons In Quorum",
|
|
1111
|
+
"Pools",
|
|
1112
|
+
"Problem PGs",
|
|
1113
|
+
]) {
|
|
1114
|
+
const tile: HTMLElement = tileByTitle(title);
|
|
1115
|
+
expect(within(tile).getByText(title)).toHaveClass("uppercase");
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
test("Problem PGs adds degraded and undersized, so a group in both states counts twice", async () => {
|
|
1120
|
+
await renderOverview();
|
|
1121
|
+
|
|
1122
|
+
// 6 degraded + 5 undersized, and pool 1's five are the same groups.
|
|
1123
|
+
const tile: HTMLElement = tileByTitle("Problem PGs");
|
|
1124
|
+
expect(within(tile).getByText("11")).toBeInTheDocument();
|
|
1125
|
+
expect(
|
|
1126
|
+
within(tile).getByText("6 degraded, 5 undersized"),
|
|
1127
|
+
).toBeInTheDocument();
|
|
1128
|
+
expect(CEPH_METRIC_DESCRIPTIONS.problemPgs).toMatch(/counted twice/);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
test("the PG counts and health checks read the last 10 minutes", async () => {
|
|
1132
|
+
await renderOverview();
|
|
1133
|
+
|
|
1134
|
+
for (const name of [
|
|
1135
|
+
"ceph_health_detail",
|
|
1136
|
+
"ceph_pg_total",
|
|
1137
|
+
"ceph_pg_clean",
|
|
1138
|
+
"ceph_pg_degraded",
|
|
1139
|
+
"ceph_pg_undersized",
|
|
1140
|
+
]) {
|
|
1141
|
+
const queries: Array<CapturedQuery> = queriesFor(
|
|
1142
|
+
analyticsGetListMock,
|
|
1143
|
+
name,
|
|
1144
|
+
);
|
|
1145
|
+
expect(queries.length).toBeGreaterThan(0);
|
|
1146
|
+
for (const q of queries) {
|
|
1147
|
+
expect(windowMinutes(q)).toBe(10);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
expect(CEPH_METRIC_DESCRIPTIONS.problemPgs).toMatch(/last 10 minutes/);
|
|
1151
|
+
expect(CEPH_METRIC_DESCRIPTIONS.pgStates).toMatch(/last 10 minutes/);
|
|
1152
|
+
expect(CEPH_METRIC_DESCRIPTIONS.activeHealthChecks).toMatch(
|
|
1153
|
+
/last 10 minutes/,
|
|
1154
|
+
);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
test("only checks that are raised show under Active Health Checks", async () => {
|
|
1158
|
+
await renderOverview();
|
|
1159
|
+
|
|
1160
|
+
expect(screen.getByText("OSD_DOWN")).toBeInTheDocument();
|
|
1161
|
+
expect(screen.queryByText("MON_DOWN")).not.toBeInTheDocument();
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
test("Capacity Used is the latest reading and projects the last 24 hours to 85%", async () => {
|
|
1165
|
+
await renderOverview();
|
|
1166
|
+
|
|
1167
|
+
const tile: HTMLElement = tileByTitle("Capacity Used");
|
|
1168
|
+
// Latest 300 GiB of 1000 GiB - not the 200 GiB average of the day.
|
|
1169
|
+
expect(within(tile).getByText("30.0%")).toBeInTheDocument();
|
|
1170
|
+
expect(
|
|
1171
|
+
within(tile).getByText(
|
|
1172
|
+
"300 GiB of 1000 GiB — 85% in ~1d at current growth",
|
|
1173
|
+
),
|
|
1174
|
+
).toBeInTheDocument();
|
|
1175
|
+
|
|
1176
|
+
const used: Array<CapturedQuery> = queriesFor(
|
|
1177
|
+
analyticsAggregateMock,
|
|
1178
|
+
"ceph_cluster_total_used_bytes",
|
|
1179
|
+
);
|
|
1180
|
+
expect(used).toHaveLength(1);
|
|
1181
|
+
expect(windowMinutes(used[0]!)).toBe(24 * 60);
|
|
1182
|
+
expect(CEPH_METRIC_DESCRIPTIONS.capacityUsed).toMatch(/24 hours/);
|
|
1183
|
+
expect(CEPH_METRIC_DESCRIPTIONS.capacityUsed).toMatch(/85%/);
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
test("Capacity Used ignores the chart time range, while the rate charts follow it", async () => {
|
|
1187
|
+
await renderOverview();
|
|
1188
|
+
|
|
1189
|
+
const capacityQueriesBefore: number = queriesFor(
|
|
1190
|
+
analyticsAggregateMock,
|
|
1191
|
+
"ceph_cluster_total_used_bytes",
|
|
1192
|
+
).length;
|
|
1193
|
+
const rateCallsBefore: number = rateChartMock.mock.calls.length;
|
|
1194
|
+
|
|
1195
|
+
fireEvent.click(screen.getByRole("button", { name: "Pick past day" }));
|
|
1196
|
+
await flush();
|
|
1197
|
+
|
|
1198
|
+
expect(
|
|
1199
|
+
queriesFor(analyticsAggregateMock, "ceph_cluster_total_used_bytes"),
|
|
1200
|
+
).toHaveLength(capacityQueriesBefore);
|
|
1201
|
+
|
|
1202
|
+
const lastRateProps: { startDate: Date; endDate: Date } = rateChartMock.mock
|
|
1203
|
+
.calls[rateChartMock.mock.calls.length - 1]![0] as {
|
|
1204
|
+
startDate: Date;
|
|
1205
|
+
endDate: Date;
|
|
1206
|
+
};
|
|
1207
|
+
expect(rateChartMock.mock.calls.length).toBeGreaterThan(rateCallsBefore);
|
|
1208
|
+
expect(
|
|
1209
|
+
(lastRateProps.endDate.getTime() - lastRateProps.startDate.getTime()) /
|
|
1210
|
+
MINUTE,
|
|
1211
|
+
).toBe(24 * 60);
|
|
1212
|
+
expect(CEPH_METRIC_DESCRIPTIONS.capacityUsed).toMatch(
|
|
1213
|
+
/rather than the chart time range/,
|
|
1214
|
+
);
|
|
1215
|
+
expect(CEPH_METRIC_DESCRIPTIONS.clientIops).toMatch(/selected time range/);
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
test("the rate charts under Client IOPS and Client Throughput are the summed pool counters", async () => {
|
|
1219
|
+
await renderOverview();
|
|
1220
|
+
|
|
1221
|
+
const charts: Array<HTMLElement> = screen.getAllByTestId("ceph-rate-chart");
|
|
1222
|
+
expect(
|
|
1223
|
+
charts.map((c: HTMLElement) => {
|
|
1224
|
+
return c.textContent;
|
|
1225
|
+
}),
|
|
1226
|
+
).toEqual([
|
|
1227
|
+
"ceph_pool_rd,ceph_pool_wr",
|
|
1228
|
+
"ceph_pool_rd_bytes,ceph_pool_wr_bytes",
|
|
1229
|
+
]);
|
|
1230
|
+
expect(infoButton("Client IOPS").closest("div")?.nextElementSibling).toBe(
|
|
1231
|
+
charts[0],
|
|
1232
|
+
);
|
|
1233
|
+
expect(
|
|
1234
|
+
infoButton("Client Throughput").closest("div")?.nextElementSibling,
|
|
1235
|
+
).toBe(charts[1]);
|
|
1236
|
+
expect(CEPH_METRIC_DESCRIPTIONS.clientIops).toMatch(/across all pools/);
|
|
1237
|
+
expect(CEPH_METRIC_DESCRIPTIONS.clientThroughput).toMatch(
|
|
1238
|
+
/across all pools/,
|
|
1239
|
+
);
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
test("Largest and Fullest Pools leave out a pool whose figures are over 15 minutes old", async () => {
|
|
1243
|
+
await renderOverview();
|
|
1244
|
+
|
|
1245
|
+
const rowsOf: (title: string) => Array<string> = (
|
|
1246
|
+
title: string,
|
|
1247
|
+
): Array<string> => {
|
|
1248
|
+
const card: HTMLElement = infoButton(title).closest(
|
|
1249
|
+
"[data-testid='card']",
|
|
1250
|
+
) as HTMLElement;
|
|
1251
|
+
return Array.from(card.querySelectorAll("div.py-3")).map(
|
|
1252
|
+
(row: Element): string => {
|
|
1253
|
+
return (row.textContent || "").trim();
|
|
1254
|
+
},
|
|
1255
|
+
);
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
// The 5000 GiB, 99.98%-full pool stopped reporting 20 minutes ago.
|
|
1259
|
+
expect(rowsOf("Largest Pools")).toEqual([
|
|
1260
|
+
"rbd300 GiB",
|
|
1261
|
+
"cephfs_data100 GiB",
|
|
1262
|
+
]);
|
|
1263
|
+
expect(rowsOf("Fullest Pools")).toEqual(["cephfs_data50.0%", "rbd30.0%"]);
|
|
1264
|
+
expect(screen.queryByText("stale-archive")).not.toBeInTheDocument();
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
test("the OSD States cells count each up/in quadrant from the inventory", async () => {
|
|
1268
|
+
await renderOverview();
|
|
1269
|
+
|
|
1270
|
+
for (const [label, count] of [
|
|
1271
|
+
["Up + In", "2"],
|
|
1272
|
+
["Up + Out", "1"],
|
|
1273
|
+
["Down + In", "1"],
|
|
1274
|
+
["Down + Out", "0"],
|
|
1275
|
+
]) {
|
|
1276
|
+
const cell: HTMLElement = infoButton(label as string).closest(
|
|
1277
|
+
"div.rounded-lg",
|
|
1278
|
+
) as HTMLElement;
|
|
1279
|
+
expect(within(cell).getByText(count as string)).toBeInTheDocument();
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
test("Mons In Quorum counts monitors in quorum out of all known monitors", async () => {
|
|
1284
|
+
await renderOverview();
|
|
1285
|
+
|
|
1286
|
+
expect(
|
|
1287
|
+
within(tileByTitle("Mons In Quorum")).getByText("3/3"),
|
|
1288
|
+
).toBeInTheDocument();
|
|
1289
|
+
expect(within(tileByTitle("OSDs Up")).getByText("3/4")).toBeInTheDocument();
|
|
1290
|
+
expect(
|
|
1291
|
+
within(tileByTitle("OSDs Up")).getByText("3 in"),
|
|
1292
|
+
).toBeInTheDocument();
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
test("a healthy cluster hides Active Health Checks and its (i) with it", async () => {
|
|
1296
|
+
modelGetItemMock.mockImplementation(async () => {
|
|
1297
|
+
return { ...CLUSTER, healthStatus: 0 };
|
|
1298
|
+
});
|
|
1299
|
+
await renderOverview();
|
|
1300
|
+
|
|
1301
|
+
expectNoInfo("Active Health Checks");
|
|
1302
|
+
expect(infoButton("Cluster health")).toBeInTheDocument();
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
test("the quick links and the details card carry no (i) - they are navigation and metadata", async () => {
|
|
1306
|
+
await renderOverview();
|
|
1307
|
+
|
|
1308
|
+
expectNoInfo("Quick Links");
|
|
1309
|
+
expectNoInfo("OSDs");
|
|
1310
|
+
expectNoInfo("Daemons");
|
|
1311
|
+
expectNoInfo("Metrics");
|
|
1312
|
+
expectNoInfo("Ceph Cluster Details");
|
|
1313
|
+
});
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
/*
|
|
1317
|
+
* The OSD / monitor / pool count chips beside the cluster name. Their text
|
|
1318
|
+
* says they come from the cluster's latest data rather than a time range,
|
|
1319
|
+
* that quorum is monitors in quorum out of all known monitors, and that
|
|
1320
|
+
* only the monitor count shows when quorum is not reported.
|
|
1321
|
+
*/
|
|
1322
|
+
describe("Ceph cluster overview: the count chips", () => {
|
|
1323
|
+
function chipRow(): HTMLElement {
|
|
1324
|
+
return infoButton("Cluster inventory counts").parentElement as HTMLElement;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
function withInventory(overrides: Record<string, Array<Row>>): void {
|
|
1328
|
+
modelGetListMock.mockImplementation(async (args: unknown) => {
|
|
1329
|
+
const kind: string = String(
|
|
1330
|
+
(args as { query: Record<string, unknown> }).query["kind"],
|
|
1331
|
+
);
|
|
1332
|
+
const rows: Array<Row> = overrides[kind] ?? ROWS_BY_KIND[kind] ?? [];
|
|
1333
|
+
return { data: rows, count: rows.length, skip: 0, limit: 100 };
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
test("the OSD, monitor and pool chips share one (i), and the version chip sits beside them", async () => {
|
|
1338
|
+
await renderOverview();
|
|
1339
|
+
|
|
1340
|
+
expect(within(chipRow()).getByText("3/4 OSDs up")).toBeInTheDocument();
|
|
1341
|
+
expect(
|
|
1342
|
+
within(chipRow()).getByText("3/3 mons in quorum"),
|
|
1343
|
+
).toBeInTheDocument();
|
|
1344
|
+
expect(within(chipRow()).getByText("3 pools")).toBeInTheDocument();
|
|
1345
|
+
expect(within(chipRow()).getByText("18.2.4")).toBeInTheDocument();
|
|
1346
|
+
expect(within(chipRow()).getAllByRole("button")).toHaveLength(1);
|
|
1347
|
+
await expectExplained("Cluster inventory counts", "inventoryCounts");
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
test("OSDs come from the cluster's latest snapshot and quorum from each monitor's latest state", async () => {
|
|
1351
|
+
modelGetItemMock.mockImplementation(async () => {
|
|
1352
|
+
return { ...CLUSTER, osdCount: 5, osdUpCount: 2 };
|
|
1353
|
+
});
|
|
1354
|
+
withInventory({
|
|
1355
|
+
Mon: MON_ROWS.map((row: Row): Row => {
|
|
1356
|
+
return row["externalId"] === "mon.b"
|
|
1357
|
+
? { ...row, inQuorum: false }
|
|
1358
|
+
: row;
|
|
1359
|
+
}),
|
|
1360
|
+
});
|
|
1361
|
+
await renderOverview();
|
|
1362
|
+
|
|
1363
|
+
expect(within(chipRow()).getByText("2/5 OSDs up")).toBeInTheDocument();
|
|
1364
|
+
expect(
|
|
1365
|
+
within(chipRow()).getByText("2/3 mons in quorum"),
|
|
1366
|
+
).toBeInTheDocument();
|
|
1367
|
+
expect(CEPH_METRIC_DESCRIPTIONS.inventoryCounts).toMatch(
|
|
1368
|
+
/monitors in quorum out of all known monitors/,
|
|
1369
|
+
);
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
test("without monitor rows, the chip shows only the monitor count, as the text says", async () => {
|
|
1373
|
+
withInventory({ Mon: [] });
|
|
1374
|
+
await renderOverview();
|
|
1375
|
+
|
|
1376
|
+
expect(within(chipRow()).getByText("3 mons")).toBeInTheDocument();
|
|
1377
|
+
expect(screen.queryByText(/mons in quorum$/)).not.toBeInTheDocument();
|
|
1378
|
+
expect(CEPH_METRIC_DESCRIPTIONS.inventoryCounts).toMatch(
|
|
1379
|
+
/only the monitor count when quorum is not reported/,
|
|
1380
|
+
);
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
test("the chips ignore the chart time range", async () => {
|
|
1384
|
+
await renderOverview();
|
|
1385
|
+
|
|
1386
|
+
const clusterReads: number = modelGetItemMock.mock.calls.length;
|
|
1387
|
+
const inventoryReads: number = modelGetListMock.mock.calls.length;
|
|
1388
|
+
|
|
1389
|
+
fireEvent.click(
|
|
1390
|
+
screen.getAllByRole("button", { name: "Pick past day" })[0]!,
|
|
1391
|
+
);
|
|
1392
|
+
await flush();
|
|
1393
|
+
|
|
1394
|
+
expect(modelGetItemMock.mock.calls.length).toBe(clusterReads);
|
|
1395
|
+
expect(modelGetListMock.mock.calls.length).toBe(inventoryReads);
|
|
1396
|
+
expect(within(chipRow()).getByText("3/4 OSDs up")).toBeInTheDocument();
|
|
1397
|
+
expect(CEPH_METRIC_DESCRIPTIONS.inventoryCounts).toMatch(
|
|
1398
|
+
/latest data, not a time range/,
|
|
1399
|
+
);
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
test("a cluster that has reported only its version gets no (i) on the chip row", async () => {
|
|
1403
|
+
modelGetItemMock.mockImplementation(async () => {
|
|
1404
|
+
return {
|
|
1405
|
+
...CLUSTER,
|
|
1406
|
+
osdCount: 0,
|
|
1407
|
+
osdUpCount: 0,
|
|
1408
|
+
monCount: 0,
|
|
1409
|
+
poolCount: 0,
|
|
1410
|
+
};
|
|
1411
|
+
});
|
|
1412
|
+
withInventory({ Mon: [] });
|
|
1413
|
+
await renderOverview();
|
|
1414
|
+
|
|
1415
|
+
expect(screen.getByText("18.2.4")).toBeInTheDocument();
|
|
1416
|
+
expectNoInfo("Cluster inventory counts");
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
// ------------------------------------------------------------------- lists
|
|
1421
|
+
|
|
1422
|
+
async function renderAndSettle(element: React.ReactElement): Promise<void> {
|
|
1423
|
+
render(element);
|
|
1424
|
+
await flush();
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
describe("OSD list", () => {
|
|
1428
|
+
test("each metric column header has an (i) with its text", async () => {
|
|
1429
|
+
await renderAndSettle(<CephClusterOsds {...PAGE_PROPS} />);
|
|
1430
|
+
await screen.findByText("osd.0");
|
|
1431
|
+
|
|
1432
|
+
for (const [title, key] of OSD_LIST_TITLES) {
|
|
1433
|
+
await expectExplained(title, key);
|
|
1434
|
+
}
|
|
1435
|
+
expectNoNestedInfoButtons();
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
test("identity columns carry no (i)", async () => {
|
|
1439
|
+
await renderAndSettle(<CephClusterOsds {...PAGE_PROPS} />);
|
|
1440
|
+
await screen.findByText("osd.0");
|
|
1441
|
+
|
|
1442
|
+
for (const title of ["Name", "Host", "Class"]) {
|
|
1443
|
+
expectNoInfo(title);
|
|
1444
|
+
}
|
|
1445
|
+
expect(allInfoButtons()).toHaveLength(OSD_LIST_TITLES.length);
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
/*
|
|
1449
|
+
* Ceph reports no creation time for an OSD, so Age is how long OneUptime
|
|
1450
|
+
* has had the OSD's inventory row - which is what its text says.
|
|
1451
|
+
*/
|
|
1452
|
+
test("Age counts from when OneUptime first recorded the OSD, not from anything Ceph reports", async () => {
|
|
1453
|
+
const rows: Array<Row> = OSD_ROWS.map((row: Row): Row => {
|
|
1454
|
+
// osd.2 was first recorded 10 minutes ago; the rest 3 days ago.
|
|
1455
|
+
return row["externalId"] === "osd.2"
|
|
1456
|
+
? { ...row, createdAt: minutesAgo(10) }
|
|
1457
|
+
: row;
|
|
1458
|
+
});
|
|
1459
|
+
modelGetListMock.mockImplementation(async () => {
|
|
1460
|
+
return { data: rows, count: rows.length, skip: 0, limit: 100 };
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
await renderAndSettle(<CephClusterOsds {...PAGE_PROPS} />);
|
|
1464
|
+
await screen.findByText("osd.0");
|
|
1465
|
+
|
|
1466
|
+
const ageHeader: HTMLElement = infoButton("Age").closest(
|
|
1467
|
+
"th",
|
|
1468
|
+
) as HTMLElement;
|
|
1469
|
+
const ageIndex: number = Array.from(
|
|
1470
|
+
ageHeader.parentElement!.children,
|
|
1471
|
+
).indexOf(ageHeader);
|
|
1472
|
+
expect(ageIndex).toBeGreaterThan(0);
|
|
1473
|
+
|
|
1474
|
+
const cellUnderAge: (name: string) => string = (name: string): string => {
|
|
1475
|
+
const row: HTMLElement = screen
|
|
1476
|
+
.getByText(name)
|
|
1477
|
+
.closest("tr") as HTMLElement;
|
|
1478
|
+
return (row.children[ageIndex]?.textContent || "").trim();
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
expect(cellUnderAge("osd.0")).toBe("3d");
|
|
1482
|
+
expect(cellUnderAge("osd.2")).toBe("10m");
|
|
1483
|
+
// Its 20-minute-old figures do not touch Age: that is the row's age.
|
|
1484
|
+
expect(cellUnderAge("osd.3")).toBe("3d");
|
|
1485
|
+
expect(CEPH_METRIC_DESCRIPTIONS.osdAgeColumn).toMatch(
|
|
1486
|
+
/first saw this OSD in the data the Ceph agent sends, not how old the OSD or its disk is/,
|
|
1487
|
+
);
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
test("an OSD that has not reported in 15 minutes shows a dash, as the text says", async () => {
|
|
1491
|
+
await renderAndSettle(<CephClusterOsds {...PAGE_PROPS} />);
|
|
1492
|
+
const row: HTMLElement = (await screen.findByText("osd.3")).closest(
|
|
1493
|
+
"tr",
|
|
1494
|
+
) as HTMLElement;
|
|
1495
|
+
|
|
1496
|
+
// Used / Total, PGs and latency all fall back to a dash.
|
|
1497
|
+
expect(within(row).getAllByText("—").length).toBeGreaterThanOrEqual(3);
|
|
1498
|
+
expect(within(row).getByText("Down")).toBeInTheDocument();
|
|
1499
|
+
expect(within(row).getByText("In")).toBeInTheDocument();
|
|
1500
|
+
|
|
1501
|
+
const fresh: HTMLElement = screen
|
|
1502
|
+
.getByText("osd.1")
|
|
1503
|
+
.closest("tr") as HTMLElement;
|
|
1504
|
+
expect(
|
|
1505
|
+
within(fresh).getByText("500 GiB / 2.0 TiB (25.0%)"),
|
|
1506
|
+
).toBeInTheDocument();
|
|
1507
|
+
expect(within(fresh).getByText("12 / 13 ms")).toBeInTheDocument();
|
|
1508
|
+
});
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
describe("Pool list", () => {
|
|
1512
|
+
test("each metric column header has an (i) with its text", async () => {
|
|
1513
|
+
await renderAndSettle(<CephClusterPools {...PAGE_PROPS} />);
|
|
1514
|
+
await screen.findByText("rbd");
|
|
1515
|
+
|
|
1516
|
+
for (const [title, key] of POOL_LIST_TITLES) {
|
|
1517
|
+
await expectExplained(title, key);
|
|
1518
|
+
}
|
|
1519
|
+
expectNoInfo("Name");
|
|
1520
|
+
expect(allInfoButtons()).toHaveLength(POOL_LIST_TITLES.length);
|
|
1521
|
+
expectNoNestedInfoButtons();
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
test("Read and Write IOPS always average the last 15 minutes", async () => {
|
|
1525
|
+
await renderAndSettle(<CephClusterPools {...PAGE_PROPS} />);
|
|
1526
|
+
const row: HTMLElement = (await screen.findByText("rbd")).closest(
|
|
1527
|
+
"tr",
|
|
1528
|
+
) as HTMLElement;
|
|
1529
|
+
|
|
1530
|
+
expect(within(row).getByText("10.0/s")).toBeInTheDocument();
|
|
1531
|
+
expect(within(row).getByText("2.5/s")).toBeInTheDocument();
|
|
1532
|
+
|
|
1533
|
+
for (const name of ["ceph_pool_rd", "ceph_pool_wr"]) {
|
|
1534
|
+
const queries: Array<CapturedQuery> = queriesFor(
|
|
1535
|
+
analyticsAggregateMock,
|
|
1536
|
+
name,
|
|
1537
|
+
);
|
|
1538
|
+
expect(queries).toHaveLength(1);
|
|
1539
|
+
expect(windowMinutes(queries[0]!)).toBe(15);
|
|
1540
|
+
}
|
|
1541
|
+
expect(CEPH_METRIC_DESCRIPTIONS.poolReadIopsColumn).toMatch(
|
|
1542
|
+
/last 15 minutes/,
|
|
1543
|
+
);
|
|
1544
|
+
expect(CEPH_METRIC_DESCRIPTIONS.poolWriteIopsColumn).toMatch(
|
|
1545
|
+
/last 15 minutes/,
|
|
1546
|
+
);
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
test("Used is Stored over Stored plus Max Avail", async () => {
|
|
1550
|
+
await renderAndSettle(<CephClusterPools {...PAGE_PROPS} />);
|
|
1551
|
+
const row: HTMLElement = (await screen.findByText("rbd")).closest(
|
|
1552
|
+
"tr",
|
|
1553
|
+
) as HTMLElement;
|
|
1554
|
+
|
|
1555
|
+
// 300 / (300 + 700)
|
|
1556
|
+
expect(within(row).getByText("30.0%")).toBeInTheDocument();
|
|
1557
|
+
expect(CEPH_METRIC_DESCRIPTIONS.poolUsedColumn).toMatch(
|
|
1558
|
+
/Stored divided by Stored plus Max Avail/,
|
|
1559
|
+
);
|
|
1560
|
+
});
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
describe("Daemons table", () => {
|
|
1564
|
+
test("the Status header explains quorum, Reporting and Stale", async () => {
|
|
1565
|
+
await renderAndSettle(<CephClusterDaemons {...PAGE_PROPS} />);
|
|
1566
|
+
await screen.findByText("mon.a");
|
|
1567
|
+
|
|
1568
|
+
await expectExplained("Status", "daemonStatus");
|
|
1569
|
+
expect(allInfoButtons()).toHaveLength(1);
|
|
1570
|
+
for (const title of ["Daemon", "Kind", "Host", "Version"]) {
|
|
1571
|
+
expectNoInfo(title);
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
test("statuses follow the rules the text gives", async () => {
|
|
1576
|
+
await renderAndSettle(<CephClusterDaemons {...PAGE_PROPS} />);
|
|
1577
|
+
|
|
1578
|
+
const row: (name: string) => HTMLElement = (name: string) => {
|
|
1579
|
+
return screen.getByText(name).closest("tr") as HTMLElement;
|
|
1580
|
+
};
|
|
1581
|
+
await screen.findByText("mon.a");
|
|
1582
|
+
|
|
1583
|
+
expect(within(row("mon.a")).getByText("In Quorum")).toBeInTheDocument();
|
|
1584
|
+
// In quorum when last seen, but silent for 20 minutes now.
|
|
1585
|
+
expect(within(row("mon.c")).getByText("Stale")).toBeInTheDocument();
|
|
1586
|
+
expect(within(row("mgr.x")).getByText("Reporting")).toBeInTheDocument();
|
|
1587
|
+
});
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
// ----------------------------------------------------------------- details
|
|
1591
|
+
|
|
1592
|
+
describe("OSD detail", () => {
|
|
1593
|
+
test("each metric summary field has an (i) with its text; identity fields have none", async () => {
|
|
1594
|
+
mockLastParam = "osd.1";
|
|
1595
|
+
await renderAndSettle(<CephClusterOsdDetail {...PAGE_PROPS} />);
|
|
1596
|
+
await screen.findByText("node-b");
|
|
1597
|
+
|
|
1598
|
+
for (const [title, key] of OSD_DETAIL_TITLES) {
|
|
1599
|
+
await expectExplained(title, key);
|
|
1600
|
+
}
|
|
1601
|
+
for (const title of [
|
|
1602
|
+
"OSD",
|
|
1603
|
+
"Cluster",
|
|
1604
|
+
"Host",
|
|
1605
|
+
"Device Class",
|
|
1606
|
+
"Version",
|
|
1607
|
+
"Last Seen",
|
|
1608
|
+
]) {
|
|
1609
|
+
expectNoInfo(title);
|
|
1610
|
+
}
|
|
1611
|
+
expect(allInfoButtons()).toHaveLength(OSD_DETAIL_TITLES.length);
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
test("a stale OSD keeps its Up/In state but blanks the figures", async () => {
|
|
1615
|
+
mockLastParam = "osd.3";
|
|
1616
|
+
await renderAndSettle(<CephClusterOsdDetail {...PAGE_PROPS} />);
|
|
1617
|
+
await screen.findByText("node-d");
|
|
1618
|
+
|
|
1619
|
+
expect(screen.getByText("Down")).toBeInTheDocument();
|
|
1620
|
+
expect(screen.getByText("In")).toBeInTheDocument();
|
|
1621
|
+
// Used / Total and Apply / Commit Latency.
|
|
1622
|
+
expect(screen.getAllByText("— / —")).toHaveLength(2);
|
|
1623
|
+
});
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
describe("Pool detail", () => {
|
|
1627
|
+
test("each metric summary field has an (i) with its text; identity fields have none", async () => {
|
|
1628
|
+
mockLastParam = "1";
|
|
1629
|
+
await renderAndSettle(<CephClusterPoolDetail {...PAGE_PROPS} />);
|
|
1630
|
+
await screen.findByText("Growth");
|
|
1631
|
+
await flush();
|
|
1632
|
+
|
|
1633
|
+
for (const [title, key] of POOL_DETAIL_TITLES) {
|
|
1634
|
+
await expectExplained(title, key);
|
|
1635
|
+
}
|
|
1636
|
+
for (const title of ["Pool", "Pool ID", "Cluster", "Last Seen"]) {
|
|
1637
|
+
expectNoInfo(title);
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
test("Growth projects the 24-hour stored trend to Stored plus Max Available", async () => {
|
|
1642
|
+
mockLastParam = "1";
|
|
1643
|
+
await renderAndSettle(<CephClusterPoolDetail {...PAGE_PROPS} />);
|
|
1644
|
+
await screen.findByText("Growth");
|
|
1645
|
+
await flush();
|
|
1646
|
+
|
|
1647
|
+
expect(
|
|
1648
|
+
screen.getByText(/^Full in ~\d+d at current growth$/),
|
|
1649
|
+
).toBeInTheDocument();
|
|
1650
|
+
const stored: Array<CapturedQuery> = queriesFor(
|
|
1651
|
+
analyticsAggregateMock,
|
|
1652
|
+
"ceph_pool_stored",
|
|
1653
|
+
);
|
|
1654
|
+
expect(stored).toHaveLength(1);
|
|
1655
|
+
expect(windowMinutes(stored[0]!)).toBe(24 * 60);
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
test("the Metrics tab's rate charts carry the pool's own IOPS and throughput texts", async () => {
|
|
1659
|
+
mockLastParam = "1";
|
|
1660
|
+
await renderAndSettle(<CephClusterPoolDetail {...PAGE_PROPS} />);
|
|
1661
|
+
await screen.findByText("Growth");
|
|
1662
|
+
|
|
1663
|
+
fireEvent.click(screen.getByRole("tab", { name: "Metrics" }));
|
|
1664
|
+
await flush();
|
|
1665
|
+
|
|
1666
|
+
await expectExplained("Client IOPS", "poolClientIops");
|
|
1667
|
+
await expectExplained("Client Throughput", "poolClientThroughput");
|
|
1668
|
+
expectNoNestedInfoButtons();
|
|
1669
|
+
});
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
describe("Insights", () => {
|
|
1673
|
+
test("the Client I/O rate charts carry the cluster-wide IOPS and throughput texts", async () => {
|
|
1674
|
+
await renderAndSettle(<CephClusterInsights {...PAGE_PROPS} />);
|
|
1675
|
+
await screen.findByText("Client IOPS");
|
|
1676
|
+
|
|
1677
|
+
await expectExplained("Client IOPS", "clientIops");
|
|
1678
|
+
await expectExplained("Client Throughput", "clientThroughput");
|
|
1679
|
+
expect(allInfoButtons()).toHaveLength(2);
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
// ------------------------------------------------------------ clusters list
|
|
1684
|
+
|
|
1685
|
+
type CapturedColumn = {
|
|
1686
|
+
title: string;
|
|
1687
|
+
headerTooltip?: string | undefined;
|
|
1688
|
+
getElement?: ((item: unknown) => React.ReactElement) | undefined;
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
async function clusterColumns(): Promise<Array<CapturedColumn>> {
|
|
1692
|
+
await renderAndSettle(<CephClusters {...PAGE_PROPS} />);
|
|
1693
|
+
expect(modelTableMock).toHaveBeenCalled();
|
|
1694
|
+
const props: { columns: Array<CapturedColumn> } = modelTableMock.mock.calls[
|
|
1695
|
+
modelTableMock.mock.calls.length - 1
|
|
1696
|
+
]![0] as { columns: Array<CapturedColumn> };
|
|
1697
|
+
return props.columns;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
describe("Clusters list", () => {
|
|
1701
|
+
test("each metric column carries its header tooltip; the rest carry none", async () => {
|
|
1702
|
+
const columns: Array<CapturedColumn> = await clusterColumns();
|
|
1703
|
+
const tooltips: Record<string, string | undefined> = {};
|
|
1704
|
+
for (const column of columns) {
|
|
1705
|
+
tooltips[column.title] = column.headerTooltip;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
for (const [title, key] of CLUSTER_LIST_TITLES) {
|
|
1709
|
+
expect({ title, tooltip: tooltips[title] }).toEqual({
|
|
1710
|
+
title,
|
|
1711
|
+
tooltip: CEPH_METRIC_DESCRIPTIONS[key],
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
for (const title of [
|
|
1715
|
+
"Name",
|
|
1716
|
+
"Status",
|
|
1717
|
+
"Version",
|
|
1718
|
+
"Last Seen",
|
|
1719
|
+
"Labels",
|
|
1720
|
+
"Owners",
|
|
1721
|
+
]) {
|
|
1722
|
+
expect({ title, tooltip: tooltips[title] }).toEqual({
|
|
1723
|
+
title,
|
|
1724
|
+
tooltip: undefined,
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
test("the Capacity bar colours match the thresholds its text names", async () => {
|
|
1730
|
+
const columns: Array<CapturedColumn> = await clusterColumns();
|
|
1731
|
+
const capacity: CapturedColumn = columns.find((c: CapturedColumn) => {
|
|
1732
|
+
return c.title === "Capacity";
|
|
1733
|
+
})!;
|
|
1734
|
+
|
|
1735
|
+
const barFor: (pct: number) => string = (pct: number): string => {
|
|
1736
|
+
const { container, unmount } = render(
|
|
1737
|
+
capacity.getElement!({ capacityUsedPercent: pct }),
|
|
1738
|
+
);
|
|
1739
|
+
const bar: Element | null = container.querySelector(
|
|
1740
|
+
"div.h-2.rounded-full[style]",
|
|
1741
|
+
);
|
|
1742
|
+
const className: string = bar?.className || "";
|
|
1743
|
+
unmount();
|
|
1744
|
+
return className;
|
|
1745
|
+
};
|
|
1746
|
+
|
|
1747
|
+
expect(barFor(74.9)).toContain("bg-emerald-500");
|
|
1748
|
+
expect(barFor(75)).toContain("bg-amber-500");
|
|
1749
|
+
expect(barFor(89.9)).toContain("bg-amber-500");
|
|
1750
|
+
expect(barFor(90)).toContain("bg-red-500");
|
|
1751
|
+
expect(CEPH_METRIC_DESCRIPTIONS.clusterCapacity).toMatch(
|
|
1752
|
+
/amber at 75% and red at 90%/,
|
|
1753
|
+
);
|
|
1754
|
+
});
|
|
1755
|
+
|
|
1756
|
+
test("the OSDs cell turns amber when any OSD is down or out, as its text says", async () => {
|
|
1757
|
+
const columns: Array<CapturedColumn> = await clusterColumns();
|
|
1758
|
+
const osds: CapturedColumn = columns.find((c: CapturedColumn) => {
|
|
1759
|
+
return c.title === "OSDs";
|
|
1760
|
+
})!;
|
|
1761
|
+
|
|
1762
|
+
const allGood: ReturnType<typeof render> = render(
|
|
1763
|
+
osds.getElement!({ osdCount: 4, osdUpCount: 4, osdInCount: 4 }),
|
|
1764
|
+
);
|
|
1765
|
+
expect(allGood.getByText("4 up / 4 in / 4 total")).not.toHaveClass(
|
|
1766
|
+
"text-amber-700",
|
|
1767
|
+
);
|
|
1768
|
+
allGood.unmount();
|
|
1769
|
+
|
|
1770
|
+
const oneOut: ReturnType<typeof render> = render(
|
|
1771
|
+
osds.getElement!({ osdCount: 4, osdUpCount: 4, osdInCount: 3 }),
|
|
1772
|
+
);
|
|
1773
|
+
expect(oneOut.getByText("4 up / 3 in / 4 total")).toHaveClass(
|
|
1774
|
+
"text-amber-700",
|
|
1775
|
+
);
|
|
1776
|
+
});
|
|
1777
|
+
});
|