@oneuptime/common 14.0.3 → 14.0.6
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/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/Index.ts +16 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
- 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/1795000000000-AddDatabaseServerTables.ts +673 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -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/IncidentReminderRuleService.ts +103 -23
- package/Server/Services/Index.ts +16 -0
- package/Server/Services/InventoryItemService.ts +113 -5
- package/Server/Services/IoTDeviceService.ts +145 -37
- package/Server/Services/MetricService.ts +1 -1
- 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 +84 -2
- package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +81 -2
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
- package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +192 -9
- package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2264 -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/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/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/EventOverlayScope.test.ts +147 -0
- package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
- package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -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/NetworkTopologyExplorerSearch.test.tsx +1267 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
- 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 +4 -1
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
- package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
- package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
- 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/ResourceFeedModels.test.ts +24 -5
- 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/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/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -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/MetricServiceAggregate.test.ts +12 -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/SavedViewDefaults.test.ts +325 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
- 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/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
- package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
- package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
- package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -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/MetricDecimalThreshold.test.ts +417 -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 +1815 -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/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/BaseDatabase/AggregationType.test.ts +5 -1
- package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
- package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -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 +292 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +272 -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 +202 -0
- package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -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/ForeignHiddenRuleResilience.test.tsx +1147 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
- package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
- package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
- 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/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
- package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
- package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
- package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
- package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +70 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +21 -0
- package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
- package/Tests/UI/Utils/Navigation.test.ts +102 -0
- package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
- package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -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/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
- package/Tests/Utils/ValueFormatter.test.ts +50 -0
- package/Tests/tsconfig.json +16 -0
- package/Types/AI/AIResourceContext.ts +7 -0
- package/Types/BaseDatabase/AggregationType.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +22 -8
- 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/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 +267 -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/SessionReplayCustomEvents.ts +29 -3
- package/Types/Rum/WebVitals.ts +13 -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/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/Input/Input.tsx +11 -0
- 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 +4 -4
- 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/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
- 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/Telemetry/TelemetryEntityNames.ts +7 -0
- package/UI/Utils/TelemetryService.ts +4 -3
- package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -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/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/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/Index.js +16 -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/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/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 +4 -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/IncidentReminderRuleService.js +73 -23
- package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +16 -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/MetricService.js +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 +73 -2
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +58 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +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/CompareCriteria.js +31 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +135 -14
- 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/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/BaseDatabase/AggregationType.js +7 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +22 -8
- 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/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 +234 -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/SessionReplayCustomEvents.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +12 -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/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/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.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 +4 -4
- 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/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/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/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/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.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/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,1267 @@
|
|
|
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
|
+
waitFor,
|
|
17
|
+
within,
|
|
18
|
+
} from "@testing-library/react";
|
|
19
|
+
import React, { ReactElement } from "react";
|
|
20
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
21
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
22
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
23
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
24
|
+
import NetworkTopologyExplorer, {
|
|
25
|
+
ComponentProps as ExplorerProps,
|
|
26
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkTopologyExplorer";
|
|
27
|
+
import { ComponentProps as LiveViewProps } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkTopologyLiveView";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Issue #3981 — the Device Topology explorer's search box, rendered for real.
|
|
31
|
+
*
|
|
32
|
+
* The customer's complaint was that the box only filtered the level on
|
|
33
|
+
* screen: finding "Unit 104822" meant knowing which region and which market
|
|
34
|
+
* to open first, which is exactly the thing somebody searching does not
|
|
35
|
+
* know. The fix mounts the Network Map's SiteSearchBox in the explorer, so
|
|
36
|
+
* one box now gives two answers — typing still narrows this level's cards,
|
|
37
|
+
* and the dropdown lists matches from ANYWHERE in the hierarchy, each with
|
|
38
|
+
* the path to it, and picking one drills straight there.
|
|
39
|
+
*
|
|
40
|
+
* Everything between the keystroke and the drill is real here: the explorer,
|
|
41
|
+
* the search box, the Input underneath it, the cards, the breadcrumb and the
|
|
42
|
+
* health chips. Only the edges are stubbed — the HTTP layer answers
|
|
43
|
+
* /network-site/children and /network-site/search from an in-memory
|
|
44
|
+
* hierarchy, the URL helper records what the explorer writes, and the device
|
|
45
|
+
* graph is replaced by a stub that prints the scope it was handed. That
|
|
46
|
+
* keeps the assertions about behaviour a reader can see (which cards, which
|
|
47
|
+
* level, which graph) rather than about which setter ran.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
type PostOptions = {
|
|
51
|
+
url: { toString: () => string };
|
|
52
|
+
data?: JSONObject | undefined;
|
|
53
|
+
headers?: Dictionary<string> | undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const postMock: MockFunction = getJestMockFunction();
|
|
57
|
+
const setQueryStringMock: MockFunction = getJestMockFunction();
|
|
58
|
+
const getQueryStringByNameMock: MockFunction = getJestMockFunction();
|
|
59
|
+
const navigateMock: MockFunction = getJestMockFunction();
|
|
60
|
+
|
|
61
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
62
|
+
return {
|
|
63
|
+
__esModule: true,
|
|
64
|
+
default: {
|
|
65
|
+
post: (...args: Array<unknown>): unknown => {
|
|
66
|
+
return postMock(...args);
|
|
67
|
+
},
|
|
68
|
+
getFriendlyMessage: (error: unknown): string => {
|
|
69
|
+
return error instanceof Error ? error.message : "Request failed";
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
76
|
+
return {
|
|
77
|
+
__esModule: true,
|
|
78
|
+
default: {
|
|
79
|
+
getCommonHeaders: (): Dictionary<string> => {
|
|
80
|
+
return { tenantid: "project-3981" };
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
87
|
+
return {
|
|
88
|
+
__esModule: true,
|
|
89
|
+
default: {
|
|
90
|
+
getQueryStringByName: (...args: Array<unknown>): unknown => {
|
|
91
|
+
return getQueryStringByNameMock(...args);
|
|
92
|
+
},
|
|
93
|
+
setQueryString: (...args: Array<unknown>): unknown => {
|
|
94
|
+
return setQueryStringMock(...args);
|
|
95
|
+
},
|
|
96
|
+
navigate: (...args: Array<unknown>): unknown => {
|
|
97
|
+
return navigateMock(...args);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
104
|
+
return {
|
|
105
|
+
__esModule: true,
|
|
106
|
+
default: {
|
|
107
|
+
getCurrentProjectId: (): ObjectID => {
|
|
108
|
+
return new ObjectID("dde060c6-fe0d-49ce-b44c-4035a13bc1db");
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
jest.mock("react-i18next", () => {
|
|
115
|
+
return {
|
|
116
|
+
useTranslation: () => {
|
|
117
|
+
return {
|
|
118
|
+
t: (value: string): string => {
|
|
119
|
+
return value;
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* The device graph is its own component with its own tests. Here it only
|
|
128
|
+
* has to say which site it was scoped to and which layout it opened on —
|
|
129
|
+
* the two things the drill decides.
|
|
130
|
+
*/
|
|
131
|
+
jest.mock(
|
|
132
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkTopologyLiveView",
|
|
133
|
+
() => {
|
|
134
|
+
return {
|
|
135
|
+
__esModule: true,
|
|
136
|
+
default: (props: LiveViewProps): ReactElement => {
|
|
137
|
+
return (
|
|
138
|
+
<div
|
|
139
|
+
data-testid="live-view-stub"
|
|
140
|
+
data-site-id={props.siteId || ""}
|
|
141
|
+
data-layout-mode={props.layoutMode || ""}
|
|
142
|
+
>
|
|
143
|
+
{`siteId=${props.siteId || "none"} layoutMode=${
|
|
144
|
+
props.layoutMode || "none"
|
|
145
|
+
}`}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/*
|
|
154
|
+
* ------------------------------------------------------------------
|
|
155
|
+
* An in-memory hierarchy: Franchise A > Region East > Market 5 > Unit
|
|
156
|
+
* 104822, plus two sibling regions so the root level has something to
|
|
157
|
+
* narrow and a second branch to drill sideways into.
|
|
158
|
+
* ------------------------------------------------------------------
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
const FRANCHISE_ID: string = "franchise-a";
|
|
162
|
+
const REGION_EAST_ID: string = "region-east";
|
|
163
|
+
const REGION_WEST_ID: string = "region-west";
|
|
164
|
+
const REGION_CENTRAL_ID: string = "region-central";
|
|
165
|
+
const MARKET_ID: string = "market-5";
|
|
166
|
+
const MARKET_WEST_ID: string = "market-9";
|
|
167
|
+
const UNIT_ID: string = "unit-104822";
|
|
168
|
+
const OTHER_UNIT_ID: string = "unit-200";
|
|
169
|
+
|
|
170
|
+
const UNIT_NAME: string = "Unit 104822 - Michigan Ave";
|
|
171
|
+
const UNIT_PATH: string = "Franchise A / Region East / Market 5";
|
|
172
|
+
|
|
173
|
+
const SEARCH_ID: string = "topology-hierarchy-search";
|
|
174
|
+
|
|
175
|
+
function deviceStats(input: {
|
|
176
|
+
down?: number;
|
|
177
|
+
degraded?: number;
|
|
178
|
+
healthy?: number;
|
|
179
|
+
}): JSONObject {
|
|
180
|
+
const down: number = input.down || 0;
|
|
181
|
+
const degraded: number = input.degraded || 0;
|
|
182
|
+
const healthy: number = input.healthy || 0;
|
|
183
|
+
return {
|
|
184
|
+
down: down,
|
|
185
|
+
degraded: degraded,
|
|
186
|
+
healthy: healthy,
|
|
187
|
+
unknown: 0,
|
|
188
|
+
total: down + degraded + healthy,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function childRow(input: {
|
|
193
|
+
id: string;
|
|
194
|
+
name: string;
|
|
195
|
+
siteType: string;
|
|
196
|
+
isUnitLevel?: boolean;
|
|
197
|
+
down?: number;
|
|
198
|
+
healthy?: number;
|
|
199
|
+
}): JSONObject {
|
|
200
|
+
const stats: JSONObject = deviceStats({
|
|
201
|
+
down: input.down || 0,
|
|
202
|
+
healthy: input.healthy || 0,
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
id: input.id,
|
|
206
|
+
name: input.name,
|
|
207
|
+
siteType: input.siteType,
|
|
208
|
+
isUnitLevel: input.isUnitLevel === true,
|
|
209
|
+
childSiteCount: input.isUnitLevel ? 0 : 1,
|
|
210
|
+
deviceCount: stats["total"] as number,
|
|
211
|
+
deviceStats: stats,
|
|
212
|
+
unitStats: { totalUnits: 0, operationalUnits: 0 },
|
|
213
|
+
uptimePercent: null,
|
|
214
|
+
dailyUptimePercent: null,
|
|
215
|
+
isUnderMaintenance: false,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function crumb(
|
|
220
|
+
id: string,
|
|
221
|
+
name: string,
|
|
222
|
+
siteType: string,
|
|
223
|
+
isUnitLevel: boolean = false,
|
|
224
|
+
): JSONObject {
|
|
225
|
+
return { id: id, name: name, siteType: siteType, isUnitLevel: isUnitLevel };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/*
|
|
229
|
+
* attachedDeviceCount must be above zero on every level: at the root a
|
|
230
|
+
* hierarchy nobody attached a device to falls back to the flat map, and
|
|
231
|
+
* this suite is about the hierarchy.
|
|
232
|
+
*/
|
|
233
|
+
function level(
|
|
234
|
+
breadcrumb: Array<JSONObject>,
|
|
235
|
+
children: Array<JSONObject>,
|
|
236
|
+
ownDeviceStats: JSONObject = deviceStats({}),
|
|
237
|
+
): JSONObject {
|
|
238
|
+
return {
|
|
239
|
+
breadcrumb: breadcrumb,
|
|
240
|
+
children: children,
|
|
241
|
+
links: [],
|
|
242
|
+
ownDeviceStats: ownDeviceStats,
|
|
243
|
+
deviceScope: { attachedDeviceCount: 24, unattachedDeviceCount: 0 },
|
|
244
|
+
childrenTruncated: false,
|
|
245
|
+
descendantCountsTruncated: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const ROOT_LEVEL: JSONObject = level(
|
|
250
|
+
[],
|
|
251
|
+
[
|
|
252
|
+
childRow({
|
|
253
|
+
id: REGION_CENTRAL_ID,
|
|
254
|
+
name: "Region Central",
|
|
255
|
+
siteType: "Region",
|
|
256
|
+
healthy: 3,
|
|
257
|
+
}),
|
|
258
|
+
childRow({
|
|
259
|
+
id: REGION_EAST_ID,
|
|
260
|
+
name: "Region East",
|
|
261
|
+
siteType: "Region",
|
|
262
|
+
healthy: 10,
|
|
263
|
+
}),
|
|
264
|
+
// The one region with a device down, so the "Down" chip has a match.
|
|
265
|
+
childRow({
|
|
266
|
+
id: REGION_WEST_ID,
|
|
267
|
+
name: "Region West",
|
|
268
|
+
siteType: "Region",
|
|
269
|
+
down: 2,
|
|
270
|
+
healthy: 5,
|
|
271
|
+
}),
|
|
272
|
+
],
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const MARKET_BREADCRUMB: Array<JSONObject> = [
|
|
276
|
+
crumb(FRANCHISE_ID, "Franchise A", "Franchise"),
|
|
277
|
+
crumb(REGION_EAST_ID, "Region East", "Region"),
|
|
278
|
+
crumb(MARKET_ID, "Market 5", "Market"),
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
const LEVELS: Dictionary<JSONObject> = {
|
|
282
|
+
[REGION_EAST_ID]: level(
|
|
283
|
+
[
|
|
284
|
+
crumb(FRANCHISE_ID, "Franchise A", "Franchise"),
|
|
285
|
+
crumb(REGION_EAST_ID, "Region East", "Region"),
|
|
286
|
+
],
|
|
287
|
+
[childRow({ id: MARKET_ID, name: "Market 5", siteType: "Market" })],
|
|
288
|
+
),
|
|
289
|
+
[REGION_WEST_ID]: level(
|
|
290
|
+
[
|
|
291
|
+
crumb(FRANCHISE_ID, "Franchise A", "Franchise"),
|
|
292
|
+
crumb(REGION_WEST_ID, "Region West", "Region"),
|
|
293
|
+
],
|
|
294
|
+
[
|
|
295
|
+
childRow({
|
|
296
|
+
id: MARKET_WEST_ID,
|
|
297
|
+
name: "Market 9",
|
|
298
|
+
siteType: "Market",
|
|
299
|
+
down: 2,
|
|
300
|
+
}),
|
|
301
|
+
],
|
|
302
|
+
),
|
|
303
|
+
[MARKET_ID]: level(MARKET_BREADCRUMB, [
|
|
304
|
+
childRow({
|
|
305
|
+
id: UNIT_ID,
|
|
306
|
+
name: UNIT_NAME,
|
|
307
|
+
siteType: "Unit",
|
|
308
|
+
isUnitLevel: true,
|
|
309
|
+
down: 1,
|
|
310
|
+
}),
|
|
311
|
+
childRow({
|
|
312
|
+
id: OTHER_UNIT_ID,
|
|
313
|
+
name: "Unit 200 - State St",
|
|
314
|
+
siteType: "Unit",
|
|
315
|
+
isUnitLevel: true,
|
|
316
|
+
healthy: 4,
|
|
317
|
+
}),
|
|
318
|
+
]),
|
|
319
|
+
[UNIT_ID]: level(
|
|
320
|
+
[...MARKET_BREADCRUMB, crumb(UNIT_ID, UNIT_NAME, "Unit", true)],
|
|
321
|
+
[],
|
|
322
|
+
deviceStats({ down: 1, healthy: 3 }),
|
|
323
|
+
),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
interface IndexedSite {
|
|
327
|
+
id: string;
|
|
328
|
+
name: string;
|
|
329
|
+
siteType: string;
|
|
330
|
+
isUnitLevel: boolean;
|
|
331
|
+
path: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const SEARCH_INDEX: Array<IndexedSite> = [
|
|
335
|
+
{
|
|
336
|
+
id: FRANCHISE_ID,
|
|
337
|
+
name: "Franchise A",
|
|
338
|
+
siteType: "Franchise",
|
|
339
|
+
isUnitLevel: false,
|
|
340
|
+
path: "",
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: REGION_CENTRAL_ID,
|
|
344
|
+
name: "Region Central",
|
|
345
|
+
siteType: "Region",
|
|
346
|
+
isUnitLevel: false,
|
|
347
|
+
path: "Franchise A",
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: REGION_EAST_ID,
|
|
351
|
+
name: "Region East",
|
|
352
|
+
siteType: "Region",
|
|
353
|
+
isUnitLevel: false,
|
|
354
|
+
path: "Franchise A",
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: REGION_WEST_ID,
|
|
358
|
+
name: "Region West",
|
|
359
|
+
siteType: "Region",
|
|
360
|
+
isUnitLevel: false,
|
|
361
|
+
path: "Franchise A",
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: MARKET_ID,
|
|
365
|
+
name: "Market 5",
|
|
366
|
+
siteType: "Market",
|
|
367
|
+
isUnitLevel: false,
|
|
368
|
+
path: "Franchise A / Region East",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: UNIT_ID,
|
|
372
|
+
name: UNIT_NAME,
|
|
373
|
+
siteType: "Unit",
|
|
374
|
+
isUnitLevel: true,
|
|
375
|
+
path: UNIT_PATH,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: OTHER_UNIT_ID,
|
|
379
|
+
name: "Unit 200 - State St",
|
|
380
|
+
siteType: "Unit",
|
|
381
|
+
isUnitLevel: true,
|
|
382
|
+
path: UNIT_PATH,
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
* The server's rule, in miniature: every word, anywhere in the name. The
|
|
388
|
+
* word "unit" is also made to hit the result cap so the truncation line has
|
|
389
|
+
* a case of its own.
|
|
390
|
+
*/
|
|
391
|
+
function searchFixture(searchText: string): JSONObject {
|
|
392
|
+
const words: Array<string> = searchText
|
|
393
|
+
.toLowerCase()
|
|
394
|
+
.split(/\s+/)
|
|
395
|
+
.filter((word: string): boolean => {
|
|
396
|
+
return word.length > 0;
|
|
397
|
+
});
|
|
398
|
+
const results: Array<JSONObject> = SEARCH_INDEX.filter(
|
|
399
|
+
(site: IndexedSite): boolean => {
|
|
400
|
+
return (
|
|
401
|
+
words.length > 0 &&
|
|
402
|
+
words.every((word: string): boolean => {
|
|
403
|
+
return site.name.toLowerCase().includes(word);
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
},
|
|
407
|
+
).map((site: IndexedSite): JSONObject => {
|
|
408
|
+
return {
|
|
409
|
+
id: site.id,
|
|
410
|
+
name: site.name,
|
|
411
|
+
siteType: site.siteType,
|
|
412
|
+
isUnitLevel: site.isUnitLevel,
|
|
413
|
+
path: site.path,
|
|
414
|
+
currentMonitorStatus:
|
|
415
|
+
site.id === UNIT_ID
|
|
416
|
+
? {
|
|
417
|
+
id: "status-operational",
|
|
418
|
+
name: "Operational",
|
|
419
|
+
color: "#16a34a",
|
|
420
|
+
priority: 1,
|
|
421
|
+
isOperationalState: true,
|
|
422
|
+
}
|
|
423
|
+
: undefined,
|
|
424
|
+
} as JSONObject;
|
|
425
|
+
});
|
|
426
|
+
return { results: results, isTruncated: searchText.trim() === "unit" };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function routeOf(options: PostOptions): string {
|
|
430
|
+
return options.url.toString();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function defaultPost(options: PostOptions): Promise<unknown> {
|
|
434
|
+
const route: string = routeOf(options);
|
|
435
|
+
if (route.includes("/network-site/children")) {
|
|
436
|
+
const siteId: string | undefined = options.data?.["siteId"] as
|
|
437
|
+
| string
|
|
438
|
+
| undefined;
|
|
439
|
+
const body: JSONObject | undefined = siteId ? LEVELS[siteId] : ROOT_LEVEL;
|
|
440
|
+
if (!body) {
|
|
441
|
+
return Promise.reject(new Error(`No level fixture for ${siteId}`));
|
|
442
|
+
}
|
|
443
|
+
return Promise.resolve({ data: body });
|
|
444
|
+
}
|
|
445
|
+
if (route.includes("/network-site/search")) {
|
|
446
|
+
return Promise.resolve({
|
|
447
|
+
data: searchFixture(String(options.data?.["searchText"] || "")),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return Promise.reject(new Error(`Unexpected request to ${route}`));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function postsTo(route: string): Array<PostOptions> {
|
|
454
|
+
return postMock.mock.calls
|
|
455
|
+
.map((call: Array<unknown>): PostOptions => {
|
|
456
|
+
return call[0] as PostOptions;
|
|
457
|
+
})
|
|
458
|
+
.filter((options: PostOptions): boolean => {
|
|
459
|
+
return routeOf(options).includes(route);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function childrenPosts(): Array<PostOptions> {
|
|
464
|
+
return postsTo("/network-site/children");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function searchPosts(): Array<PostOptions> {
|
|
468
|
+
return postsTo("/network-site/search");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function searchInput(): HTMLInputElement {
|
|
472
|
+
return screen.getByTestId(SEARCH_ID) as HTMLInputElement;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function typeSearch(value: string): void {
|
|
476
|
+
const input: HTMLInputElement = searchInput();
|
|
477
|
+
fireEvent.focus(input);
|
|
478
|
+
fireEvent.change(input, { target: { value: value } });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function resultCountText(): string {
|
|
482
|
+
return (
|
|
483
|
+
screen.getByTestId("topology-hierarchy-result-count").textContent || ""
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function waitMs(ms: number): Promise<void> {
|
|
488
|
+
return new Promise<void>((resolve: () => void) => {
|
|
489
|
+
setTimeout(resolve, ms);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function renderExplorer(
|
|
494
|
+
props: ExplorerProps = {},
|
|
495
|
+
firstCardId: string = REGION_EAST_ID,
|
|
496
|
+
): Promise<void> {
|
|
497
|
+
render(<NetworkTopologyExplorer {...props} />);
|
|
498
|
+
await screen.findByTestId(`site-card-${firstCardId}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Type the query and wait for the debounced dropdown to answer it.
|
|
502
|
+
async function searchFor(value: string, hitId: string): Promise<HTMLElement> {
|
|
503
|
+
typeSearch(value);
|
|
504
|
+
return screen.findByTestId(`${SEARCH_ID}-result-${hitId}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
beforeEach(() => {
|
|
508
|
+
postMock.mockReset();
|
|
509
|
+
postMock.mockImplementation((...args: Array<unknown>): unknown => {
|
|
510
|
+
return defaultPost(args[0] as PostOptions);
|
|
511
|
+
});
|
|
512
|
+
setQueryStringMock.mockReset();
|
|
513
|
+
navigateMock.mockReset();
|
|
514
|
+
getQueryStringByNameMock.mockReset();
|
|
515
|
+
getQueryStringByNameMock.mockReturnValue(null);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
afterEach(() => {
|
|
519
|
+
cleanup();
|
|
520
|
+
jest.restoreAllMocks();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
/*
|
|
524
|
+
* The box itself: the explorer mounts the shared SiteSearchBox under its
|
|
525
|
+
* own test-id prefix and copy, and hides the box's local count because the
|
|
526
|
+
* explorer already prints one beside it.
|
|
527
|
+
*/
|
|
528
|
+
describe("the explorer mounts the hierarchy-wide search box", () => {
|
|
529
|
+
test("the root renders its region cards and the new search box", async () => {
|
|
530
|
+
await renderExplorer();
|
|
531
|
+
|
|
532
|
+
expect(screen.getByTestId(`site-card-${REGION_EAST_ID}`)).toBeVisible();
|
|
533
|
+
expect(screen.getByTestId(`site-card-${REGION_WEST_ID}`)).toBeVisible();
|
|
534
|
+
expect(screen.getByTestId(`site-card-${REGION_CENTRAL_ID}`)).toBeVisible();
|
|
535
|
+
|
|
536
|
+
const input: HTMLInputElement = searchInput();
|
|
537
|
+
expect(input).toHaveAttribute(
|
|
538
|
+
"placeholder",
|
|
539
|
+
"Search sites by name — anywhere in your network",
|
|
540
|
+
);
|
|
541
|
+
expect(input).toHaveAttribute(
|
|
542
|
+
"aria-label",
|
|
543
|
+
"Search sites by name — anywhere in your network",
|
|
544
|
+
);
|
|
545
|
+
expect(input).toHaveValue("");
|
|
546
|
+
expect(resultCountText()).toBe("3 of 3 regions · Updates every minute");
|
|
547
|
+
|
|
548
|
+
// The root level was the only thing fetched: nothing searched yet.
|
|
549
|
+
expect(childrenPosts()).toHaveLength(1);
|
|
550
|
+
expect(childrenPosts()[0]!.data).toEqual({});
|
|
551
|
+
expect(searchPosts()).toHaveLength(0);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
/*
|
|
555
|
+
* The Network Map keeps the default "network-map-search" prefix; the
|
|
556
|
+
* explorer must never render under it, or two pages' tests (and any
|
|
557
|
+
* automation built on them) would be reading each other's ids.
|
|
558
|
+
*/
|
|
559
|
+
test("every id the box renders uses the explorer's prefix", async () => {
|
|
560
|
+
await renderExplorer();
|
|
561
|
+
await searchFor("104822", UNIT_ID);
|
|
562
|
+
|
|
563
|
+
expect(screen.getByTestId(`${SEARCH_ID}-results`)).toBeVisible();
|
|
564
|
+
expect(screen.getByTestId(`${SEARCH_ID}-clear`)).toBeVisible();
|
|
565
|
+
expect(screen.queryByTestId("network-map-search")).not.toBeInTheDocument();
|
|
566
|
+
expect(
|
|
567
|
+
screen.queryByTestId("network-map-search-results"),
|
|
568
|
+
).not.toBeInTheDocument();
|
|
569
|
+
expect(
|
|
570
|
+
screen.queryByTestId(`network-map-search-result-${UNIT_ID}`),
|
|
571
|
+
).not.toBeInTheDocument();
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("the box is a combobox wired to its listbox", async () => {
|
|
575
|
+
await renderExplorer();
|
|
576
|
+
const input: HTMLInputElement = searchInput();
|
|
577
|
+
|
|
578
|
+
expect(input).toHaveAttribute("role", "combobox");
|
|
579
|
+
expect(input).toHaveAttribute("aria-autocomplete", "list");
|
|
580
|
+
expect(input).toHaveAttribute("aria-haspopup", "listbox");
|
|
581
|
+
expect(input).toHaveAttribute("aria-expanded", "false");
|
|
582
|
+
// aria-controls only while there is something to control.
|
|
583
|
+
expect(input).not.toHaveAttribute("aria-controls");
|
|
584
|
+
expect(input).not.toHaveAttribute("aria-activedescendant");
|
|
585
|
+
|
|
586
|
+
const hit: HTMLElement = await searchFor("104822", UNIT_ID);
|
|
587
|
+
|
|
588
|
+
expect(input).toHaveAttribute("aria-expanded", "true");
|
|
589
|
+
expect(input).toHaveAttribute("aria-controls", `${SEARCH_ID}-listbox`);
|
|
590
|
+
const listbox: HTMLElement = screen.getByRole("listbox", {
|
|
591
|
+
name: "Site search results",
|
|
592
|
+
});
|
|
593
|
+
expect(listbox).toHaveAttribute("id", `${SEARCH_ID}-listbox`);
|
|
594
|
+
expect(hit).toHaveAttribute("id", `${SEARCH_ID}-option-${UNIT_ID}`);
|
|
595
|
+
expect(hit).toHaveAttribute("role", "option");
|
|
596
|
+
expect(hit).toHaveAttribute("aria-selected", "false");
|
|
597
|
+
|
|
598
|
+
fireEvent.keyDown(input, { key: "ArrowDown" });
|
|
599
|
+
expect(input).toHaveAttribute(
|
|
600
|
+
"aria-activedescendant",
|
|
601
|
+
`${SEARCH_ID}-option-${UNIT_ID}`,
|
|
602
|
+
);
|
|
603
|
+
expect(hit).toHaveAttribute("aria-selected", "true");
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
/*
|
|
607
|
+
* The explorer prints "x of y regions" to the right of the box already.
|
|
608
|
+
* The box's own "Showing x of y at this level" line would say the same
|
|
609
|
+
* thing twice, one line apart.
|
|
610
|
+
*/
|
|
611
|
+
test("the box's own local-count line is not rendered", async () => {
|
|
612
|
+
await renderExplorer();
|
|
613
|
+
typeSearch("east");
|
|
614
|
+
|
|
615
|
+
expect(resultCountText()).toBe("1 of 3 regions · Updates every minute");
|
|
616
|
+
expect(
|
|
617
|
+
screen.queryByTestId(`${SEARCH_ID}-local-count`),
|
|
618
|
+
).not.toBeInTheDocument();
|
|
619
|
+
expect(
|
|
620
|
+
screen.queryByTestId("network-map-search-local-count"),
|
|
621
|
+
).not.toBeInTheDocument();
|
|
622
|
+
expect(screen.queryByText(/at this level$/)).not.toBeInTheDocument();
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
/*
|
|
627
|
+
* The customer's actual scenario, end to end: a unit number typed at the
|
|
628
|
+
* root, where no card matches it, found four levels down and opened in one
|
|
629
|
+
* click.
|
|
630
|
+
*/
|
|
631
|
+
describe("finding a unit from the root", () => {
|
|
632
|
+
test("typing a unit number empties the level and lists the unit with its path", async () => {
|
|
633
|
+
await renderExplorer();
|
|
634
|
+
typeSearch("104822");
|
|
635
|
+
|
|
636
|
+
// The local half answers at once: nothing at this level matches.
|
|
637
|
+
expect(resultCountText()).toBe("0 of 3 regions · Updates every minute");
|
|
638
|
+
expect(
|
|
639
|
+
screen.queryByTestId(`site-card-${REGION_EAST_ID}`),
|
|
640
|
+
).not.toBeInTheDocument();
|
|
641
|
+
expect(screen.getByText("No sites match your filters")).toBeVisible();
|
|
642
|
+
|
|
643
|
+
// The remote half is debounced, so nothing has been asked for yet.
|
|
644
|
+
expect(searchPosts()).toHaveLength(0);
|
|
645
|
+
|
|
646
|
+
const hit: HTMLElement = await screen.findByTestId(
|
|
647
|
+
`${SEARCH_ID}-result-${UNIT_ID}`,
|
|
648
|
+
);
|
|
649
|
+
expect(hit).toHaveTextContent(UNIT_NAME);
|
|
650
|
+
expect(hit).toHaveTextContent(UNIT_PATH);
|
|
651
|
+
expect(hit).toHaveTextContent("Unit");
|
|
652
|
+
expect(screen.getByText(UNIT_PATH)).toBeVisible();
|
|
653
|
+
|
|
654
|
+
expect(searchPosts()).toHaveLength(1);
|
|
655
|
+
expect(searchPosts()[0]!.data).toEqual({ searchText: "104822" });
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test("clicking the unit opens its device topology, scoped and tiered", async () => {
|
|
659
|
+
await renderExplorer();
|
|
660
|
+
const hit: HTMLElement = await searchFor("104822", UNIT_ID);
|
|
661
|
+
const childrenBefore: number = childrenPosts().length;
|
|
662
|
+
|
|
663
|
+
fireEvent.click(hit);
|
|
664
|
+
|
|
665
|
+
const stub: HTMLElement = await screen.findByTestId("live-view-stub");
|
|
666
|
+
expect(stub).toHaveAttribute("data-site-id", UNIT_ID);
|
|
667
|
+
expect(stub).toHaveAttribute("data-layout-mode", "tiered");
|
|
668
|
+
expect(stub).toHaveTextContent(`siteId=${UNIT_ID} layoutMode=tiered`);
|
|
669
|
+
|
|
670
|
+
// Exactly one new level fetch, for the unit that was picked.
|
|
671
|
+
expect(childrenPosts()).toHaveLength(childrenBefore + 1);
|
|
672
|
+
expect(childrenPosts()[childrenPosts().length - 1]!.data).toEqual({
|
|
673
|
+
siteId: UNIT_ID,
|
|
674
|
+
});
|
|
675
|
+
expect(childrenPosts()[childrenPosts().length - 1]!.headers).toMatchObject({
|
|
676
|
+
tenantid: "project-3981",
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
// The level and its box are gone; the breadcrumb names the whole path.
|
|
680
|
+
expect(screen.queryByTestId(SEARCH_ID)).not.toBeInTheDocument();
|
|
681
|
+
const trail: HTMLElement = screen.getByRole("navigation", {
|
|
682
|
+
name: "Site breadcrumb",
|
|
683
|
+
});
|
|
684
|
+
expect(within(trail).getByText("Franchise A")).toBeVisible();
|
|
685
|
+
expect(within(trail).getByText("Region East")).toBeVisible();
|
|
686
|
+
expect(within(trail).getByText("Market 5")).toBeVisible();
|
|
687
|
+
expect(within(trail).getByText(UNIT_NAME)).toHaveAttribute(
|
|
688
|
+
"aria-current",
|
|
689
|
+
"page",
|
|
690
|
+
);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("the drill is mirrored to the URL once the 200 ms debounce passes", async () => {
|
|
694
|
+
await renderExplorer();
|
|
695
|
+
const hit: HTMLElement = await searchFor("104822", UNIT_ID);
|
|
696
|
+
|
|
697
|
+
fireEvent.click(hit);
|
|
698
|
+
// Not synchronously: the write is debounced.
|
|
699
|
+
expect(setQueryStringMock).not.toHaveBeenCalled();
|
|
700
|
+
|
|
701
|
+
await waitFor(() => {
|
|
702
|
+
expect(setQueryStringMock).toHaveBeenCalledWith({
|
|
703
|
+
topologySite: UNIT_ID,
|
|
704
|
+
topologyDevices: null,
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
expect(setQueryStringMock).toHaveBeenCalledTimes(1);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test("a page opened with a device layout of its own keeps it for the picked unit", async () => {
|
|
711
|
+
await renderExplorer({ siteLayoutMode: "force" });
|
|
712
|
+
fireEvent.click(await searchFor("104822", UNIT_ID));
|
|
713
|
+
|
|
714
|
+
const stub: HTMLElement = await screen.findByTestId("live-view-stub");
|
|
715
|
+
expect(stub).toHaveAttribute("data-site-id", UNIT_ID);
|
|
716
|
+
expect(stub).toHaveAttribute("data-layout-mode", "force");
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
/*
|
|
720
|
+
* The box clears on the way through. Arriving back at a level with the
|
|
721
|
+
* old question still typed into it would leave that level filtered by a
|
|
722
|
+
* unit number that matches nothing on it.
|
|
723
|
+
*/
|
|
724
|
+
test("walking back up from the unit finds an empty box and a full level", async () => {
|
|
725
|
+
await renderExplorer();
|
|
726
|
+
fireEvent.click(await searchFor("104822", UNIT_ID));
|
|
727
|
+
await screen.findByTestId("live-view-stub");
|
|
728
|
+
|
|
729
|
+
const trail: HTMLElement = screen.getByRole("navigation", {
|
|
730
|
+
name: "Site breadcrumb",
|
|
731
|
+
});
|
|
732
|
+
fireEvent.click(within(trail).getByText("Market 5"));
|
|
733
|
+
|
|
734
|
+
await screen.findByTestId(`site-card-${UNIT_ID}`);
|
|
735
|
+
expect(searchInput()).toHaveValue("");
|
|
736
|
+
expect(screen.getByTestId(`site-card-${OTHER_UNIT_ID}`)).toBeVisible();
|
|
737
|
+
expect(resultCountText()).toBe("2 of 2 units · Updates every minute");
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
/*
|
|
742
|
+
* Not every hit is a unit. A region or a market opens its own level of
|
|
743
|
+
* cards, exactly as clicking its card would have — the search only skips
|
|
744
|
+
* the levels in between.
|
|
745
|
+
*/
|
|
746
|
+
describe("picking a mid-level site", () => {
|
|
747
|
+
test("a market hit renders that market's level with its breadcrumb", async () => {
|
|
748
|
+
await renderExplorer();
|
|
749
|
+
const hit: HTMLElement = await searchFor("market 5", MARKET_ID);
|
|
750
|
+
expect(hit).toHaveTextContent("Franchise A / Region East");
|
|
751
|
+
|
|
752
|
+
fireEvent.click(hit);
|
|
753
|
+
|
|
754
|
+
await screen.findByTestId(`site-card-${UNIT_ID}`);
|
|
755
|
+
expect(screen.getByTestId(`site-card-${OTHER_UNIT_ID}`)).toBeVisible();
|
|
756
|
+
expect(screen.queryByTestId("live-view-stub")).not.toBeInTheDocument();
|
|
757
|
+
expect(
|
|
758
|
+
screen.getByText(
|
|
759
|
+
"Explore the units in Market 5. Each card includes the health of the devices below it.",
|
|
760
|
+
),
|
|
761
|
+
).toBeVisible();
|
|
762
|
+
expect(resultCountText()).toBe("2 of 2 units · Updates every minute");
|
|
763
|
+
|
|
764
|
+
const trail: HTMLElement = screen.getByRole("navigation", {
|
|
765
|
+
name: "Site breadcrumb",
|
|
766
|
+
});
|
|
767
|
+
expect(within(trail).getByTestId("site-breadcrumb-root")).toBeVisible();
|
|
768
|
+
expect(within(trail).getByText("Franchise A").tagName).toBe("BUTTON");
|
|
769
|
+
expect(within(trail).getByText("Region East").tagName).toBe("BUTTON");
|
|
770
|
+
expect(within(trail).getByText("Market 5")).toHaveAttribute(
|
|
771
|
+
"aria-current",
|
|
772
|
+
"page",
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
expect(childrenPosts()[childrenPosts().length - 1]!.data).toEqual({
|
|
776
|
+
siteId: MARKET_ID,
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("the destination level's box is cleared and its dropdown closed", async () => {
|
|
781
|
+
await renderExplorer();
|
|
782
|
+
fireEvent.click(await searchFor("market 5", MARKET_ID));
|
|
783
|
+
await screen.findByTestId(`site-card-${UNIT_ID}`);
|
|
784
|
+
|
|
785
|
+
expect(searchInput()).toHaveValue("");
|
|
786
|
+
expect(searchInput()).toHaveAttribute("aria-expanded", "false");
|
|
787
|
+
expect(
|
|
788
|
+
screen.queryByTestId(`${SEARCH_ID}-results`),
|
|
789
|
+
).not.toBeInTheDocument();
|
|
790
|
+
expect(
|
|
791
|
+
screen.queryByTestId("topology-hierarchy-reset-filters"),
|
|
792
|
+
).not.toBeInTheDocument();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("a hit in another branch drills sideways from a drilled level", async () => {
|
|
796
|
+
getQueryStringByNameMock.mockImplementation((name: unknown): unknown => {
|
|
797
|
+
return name === "topologySite" ? MARKET_ID : null;
|
|
798
|
+
});
|
|
799
|
+
await renderExplorer({}, UNIT_ID);
|
|
800
|
+
|
|
801
|
+
const hit: HTMLElement = await searchFor("region west", REGION_WEST_ID);
|
|
802
|
+
fireEvent.click(hit);
|
|
803
|
+
|
|
804
|
+
await screen.findByTestId(`site-card-${MARKET_WEST_ID}`);
|
|
805
|
+
const trail: HTMLElement = screen.getByRole("navigation", {
|
|
806
|
+
name: "Site breadcrumb",
|
|
807
|
+
});
|
|
808
|
+
expect(within(trail).getByText("Region West")).toHaveAttribute(
|
|
809
|
+
"aria-current",
|
|
810
|
+
"page",
|
|
811
|
+
);
|
|
812
|
+
expect(within(trail).queryByText("Market 5")).not.toBeInTheDocument();
|
|
813
|
+
await waitFor(() => {
|
|
814
|
+
expect(setQueryStringMock).toHaveBeenCalledWith({
|
|
815
|
+
topologySite: REGION_WEST_ID,
|
|
816
|
+
topologyDevices: null,
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
/*
|
|
822
|
+
* changeSite no-ops on an unchanged target. Picking the site already in
|
|
823
|
+
* view therefore only closes the question: the box clears, no level is
|
|
824
|
+
* refetched and the URL is left alone.
|
|
825
|
+
*/
|
|
826
|
+
test("picking the site already in view refetches nothing", async () => {
|
|
827
|
+
getQueryStringByNameMock.mockImplementation((name: unknown): unknown => {
|
|
828
|
+
return name === "topologySite" ? MARKET_ID : null;
|
|
829
|
+
});
|
|
830
|
+
await renderExplorer({}, UNIT_ID);
|
|
831
|
+
expect(childrenPosts()).toHaveLength(1);
|
|
832
|
+
|
|
833
|
+
fireEvent.click(await searchFor("market 5", MARKET_ID));
|
|
834
|
+
|
|
835
|
+
expect(searchInput()).toHaveValue("");
|
|
836
|
+
expect(screen.getByTestId(`site-card-${UNIT_ID}`)).toBeVisible();
|
|
837
|
+
await waitMs(300);
|
|
838
|
+
expect(childrenPosts()).toHaveLength(1);
|
|
839
|
+
expect(setQueryStringMock).not.toHaveBeenCalled();
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
/*
|
|
844
|
+
* The local half of the box, which is what the explorer's search did
|
|
845
|
+
* before issue #3981 and must keep doing: typing narrows the cards of the
|
|
846
|
+
* level in view on every keystroke.
|
|
847
|
+
*/
|
|
848
|
+
describe("typing still narrows the level in view", () => {
|
|
849
|
+
test("a region name narrows the cards and the count follows", async () => {
|
|
850
|
+
await renderExplorer();
|
|
851
|
+
typeSearch("west");
|
|
852
|
+
|
|
853
|
+
expect(screen.getByTestId(`site-card-${REGION_WEST_ID}`)).toBeVisible();
|
|
854
|
+
expect(
|
|
855
|
+
screen.queryByTestId(`site-card-${REGION_EAST_ID}`),
|
|
856
|
+
).not.toBeInTheDocument();
|
|
857
|
+
expect(
|
|
858
|
+
screen.queryByTestId(`site-card-${REGION_CENTRAL_ID}`),
|
|
859
|
+
).not.toBeInTheDocument();
|
|
860
|
+
expect(resultCountText()).toBe("1 of 3 regions · Updates every minute");
|
|
861
|
+
|
|
862
|
+
// Every word, in any order, the same rule the dropdown's server uses.
|
|
863
|
+
typeSearch("EAST region");
|
|
864
|
+
expect(screen.getByTestId(`site-card-${REGION_EAST_ID}`)).toBeVisible();
|
|
865
|
+
expect(resultCountText()).toBe("1 of 3 regions · Updates every minute");
|
|
866
|
+
|
|
867
|
+
typeSearch("region");
|
|
868
|
+
expect(resultCountText()).toBe("3 of 3 regions · Updates every minute");
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
test("the local narrowing never waits for the server", async () => {
|
|
872
|
+
await renderExplorer();
|
|
873
|
+
postMock.mockImplementation((...args: Array<unknown>): unknown => {
|
|
874
|
+
const options: PostOptions = args[0] as PostOptions;
|
|
875
|
+
if (routeOf(options).includes("/network-site/search")) {
|
|
876
|
+
return new Promise<unknown>(() => {
|
|
877
|
+
// Never answers.
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
return defaultPost(options);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
typeSearch("central");
|
|
884
|
+
|
|
885
|
+
expect(screen.getByTestId(`site-card-${REGION_CENTRAL_ID}`)).toBeVisible();
|
|
886
|
+
expect(resultCountText()).toBe("1 of 3 regions · Updates every minute");
|
|
887
|
+
expect(await screen.findByTestId(`${SEARCH_ID}-searching`)).toBeVisible();
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test("the search request carries the normalized text and the project header", async () => {
|
|
891
|
+
await renderExplorer();
|
|
892
|
+
typeSearch(" MICHIGAN 104822 ");
|
|
893
|
+
|
|
894
|
+
await screen.findByTestId(`${SEARCH_ID}-result-${UNIT_ID}`);
|
|
895
|
+
expect(searchPosts()).toHaveLength(1);
|
|
896
|
+
expect(searchPosts()[0]!.data).toEqual({ searchText: "michigan 104822" });
|
|
897
|
+
expect(searchPosts()[0]!.headers).toMatchObject({
|
|
898
|
+
tenantid: "project-3981",
|
|
899
|
+
});
|
|
900
|
+
expect(routeOf(searchPosts()[0]!)).toContain("/network-site/search");
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
test("a burst of keystrokes is one request, for the last text", async () => {
|
|
904
|
+
await renderExplorer();
|
|
905
|
+
for (const value of ["1", "10", "104", "1048", "10482", "104822"]) {
|
|
906
|
+
typeSearch(value);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
await screen.findByTestId(`${SEARCH_ID}-result-${UNIT_ID}`);
|
|
910
|
+
await waitMs(300);
|
|
911
|
+
expect(searchPosts()).toHaveLength(1);
|
|
912
|
+
expect(searchPosts()[0]!.data).toEqual({ searchText: "104822" });
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
/*
|
|
916
|
+
* One character matches most of an estate, so the box does not ask the
|
|
917
|
+
* server below two. Measured after trimming: padding a single letter
|
|
918
|
+
* with spaces is still a single letter.
|
|
919
|
+
*/
|
|
920
|
+
test.each(["e", " e ", " "])(
|
|
921
|
+
"%j narrows locally but never reaches the server",
|
|
922
|
+
async (value: string) => {
|
|
923
|
+
await renderExplorer();
|
|
924
|
+
typeSearch(value);
|
|
925
|
+
|
|
926
|
+
await waitMs(400);
|
|
927
|
+
expect(searchPosts()).toHaveLength(0);
|
|
928
|
+
expect(
|
|
929
|
+
screen.queryByTestId(`${SEARCH_ID}-results`),
|
|
930
|
+
).not.toBeInTheDocument();
|
|
931
|
+
expect(searchInput()).toHaveAttribute("aria-expanded", "false");
|
|
932
|
+
},
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
test("typing never writes the URL", async () => {
|
|
936
|
+
await renderExplorer();
|
|
937
|
+
await searchFor("104822", UNIT_ID);
|
|
938
|
+
await waitMs(300);
|
|
939
|
+
expect(setQueryStringMock).not.toHaveBeenCalled();
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
/*
|
|
944
|
+
* The empty state is where a reader lands when the level has nothing for
|
|
945
|
+
* them. Before issue #3981 that was a dead end; now the answer is usually in
|
|
946
|
+
* the dropdown, and the copy has to say so — but only when the dropdown can
|
|
947
|
+
* actually have one.
|
|
948
|
+
*/
|
|
949
|
+
describe("the empty level points at the dropdown only when it can help", () => {
|
|
950
|
+
test("two or more characters mention matches anywhere in the network", async () => {
|
|
951
|
+
await renderExplorer();
|
|
952
|
+
typeSearch("zz");
|
|
953
|
+
|
|
954
|
+
expect(screen.getByText("No sites match your filters")).toBeVisible();
|
|
955
|
+
const description: HTMLElement = screen.getByTestId(
|
|
956
|
+
"topology-hierarchy-empty-description",
|
|
957
|
+
);
|
|
958
|
+
expect(description).toHaveTextContent(
|
|
959
|
+
"No regions at this level match what you are looking for. Click the search box to see matching sites from anywhere in your network, or clear the search or the health filter to see the rest of this level.",
|
|
960
|
+
);
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
test("a single character keeps the old copy", async () => {
|
|
964
|
+
await renderExplorer();
|
|
965
|
+
typeSearch("z");
|
|
966
|
+
|
|
967
|
+
const description: HTMLElement = screen.getByTestId(
|
|
968
|
+
"topology-hierarchy-empty-description",
|
|
969
|
+
);
|
|
970
|
+
expect(description).toHaveTextContent(
|
|
971
|
+
"No regions at this level match what you are looking for. Clear the search or the health filter to see the rest.",
|
|
972
|
+
);
|
|
973
|
+
expect(description).not.toHaveTextContent("anywhere in your network");
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
test("a single character padded with spaces is still a single character", async () => {
|
|
977
|
+
await renderExplorer();
|
|
978
|
+
typeSearch(" z ");
|
|
979
|
+
|
|
980
|
+
expect(
|
|
981
|
+
screen.getByTestId("topology-hierarchy-empty-description"),
|
|
982
|
+
).not.toHaveTextContent("anywhere in your network");
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
test("a health filter alone never points at the dropdown", async () => {
|
|
986
|
+
await renderExplorer();
|
|
987
|
+
fireEvent.click(screen.getByTestId("topology-hierarchy-filter-degraded"));
|
|
988
|
+
|
|
989
|
+
const description: HTMLElement = screen.getByTestId(
|
|
990
|
+
"topology-hierarchy-empty-description",
|
|
991
|
+
);
|
|
992
|
+
expect(description).toHaveTextContent(
|
|
993
|
+
"Clear the search or the health filter to see the rest.",
|
|
994
|
+
);
|
|
995
|
+
expect(description).not.toHaveTextContent("anywhere in your network");
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
test('"Clear filters" empties the box and brings the level back', async () => {
|
|
999
|
+
await renderExplorer();
|
|
1000
|
+
await searchFor("104822", UNIT_ID);
|
|
1001
|
+
expect(resultCountText()).toBe("0 of 3 regions · Updates every minute");
|
|
1002
|
+
|
|
1003
|
+
fireEvent.click(screen.getByTestId("topology-hierarchy-reset-filters"));
|
|
1004
|
+
|
|
1005
|
+
expect(searchInput()).toHaveValue("");
|
|
1006
|
+
expect(resultCountText()).toBe("3 of 3 regions · Updates every minute");
|
|
1007
|
+
expect(screen.getByTestId(`site-card-${REGION_EAST_ID}`)).toBeVisible();
|
|
1008
|
+
expect(
|
|
1009
|
+
screen.queryByTestId("topology-hierarchy-reset-filters"),
|
|
1010
|
+
).not.toBeInTheDocument();
|
|
1011
|
+
expect(
|
|
1012
|
+
screen.queryByTestId(`${SEARCH_ID}-results`),
|
|
1013
|
+
).not.toBeInTheDocument();
|
|
1014
|
+
expect(screen.queryByTestId(`${SEARCH_ID}-clear`)).not.toBeInTheDocument();
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
test("the box's own clear button does the same for the text", async () => {
|
|
1018
|
+
await renderExplorer();
|
|
1019
|
+
typeSearch("west");
|
|
1020
|
+
expect(resultCountText()).toBe("1 of 3 regions · Updates every minute");
|
|
1021
|
+
|
|
1022
|
+
fireEvent.click(screen.getByTestId(`${SEARCH_ID}-clear`));
|
|
1023
|
+
|
|
1024
|
+
expect(searchInput()).toHaveValue("");
|
|
1025
|
+
expect(resultCountText()).toBe("3 of 3 regions · Updates every minute");
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
/*
|
|
1030
|
+
* A drill resets the health filter: "Down" at one level says nothing about
|
|
1031
|
+
* the next, and arriving inside a market with the filter still on would
|
|
1032
|
+
* hide most of it for a reason two rows up. A drill from the dropdown is a
|
|
1033
|
+
* drill like any other.
|
|
1034
|
+
*/
|
|
1035
|
+
describe("a pick from the dropdown is a drill like any other", () => {
|
|
1036
|
+
test("it resets an active health filter", async () => {
|
|
1037
|
+
await renderExplorer();
|
|
1038
|
+
fireEvent.click(screen.getByTestId("topology-hierarchy-filter-down"));
|
|
1039
|
+
expect(
|
|
1040
|
+
screen.getByTestId("topology-hierarchy-filter-down"),
|
|
1041
|
+
).toHaveAttribute("aria-pressed", "true");
|
|
1042
|
+
expect(resultCountText()).toBe("1 of 3 regions · Updates every minute");
|
|
1043
|
+
|
|
1044
|
+
fireEvent.click(await searchFor("market 5", MARKET_ID));
|
|
1045
|
+
await screen.findByTestId(`site-card-${UNIT_ID}`);
|
|
1046
|
+
|
|
1047
|
+
expect(screen.getByTestId("topology-hierarchy-filter-all")).toHaveAttribute(
|
|
1048
|
+
"aria-pressed",
|
|
1049
|
+
"true",
|
|
1050
|
+
);
|
|
1051
|
+
expect(
|
|
1052
|
+
screen.getByTestId("topology-hierarchy-filter-down"),
|
|
1053
|
+
).toHaveAttribute("aria-pressed", "false");
|
|
1054
|
+
// The healthy unit is on screen: "Down" did not follow the reader in.
|
|
1055
|
+
expect(screen.getByTestId(`site-card-${OTHER_UNIT_ID}`)).toBeVisible();
|
|
1056
|
+
expect(resultCountText()).toBe("2 of 2 units · Updates every minute");
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
/*
|
|
1060
|
+
* The box only exists on the hierarchy view, so a device view cannot be
|
|
1061
|
+
* on screen while a hit is picked — but the URL can still carry the
|
|
1062
|
+
* parameter, and the drill has to clear it or a reload would reopen the
|
|
1063
|
+
* picked site's device map instead of its level.
|
|
1064
|
+
*/
|
|
1065
|
+
test("the URL write names the picked site and clears the device view", async () => {
|
|
1066
|
+
await renderExplorer();
|
|
1067
|
+
fireEvent.click(await searchFor("market 5", MARKET_ID));
|
|
1068
|
+
await screen.findByTestId(`site-card-${UNIT_ID}`);
|
|
1069
|
+
|
|
1070
|
+
await waitFor(() => {
|
|
1071
|
+
expect(setQueryStringMock).toHaveBeenCalledWith({
|
|
1072
|
+
topologySite: MARKET_ID,
|
|
1073
|
+
topologyDevices: null,
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
/*
|
|
1080
|
+
* The keyboard path to the same drill. Enter commits only an entry the
|
|
1081
|
+
* reader moved onto, and only while the panel is open — Escape hides the
|
|
1082
|
+
* panel AND drops the highlight, so a later Enter cannot drill somewhere
|
|
1083
|
+
* that is no longer on screen.
|
|
1084
|
+
*/
|
|
1085
|
+
describe("keyboard selection", () => {
|
|
1086
|
+
test("ArrowDown then Enter drills to the highlighted site", async () => {
|
|
1087
|
+
await renderExplorer();
|
|
1088
|
+
await searchFor("104822", UNIT_ID);
|
|
1089
|
+
const input: HTMLInputElement = searchInput();
|
|
1090
|
+
|
|
1091
|
+
fireEvent.keyDown(input, { key: "ArrowDown" });
|
|
1092
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
1093
|
+
|
|
1094
|
+
const stub: HTMLElement = await screen.findByTestId("live-view-stub");
|
|
1095
|
+
expect(stub).toHaveAttribute("data-site-id", UNIT_ID);
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
test("a bare Enter commits nothing", async () => {
|
|
1099
|
+
await renderExplorer();
|
|
1100
|
+
await searchFor("104822", UNIT_ID);
|
|
1101
|
+
const childrenBefore: number = childrenPosts().length;
|
|
1102
|
+
|
|
1103
|
+
fireEvent.keyDown(searchInput(), { key: "Enter" });
|
|
1104
|
+
|
|
1105
|
+
await waitMs(50);
|
|
1106
|
+
expect(childrenPosts()).toHaveLength(childrenBefore);
|
|
1107
|
+
expect(screen.queryByTestId("live-view-stub")).not.toBeInTheDocument();
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
test("Enter after Escape does not drill to the hidden highlight", async () => {
|
|
1111
|
+
await renderExplorer();
|
|
1112
|
+
await searchFor("104822", UNIT_ID);
|
|
1113
|
+
const input: HTMLInputElement = searchInput();
|
|
1114
|
+
const childrenBefore: number = childrenPosts().length;
|
|
1115
|
+
|
|
1116
|
+
fireEvent.keyDown(input, { key: "ArrowDown" });
|
|
1117
|
+
fireEvent.keyDown(input, { key: "Escape" });
|
|
1118
|
+
|
|
1119
|
+
expect(
|
|
1120
|
+
screen.queryByTestId(`${SEARCH_ID}-results`),
|
|
1121
|
+
).not.toBeInTheDocument();
|
|
1122
|
+
expect(input).toHaveAttribute("aria-expanded", "false");
|
|
1123
|
+
expect(input).not.toHaveAttribute("aria-activedescendant");
|
|
1124
|
+
|
|
1125
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
1126
|
+
|
|
1127
|
+
await waitMs(50);
|
|
1128
|
+
expect(childrenPosts()).toHaveLength(childrenBefore);
|
|
1129
|
+
expect(screen.queryByTestId("live-view-stub")).not.toBeInTheDocument();
|
|
1130
|
+
expect(input).toHaveValue("104822");
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
test("ArrowUp re-opens the panel after Escape, on the last hit", async () => {
|
|
1134
|
+
await renderExplorer();
|
|
1135
|
+
await searchFor("unit", UNIT_ID);
|
|
1136
|
+
const input: HTMLInputElement = searchInput();
|
|
1137
|
+
|
|
1138
|
+
fireEvent.keyDown(input, { key: "Escape" });
|
|
1139
|
+
expect(
|
|
1140
|
+
screen.queryByTestId(`${SEARCH_ID}-results`),
|
|
1141
|
+
).not.toBeInTheDocument();
|
|
1142
|
+
|
|
1143
|
+
fireEvent.keyDown(input, { key: "ArrowUp" });
|
|
1144
|
+
|
|
1145
|
+
expect(screen.getByTestId(`${SEARCH_ID}-results`)).toBeVisible();
|
|
1146
|
+
// Both units match "unit"; the last in the list is the one ArrowUp lands on.
|
|
1147
|
+
expect(input).toHaveAttribute(
|
|
1148
|
+
"aria-activedescendant",
|
|
1149
|
+
`${SEARCH_ID}-option-${OTHER_UNIT_ID}`,
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
1153
|
+
await waitFor(() => {
|
|
1154
|
+
expect(childrenPosts()[childrenPosts().length - 1]!.data).toEqual({
|
|
1155
|
+
siteId: OTHER_UNIT_ID,
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
test("blurring the box closes the panel", async () => {
|
|
1161
|
+
await renderExplorer();
|
|
1162
|
+
await searchFor("104822", UNIT_ID);
|
|
1163
|
+
|
|
1164
|
+
fireEvent.blur(searchInput());
|
|
1165
|
+
|
|
1166
|
+
expect(
|
|
1167
|
+
screen.queryByTestId(`${SEARCH_ID}-results`),
|
|
1168
|
+
).not.toBeInTheDocument();
|
|
1169
|
+
// The text, and the level it narrowed, are untouched.
|
|
1170
|
+
expect(searchInput()).toHaveValue("104822");
|
|
1171
|
+
expect(resultCountText()).toBe("0 of 3 regions · Updates every minute");
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
/*
|
|
1176
|
+
* The dropdown's other states, under the explorer's prefix. A failed or
|
|
1177
|
+
* empty search must say so inside the panel and leave the level alone.
|
|
1178
|
+
*/
|
|
1179
|
+
describe("the dropdown's other answers", () => {
|
|
1180
|
+
test("no matches says so", async () => {
|
|
1181
|
+
await renderExplorer();
|
|
1182
|
+
typeSearch("qqq");
|
|
1183
|
+
|
|
1184
|
+
expect(
|
|
1185
|
+
await screen.findByTestId(`${SEARCH_ID}-no-results`),
|
|
1186
|
+
).toHaveTextContent("No sites match that name.");
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
test("a capped answer asks the reader to keep typing", async () => {
|
|
1190
|
+
await renderExplorer();
|
|
1191
|
+
await searchFor("unit", UNIT_ID);
|
|
1192
|
+
|
|
1193
|
+
expect(screen.getByTestId(`${SEARCH_ID}-truncated`)).toBeVisible();
|
|
1194
|
+
expect(
|
|
1195
|
+
screen.getByTestId(`${SEARCH_ID}-result-${OTHER_UNIT_ID}`),
|
|
1196
|
+
).toBeVisible();
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
test("a root site prints no path line", async () => {
|
|
1200
|
+
await renderExplorer();
|
|
1201
|
+
const hit: HTMLElement = await searchFor("franchise", FRANCHISE_ID);
|
|
1202
|
+
|
|
1203
|
+
expect(hit.textContent).toBe("Franchise AFranchise");
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
test("a failed search stays inside the panel", async () => {
|
|
1207
|
+
await renderExplorer();
|
|
1208
|
+
postMock.mockImplementation((...args: Array<unknown>): unknown => {
|
|
1209
|
+
const options: PostOptions = args[0] as PostOptions;
|
|
1210
|
+
if (routeOf(options).includes("/network-site/search")) {
|
|
1211
|
+
return Promise.reject(new Error("Search is temporarily unavailable"));
|
|
1212
|
+
}
|
|
1213
|
+
return defaultPost(options);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
typeSearch("east");
|
|
1217
|
+
|
|
1218
|
+
expect(await screen.findByTestId(`${SEARCH_ID}-error`)).toHaveTextContent(
|
|
1219
|
+
"Search is temporarily unavailable",
|
|
1220
|
+
);
|
|
1221
|
+
// The level is still narrowed and still usable.
|
|
1222
|
+
expect(screen.getByTestId(`site-card-${REGION_EAST_ID}`)).toBeVisible();
|
|
1223
|
+
expect(resultCountText()).toBe("1 of 3 regions · Updates every minute");
|
|
1224
|
+
expect(
|
|
1225
|
+
screen.queryByTestId("topology-hierarchy-refresh-error"),
|
|
1226
|
+
).not.toBeInTheDocument();
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
/*
|
|
1230
|
+
* Cancel-stale: a slow answer for an earlier query must not replace the
|
|
1231
|
+
* answer for the text in the box now.
|
|
1232
|
+
*/
|
|
1233
|
+
test("a slow answer for an earlier query is dropped", async () => {
|
|
1234
|
+
await renderExplorer();
|
|
1235
|
+
let resolveStale: (value: unknown) => void = (): void => {};
|
|
1236
|
+
postMock.mockImplementation((...args: Array<unknown>): unknown => {
|
|
1237
|
+
const options: PostOptions = args[0] as PostOptions;
|
|
1238
|
+
if (
|
|
1239
|
+
routeOf(options).includes("/network-site/search") &&
|
|
1240
|
+
options.data?.["searchText"] === "region"
|
|
1241
|
+
) {
|
|
1242
|
+
return new Promise<unknown>((resolve: (value: unknown) => void) => {
|
|
1243
|
+
resolveStale = resolve;
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
return defaultPost(options);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
typeSearch("region");
|
|
1250
|
+
await waitFor(() => {
|
|
1251
|
+
expect(searchPosts()).toHaveLength(1);
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
typeSearch("104822");
|
|
1255
|
+
await screen.findByTestId(`${SEARCH_ID}-result-${UNIT_ID}`);
|
|
1256
|
+
|
|
1257
|
+
await act(async () => {
|
|
1258
|
+
resolveStale({ data: searchFixture("region") });
|
|
1259
|
+
await waitMs(0);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
expect(
|
|
1263
|
+
screen.queryByTestId(`${SEARCH_ID}-result-${REGION_EAST_ID}`),
|
|
1264
|
+
).not.toBeInTheDocument();
|
|
1265
|
+
expect(screen.getByTestId(`${SEARCH_ID}-result-${UNIT_ID}`)).toBeVisible();
|
|
1266
|
+
});
|
|
1267
|
+
});
|