@oneuptime/common 12.0.5 → 12.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/AIRun.ts +33 -0
- package/Models/DatabaseModels/DataSource.ts +744 -0
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
- package/Models/DatabaseModels/User.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
- package/Models/DatabaseModels/UserSession.ts +2 -0
- package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
- package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
- package/Server/API/AIInsightAPI.ts +47 -4
- package/Server/API/AIInvestigationAPI.ts +25 -0
- package/Server/API/DashboardAPI.ts +176 -492
- package/Server/API/DataSourceAPI.ts +268 -0
- package/Server/API/StatusPageAPI.ts +11 -1
- package/Server/API/UserAPI.ts +119 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Infrastructure/Queue.ts +26 -0
- package/Server/Infrastructure/QueueWorker.ts +17 -0
- package/Server/Middleware/TelemetryIngest.ts +8 -3
- package/Server/Services/AIInsightService.ts +132 -12
- package/Server/Services/AIRunService.ts +34 -0
- package/Server/Services/AIService.ts +17 -2
- package/Server/Services/AlertService.ts +38 -32
- package/Server/Services/AnalyticsDatabaseService.ts +8 -4
- package/Server/Services/DataSourceService.ts +200 -0
- package/Server/Services/HostService.ts +92 -0
- package/Server/Services/IncidentService.ts +45 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MonitorService.ts +49 -0
- package/Server/Services/MonitorStatusService.ts +166 -0
- package/Server/Services/OpenTelemetryIngestService.ts +255 -184
- package/Server/Services/RumSessionPinService.ts +7 -21
- package/Server/Services/StatusPageSubscriberService.ts +13 -0
- package/Server/Services/UserService.ts +280 -9
- package/Server/Static/Vendor/README.md +62 -0
- package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
- package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
- package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
- package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
- package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
- package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
- package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
- package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
- package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
- package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
- package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
- package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
- package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
- package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
- package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
- package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
- package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
- package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
- package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
- package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
- package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
- package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
- package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
- package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
- package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
- package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
- package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
- package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
- package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +2 -2
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
- package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
- package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
- package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
- package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
- package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
- package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
- package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
- package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
- package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
- package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
- package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
- package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
- package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
- package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
- package/Server/Utils/DataSource/EgressGuard.ts +446 -0
- package/Server/Utils/DataSource/HttpFetch.ts +221 -0
- package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
- package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
- package/Server/Utils/DataSource/Types.ts +93 -0
- package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
- package/Server/Utils/InProcessMemo.ts +141 -0
- package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +58 -0
- package/Server/Utils/Monitor/MonitorResource.ts +14 -0
- package/Server/Utils/Response.ts +12 -1
- package/Server/Utils/SSRFProtection.ts +237 -0
- package/Server/Utils/StartServer.ts +8 -0
- package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
- package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
- package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
- package/Server/Utils/Telemetry/Telemetry.ts +112 -1
- package/Server/Utils/Telemetry.ts +104 -3
- package/Server/Utils/VendorAssets.ts +165 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
- package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
- package/Server/Views/Partials/Head.ejs +21 -7
- package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
- package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
- package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
- package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
- package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
- package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
- package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
- package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
- package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
- package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
- package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
- package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
- package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
- package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
- package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
- package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
- package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
- package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
- package/Tests/Server/Services/AIInsightService.test.ts +266 -11
- package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +4 -0
- package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
- package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
- package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
- package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
- package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
- package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
- package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
- package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
- package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
- package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +2 -2
- package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
- package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
- package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
- package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
- package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
- package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
- package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
- package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
- package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
- package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
- package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
- package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
- package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
- package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
- package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
- package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
- package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
- package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
- package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
- package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
- package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
- package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
- package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
- package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
- package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
- package/Tests/Server/Utils/Telemetry.test.ts +87 -0
- package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
- package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
- package/Tests/Server/Views/OfflineRender.test.ts +297 -0
- package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
- package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
- package/Tests/Types/Password.test.ts +363 -0
- package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
- package/Tests/UI/Components/Modal.test.tsx +281 -1
- package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/Utils/Analytics.test.ts +23 -0
- package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
- package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
- package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
- package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
- package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
- package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
- package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
- package/Types/AI/AIInsightEvidence.ts +6 -0
- package/Types/Analytics/RevenueEvent.ts +31 -0
- package/Types/Dashboard/DashboardComponentType.ts +31 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
- package/Types/DataSource/DataSourceLimits.ts +62 -0
- package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
- package/Types/DataSource/DataSourceTableResult.ts +38 -0
- package/Types/DataSource/DataSourceType.ts +213 -0
- package/Types/Date.ts +43 -3
- package/Types/Password.ts +64 -0
- package/Types/Permission.ts +46 -0
- package/Types/UserAuthenticationStatus.ts +34 -0
- package/UI/Components/Button/Button.tsx +1 -1
- package/UI/Components/ErrorBoundary.tsx +459 -108
- package/UI/Components/Icon/Icon.tsx +10 -9
- package/UI/Components/Modal/ConfirmModal.tsx +6 -1
- package/UI/Components/Modal/Modal.tsx +267 -35
- package/UI/Components/Modal/ModalFooter.tsx +6 -1
- package/UI/Components/MoreMenu/MoreMenu.tsx +19 -4
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
- package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
- package/UI/Components/StatusPage/ResourceList.tsx +570 -0
- package/UI/Styles/Theme.css +18 -0
- package/UI/Utils/API/RequestOptions.ts +7 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +1 -0
- package/UI/Utils/TestDataSource.ts +61 -0
- package/UI/esbuild-config.js +45 -0
- package/Utils/Analytics.ts +15 -0
- package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
- package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
- package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
- package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
- package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
- package/Utils/Dashboard/Components/Index.ts +28 -0
- package/Utils/DataSource/DataSourceQueryText.ts +94 -0
- package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
- package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
- package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
- package/Utils/StatusPage/ResourceExplorer.ts +853 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
- package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +2 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
- package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
- package/build/dist/Server/API/AIInsightAPI.js +34 -4
- package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +119 -433
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/DataSourceAPI.js +173 -0
- package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +11 -1
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +94 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +49 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +27 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIInsightService.js +110 -10
- package/build/dist/Server/Services/AIInsightService.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +34 -0
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +16 -2
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +28 -35
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +8 -4
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DataSourceService.js +159 -0
- package/build/dist/Server/Services/DataSourceService.js.map +1 -0
- package/build/dist/Server/Services/HostService.js +74 -0
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +35 -24
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +45 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusService.js +129 -0
- package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +7 -15
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +241 -10
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +2 -2
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Types.js +2 -0
- package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
- package/build/dist/Server/Utils/InProcessMemo.js +113 -0
- package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +34 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +9 -1
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +192 -0
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +7 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +93 -6
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +121 -0
- package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
- package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceType.js +183 -0
- package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
- package/build/dist/Types/Date.js +38 -3
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Password.js +56 -0
- package/build/dist/Types/Password.js.map +1 -0
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/UserAuthenticationStatus.js +2 -0
- package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
- package/build/dist/UI/Components/ErrorBoundary.js +388 -72
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +10 -4
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +178 -19
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +9 -6
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +3 -8
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/TestDataSource.js +39 -0
- package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
- package/build/dist/Utils/Analytics.js +4 -0
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
- package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
- package/jest.config.json +1 -0
- package/package.json +5 -1
- package/tsconfig.json +12 -10
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
import StatusPageGroup from "../../../../Models/DatabaseModels/StatusPageGroup";
|
|
2
|
+
import StatusPageResource from "../../../../Models/DatabaseModels/StatusPageResource";
|
|
3
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
4
|
+
import StatusPageGroupViewMode from "../../../../Types/StatusPage/StatusPageGroupViewMode";
|
|
5
|
+
import ResourceGroupNavigator from "../../../../UI/Components/StatusPage/ResourceGroupNavigator";
|
|
6
|
+
import StatusPageResourceExplorerUtil, {
|
|
7
|
+
StatusPageResourceCountIndex,
|
|
8
|
+
StatusPageResourceNavigatorRow,
|
|
9
|
+
StatusPageResourceSelection,
|
|
10
|
+
StatusPageResourceSelectionType,
|
|
11
|
+
} from "../../../../Utils/StatusPage/ResourceExplorer";
|
|
12
|
+
import "@testing-library/jest-dom";
|
|
13
|
+
import { fireEvent, render, screen, within } from "@testing-library/react";
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Contract under test - the group hierarchy on Status Page > Resources.
|
|
19
|
+
*
|
|
20
|
+
* This is one control doing two jobs that used to be two pages. It is the
|
|
21
|
+
* navigator: a row selects a group, which is what puts that group's monitors in
|
|
22
|
+
* the pane beside it. It is also the group manager that Status Page > Groups
|
|
23
|
+
* used to be: a row is where a group is given a sub group, renamed, reordered,
|
|
24
|
+
* identified and deleted.
|
|
25
|
+
*
|
|
26
|
+
* So the assertions here are about keeping those two jobs from eating each
|
|
27
|
+
* other:
|
|
28
|
+
*
|
|
29
|
+
* - opening a branch and selecting a group are separate acts, because an
|
|
30
|
+
* operator has to be able to look inside a branch without losing the group
|
|
31
|
+
* they were filling,
|
|
32
|
+
* - the nesting is drawn, with guide rails that end where their level ends,
|
|
33
|
+
* - the counts say what a group holds and what is further down, and say
|
|
34
|
+
* nothing at all when they are not trustworthy,
|
|
35
|
+
* - every action the Groups page offered survives, gated on the same
|
|
36
|
+
* permissions,
|
|
37
|
+
* - and a row whose write is in flight cannot be told to do anything else.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const CORPORATE: ObjectID = new ObjectID(
|
|
41
|
+
"11111111-1111-4111-8111-111111111111",
|
|
42
|
+
);
|
|
43
|
+
const REGION_ONE: ObjectID = new ObjectID(
|
|
44
|
+
"22222222-2222-4222-8222-222222222222",
|
|
45
|
+
);
|
|
46
|
+
const REGION_TWO: ObjectID = new ObjectID(
|
|
47
|
+
"33333333-3333-4333-8333-333333333333",
|
|
48
|
+
);
|
|
49
|
+
const MARKET: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
50
|
+
const UNIT: ObjectID = new ObjectID("55555555-5555-4555-8555-555555555555");
|
|
51
|
+
|
|
52
|
+
function makeGroup(data: {
|
|
53
|
+
id: ObjectID;
|
|
54
|
+
name: string;
|
|
55
|
+
parentId?: ObjectID | undefined;
|
|
56
|
+
order?: number | undefined;
|
|
57
|
+
viewMode?: StatusPageGroupViewMode | undefined;
|
|
58
|
+
}): StatusPageGroup {
|
|
59
|
+
const group: StatusPageGroup = new StatusPageGroup();
|
|
60
|
+
group._id = data.id.toString();
|
|
61
|
+
group.name = data.name;
|
|
62
|
+
|
|
63
|
+
if (data.parentId) {
|
|
64
|
+
group.parentStatusPageGroupId = data.parentId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (data.order !== undefined) {
|
|
68
|
+
group.order = data.order;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (data.viewMode !== undefined) {
|
|
72
|
+
group.viewMode = data.viewMode;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return group;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* Corporate
|
|
80
|
+
* Region 1000
|
|
81
|
+
* Market 1001
|
|
82
|
+
* Unit 0152
|
|
83
|
+
* Region 2000
|
|
84
|
+
*/
|
|
85
|
+
function makeHierarchy(): Array<StatusPageGroup> {
|
|
86
|
+
return [
|
|
87
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
88
|
+
makeGroup({
|
|
89
|
+
id: REGION_ONE,
|
|
90
|
+
name: "Region 1000",
|
|
91
|
+
parentId: CORPORATE,
|
|
92
|
+
order: 2,
|
|
93
|
+
}),
|
|
94
|
+
makeGroup({
|
|
95
|
+
id: MARKET,
|
|
96
|
+
name: "Market 1001",
|
|
97
|
+
parentId: REGION_ONE,
|
|
98
|
+
order: 3,
|
|
99
|
+
}),
|
|
100
|
+
makeGroup({ id: UNIT, name: "Unit 0152", parentId: MARKET, order: 4 }),
|
|
101
|
+
makeGroup({
|
|
102
|
+
id: REGION_TWO,
|
|
103
|
+
name: "Region 2000",
|
|
104
|
+
parentId: CORPORATE,
|
|
105
|
+
order: 5,
|
|
106
|
+
}),
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function makeResource(data: {
|
|
111
|
+
id: string;
|
|
112
|
+
groupId?: ObjectID | undefined;
|
|
113
|
+
}): StatusPageResource {
|
|
114
|
+
const resource: StatusPageResource = new StatusPageResource();
|
|
115
|
+
resource._id = data.id;
|
|
116
|
+
|
|
117
|
+
if (data.groupId) {
|
|
118
|
+
resource.statusPageGroupId = data.groupId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return resource;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function makeCountIndex(
|
|
125
|
+
statusPageResources: Array<StatusPageResource> = [],
|
|
126
|
+
): StatusPageResourceCountIndex {
|
|
127
|
+
return StatusPageResourceExplorerUtil.buildResourceCountIndex({
|
|
128
|
+
statusPageResources: statusPageResources,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function makeRows(data: {
|
|
133
|
+
statusPageGroups?: Array<StatusPageGroup> | undefined;
|
|
134
|
+
countIndex?: StatusPageResourceCountIndex | undefined;
|
|
135
|
+
expandAll?: boolean | undefined;
|
|
136
|
+
searchText?: string | undefined;
|
|
137
|
+
maxRows?: number | undefined;
|
|
138
|
+
}): Array<StatusPageResourceNavigatorRow> {
|
|
139
|
+
const statusPageGroups: Array<StatusPageGroup> =
|
|
140
|
+
data.statusPageGroups || makeHierarchy();
|
|
141
|
+
|
|
142
|
+
return StatusPageResourceExplorerUtil.getNavigatorRows({
|
|
143
|
+
statusPageGroups: statusPageGroups,
|
|
144
|
+
countIndex: data.countIndex || makeCountIndex(),
|
|
145
|
+
expandedGroupIds:
|
|
146
|
+
data.expandAll === false
|
|
147
|
+
? new Set<string>()
|
|
148
|
+
: new Set<string>(
|
|
149
|
+
StatusPageResourceExplorerUtil.getAllGroupIds({
|
|
150
|
+
statusPageGroups: statusPageGroups,
|
|
151
|
+
}),
|
|
152
|
+
),
|
|
153
|
+
searchText: data.searchText,
|
|
154
|
+
maxRows: data.maxRows,
|
|
155
|
+
}).rows;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const UNGROUPED_SELECTION: StatusPageResourceSelection = {
|
|
159
|
+
type: StatusPageResourceSelectionType.Ungrouped,
|
|
160
|
+
statusPageGroupId: null,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
function renderNavigator(
|
|
164
|
+
props: Partial<React.ComponentProps<typeof ResourceGroupNavigator>> = {},
|
|
165
|
+
): ReturnType<typeof render> {
|
|
166
|
+
return render(
|
|
167
|
+
<ResourceGroupNavigator
|
|
168
|
+
rows={props.rows === undefined ? makeRows({}) : props.rows}
|
|
169
|
+
countIndex={props.countIndex || makeCountIndex()}
|
|
170
|
+
selection={props.selection || UNGROUPED_SELECTION}
|
|
171
|
+
searchText={props.searchText === undefined ? "" : props.searchText}
|
|
172
|
+
hiddenRowCount={
|
|
173
|
+
props.hiddenRowCount === undefined ? 0 : props.hiddenRowCount
|
|
174
|
+
}
|
|
175
|
+
onShowMore={props.onShowMore || jest.fn()}
|
|
176
|
+
onSelect={props.onSelect || jest.fn()}
|
|
177
|
+
onToggleExpand={props.onToggleExpand || jest.fn()}
|
|
178
|
+
isCreateable={
|
|
179
|
+
props.isCreateable === undefined ? true : props.isCreateable
|
|
180
|
+
}
|
|
181
|
+
isEditable={props.isEditable === undefined ? true : props.isEditable}
|
|
182
|
+
isDeleteable={
|
|
183
|
+
props.isDeleteable === undefined ? true : props.isDeleteable
|
|
184
|
+
}
|
|
185
|
+
onCreateGroup={props.onCreateGroup}
|
|
186
|
+
onAddSubGroup={props.onAddSubGroup}
|
|
187
|
+
onEditGroup={props.onEditGroup}
|
|
188
|
+
onDeleteGroup={props.onDeleteGroup}
|
|
189
|
+
onMoveGroupUp={props.onMoveGroupUp}
|
|
190
|
+
onMoveGroupDown={props.onMoveGroupDown}
|
|
191
|
+
onShowGroupId={props.onShowGroupId}
|
|
192
|
+
busyGroupId={props.busyGroupId}
|
|
193
|
+
/>,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function rowFor(name: string): HTMLElement {
|
|
198
|
+
const row: HTMLElement | undefined = screen
|
|
199
|
+
.getAllByTestId("status-page-resource-navigator-row")
|
|
200
|
+
.find((candidate: HTMLElement) => {
|
|
201
|
+
return within(candidate)
|
|
202
|
+
.getAllByTestId("status-page-resource-navigator-name")
|
|
203
|
+
.some((element: HTMLElement) => {
|
|
204
|
+
return element.textContent === name;
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (!row) {
|
|
209
|
+
throw new Error(`No row for ${name}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return row;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function rowNames(): Array<string> {
|
|
216
|
+
return screen
|
|
217
|
+
.getAllByTestId("status-page-resource-navigator-name")
|
|
218
|
+
.map((element: HTMLElement) => {
|
|
219
|
+
return element.textContent || "";
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function openMenuFor(name: string): HTMLElement {
|
|
224
|
+
fireEvent.click(
|
|
225
|
+
within(rowFor(name)).getByTestId("status-page-resource-navigator-more"),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return screen.getByRole("menu");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
describe("ResourceGroupNavigator", () => {
|
|
232
|
+
describe("the tree", () => {
|
|
233
|
+
test("draws one row per row it is handed, in the order it is handed them", () => {
|
|
234
|
+
renderNavigator();
|
|
235
|
+
|
|
236
|
+
expect(rowNames()).toEqual([
|
|
237
|
+
"Corporate",
|
|
238
|
+
"Region 1000",
|
|
239
|
+
"Market 1001",
|
|
240
|
+
"Unit 0152",
|
|
241
|
+
"Region 2000",
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("is announced as a tree", () => {
|
|
246
|
+
renderNavigator();
|
|
247
|
+
|
|
248
|
+
expect(screen.getByRole("tree")).toHaveAttribute(
|
|
249
|
+
"aria-label",
|
|
250
|
+
"Status page groups",
|
|
251
|
+
);
|
|
252
|
+
expect(screen.getAllByRole("treeitem").length).toBe(5);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("every row reports its level", () => {
|
|
256
|
+
renderNavigator();
|
|
257
|
+
|
|
258
|
+
expect(rowFor("Corporate")).toHaveAttribute("aria-level", "1");
|
|
259
|
+
expect(rowFor("Region 1000")).toHaveAttribute("aria-level", "2");
|
|
260
|
+
expect(rowFor("Market 1001")).toHaveAttribute("aria-level", "3");
|
|
261
|
+
expect(rowFor("Unit 0152")).toHaveAttribute("aria-level", "4");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("every row reports its place among its siblings", () => {
|
|
265
|
+
renderNavigator();
|
|
266
|
+
|
|
267
|
+
expect(rowFor("Region 1000")).toHaveAttribute("aria-posinset", "1");
|
|
268
|
+
expect(rowFor("Region 1000")).toHaveAttribute("aria-setsize", "2");
|
|
269
|
+
expect(rowFor("Region 2000")).toHaveAttribute("aria-posinset", "2");
|
|
270
|
+
expect(rowFor("Region 2000")).toHaveAttribute("aria-setsize", "2");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
/*
|
|
274
|
+
* A tree tells a screen reader "1 of 2 at level 2". During a search the
|
|
275
|
+
* counts it quotes have to be the ones on screen, or they describe a tree
|
|
276
|
+
* nobody can see.
|
|
277
|
+
*/
|
|
278
|
+
test("a filtered level reports the counts that survived the filter", () => {
|
|
279
|
+
renderNavigator({ rows: makeRows({ searchText: "Region 1000" }) });
|
|
280
|
+
|
|
281
|
+
expect(rowFor("Region 1000")).toHaveAttribute("aria-setsize", "1");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("each row carries its depth and id for anything reading the DOM", () => {
|
|
285
|
+
renderNavigator();
|
|
286
|
+
|
|
287
|
+
expect(rowFor("Market 1001")).toHaveAttribute("data-depth", "2");
|
|
288
|
+
expect(rowFor("Market 1001")).toHaveAttribute(
|
|
289
|
+
"data-group-id",
|
|
290
|
+
MARKET.toString(),
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("a group with no name is still a row you can act on", () => {
|
|
295
|
+
const unnamed: StatusPageGroup = makeGroup({
|
|
296
|
+
id: CORPORATE,
|
|
297
|
+
name: "",
|
|
298
|
+
order: 1,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
renderNavigator({ rows: makeRows({ statusPageGroups: [unnamed] }) });
|
|
302
|
+
|
|
303
|
+
expect(rowNames()).toEqual(["Untitled group"]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("says so when a search matched nothing at all", () => {
|
|
307
|
+
renderNavigator({ rows: [], searchText: "nothing here" });
|
|
308
|
+
|
|
309
|
+
expect(
|
|
310
|
+
screen.getByTestId("status-page-resource-navigator-empty").textContent,
|
|
311
|
+
).toContain("nothing here");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
/*
|
|
315
|
+
* An empty tree with no search running is a status page nobody has grouped
|
|
316
|
+
* yet, which is a different message from a search that found nothing.
|
|
317
|
+
*/
|
|
318
|
+
test("an ungrouped status page is told what groups are for", () => {
|
|
319
|
+
renderNavigator({ rows: [], searchText: "" });
|
|
320
|
+
|
|
321
|
+
expect(
|
|
322
|
+
screen.getByTestId("status-page-resource-navigator-empty").textContent,
|
|
323
|
+
).toContain("No groups yet");
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("selecting versus opening", () => {
|
|
328
|
+
/*
|
|
329
|
+
* The whole reason the chevron is a separate control. An operator filling
|
|
330
|
+
* "Region 1000" has to be able to look inside "Corporate" without the pane
|
|
331
|
+
* beside them jumping to it.
|
|
332
|
+
*/
|
|
333
|
+
test("the chevron opens a branch and selects nothing", () => {
|
|
334
|
+
const onToggleExpand: (statusPageGroupId: string) => void = jest.fn();
|
|
335
|
+
const onSelect: (selection: StatusPageResourceSelection) => void =
|
|
336
|
+
jest.fn();
|
|
337
|
+
|
|
338
|
+
renderNavigator({
|
|
339
|
+
onToggleExpand: onToggleExpand,
|
|
340
|
+
onSelect: onSelect,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
fireEvent.click(
|
|
344
|
+
within(rowFor("Corporate")).getByTestId(
|
|
345
|
+
"status-page-resource-navigator-disclosure",
|
|
346
|
+
),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect(onToggleExpand).toHaveBeenCalledWith(CORPORATE.toString());
|
|
350
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("the row selects the group and opens nothing", () => {
|
|
354
|
+
const onToggleExpand: (statusPageGroupId: string) => void = jest.fn();
|
|
355
|
+
const onSelect: (selection: StatusPageResourceSelection) => void =
|
|
356
|
+
jest.fn();
|
|
357
|
+
|
|
358
|
+
renderNavigator({
|
|
359
|
+
onToggleExpand: onToggleExpand,
|
|
360
|
+
onSelect: onSelect,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
fireEvent.click(
|
|
364
|
+
within(rowFor("Corporate")).getByTestId(
|
|
365
|
+
"status-page-resource-navigator-select",
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(onSelect).toHaveBeenCalledWith({
|
|
370
|
+
type: StatusPageResourceSelectionType.Group,
|
|
371
|
+
statusPageGroupId: CORPORATE.toString(),
|
|
372
|
+
});
|
|
373
|
+
expect(onToggleExpand).not.toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("the selected group is the one marked as selected", () => {
|
|
377
|
+
renderNavigator({
|
|
378
|
+
selection: {
|
|
379
|
+
type: StatusPageResourceSelectionType.Group,
|
|
380
|
+
statusPageGroupId: MARKET.toString(),
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
expect(rowFor("Market 1001")).toHaveAttribute("aria-selected", "true");
|
|
385
|
+
expect(rowFor("Corporate")).toHaveAttribute("aria-selected", "false");
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
/*
|
|
389
|
+
* The resources that belong to no group are a real place on a status page -
|
|
390
|
+
* they are drawn above every group - so they are selectable like anything
|
|
391
|
+
* else rather than being a group with a null id.
|
|
392
|
+
*/
|
|
393
|
+
test("the ungrouped bucket is selectable and reports being current", () => {
|
|
394
|
+
const onSelect: (selection: StatusPageResourceSelection) => void =
|
|
395
|
+
jest.fn();
|
|
396
|
+
|
|
397
|
+
renderNavigator({ onSelect: onSelect });
|
|
398
|
+
|
|
399
|
+
const ungrouped: HTMLElement = screen.getByTestId(
|
|
400
|
+
"status-page-resource-navigator-ungrouped",
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
expect(ungrouped).toHaveAttribute("aria-current", "true");
|
|
404
|
+
|
|
405
|
+
fireEvent.click(ungrouped);
|
|
406
|
+
|
|
407
|
+
expect(onSelect).toHaveBeenCalledWith(UNGROUPED_SELECTION);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("a leaf keeps the chevron's column so every name starts on one line", () => {
|
|
411
|
+
renderNavigator();
|
|
412
|
+
|
|
413
|
+
expect(
|
|
414
|
+
within(rowFor("Unit 0152")).getByTestId(
|
|
415
|
+
"status-page-resource-navigator-leaf-spacer",
|
|
416
|
+
),
|
|
417
|
+
).toBeInTheDocument();
|
|
418
|
+
expect(
|
|
419
|
+
within(rowFor("Unit 0152")).queryByTestId(
|
|
420
|
+
"status-page-resource-navigator-disclosure",
|
|
421
|
+
),
|
|
422
|
+
).not.toBeInTheDocument();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("a collapsed row says so, and offers to expand", () => {
|
|
426
|
+
renderNavigator({ rows: makeRows({ expandAll: false }) });
|
|
427
|
+
|
|
428
|
+
expect(rowFor("Corporate")).toHaveAttribute("aria-expanded", "false");
|
|
429
|
+
expect(
|
|
430
|
+
within(rowFor("Corporate")).getByTestId(
|
|
431
|
+
"status-page-resource-navigator-disclosure",
|
|
432
|
+
),
|
|
433
|
+
).toHaveAttribute("aria-label", "Expand Corporate");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("an expanded row offers to collapse", () => {
|
|
437
|
+
renderNavigator();
|
|
438
|
+
|
|
439
|
+
expect(rowFor("Corporate")).toHaveAttribute("aria-expanded", "true");
|
|
440
|
+
expect(
|
|
441
|
+
within(rowFor("Corporate")).getByTestId(
|
|
442
|
+
"status-page-resource-navigator-disclosure",
|
|
443
|
+
),
|
|
444
|
+
).toHaveAttribute("aria-label", "Collapse Corporate");
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
/*
|
|
449
|
+
* The arrow keys everybody already knows from a file tree. Without them the
|
|
450
|
+
* only way through a large hierarchy from the keyboard is Tab, which stops on
|
|
451
|
+
* every chevron and every row action on the way past and cannot skip a
|
|
452
|
+
* subtree at all.
|
|
453
|
+
*/
|
|
454
|
+
describe("walking the tree from the keyboard", () => {
|
|
455
|
+
type SelectButtonFunction = (name: string) => HTMLElement;
|
|
456
|
+
|
|
457
|
+
const selectButtonFor: SelectButtonFunction = (
|
|
458
|
+
name: string,
|
|
459
|
+
): HTMLElement => {
|
|
460
|
+
return within(rowFor(name)).getByTestId(
|
|
461
|
+
"status-page-resource-navigator-select",
|
|
462
|
+
);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
type PressFunction = (name: string, key: string) => void;
|
|
466
|
+
|
|
467
|
+
const press: PressFunction = (name: string, key: string): void => {
|
|
468
|
+
const button: HTMLElement = selectButtonFor(name);
|
|
469
|
+
|
|
470
|
+
button.focus();
|
|
471
|
+
fireEvent.keyDown(button, { key: key });
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
test("down and up walk the rows that are on screen", () => {
|
|
475
|
+
renderNavigator();
|
|
476
|
+
|
|
477
|
+
press("Corporate", "ArrowDown");
|
|
478
|
+
expect(document.activeElement).toBe(selectButtonFor("Region 1000"));
|
|
479
|
+
|
|
480
|
+
fireEvent.keyDown(document.activeElement as HTMLElement, {
|
|
481
|
+
key: "ArrowDown",
|
|
482
|
+
});
|
|
483
|
+
expect(document.activeElement).toBe(selectButtonFor("Market 1001"));
|
|
484
|
+
|
|
485
|
+
fireEvent.keyDown(document.activeElement as HTMLElement, {
|
|
486
|
+
key: "ArrowUp",
|
|
487
|
+
});
|
|
488
|
+
expect(document.activeElement).toBe(selectButtonFor("Region 1000"));
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("home and end jump to the ends", () => {
|
|
492
|
+
renderNavigator();
|
|
493
|
+
|
|
494
|
+
press("Market 1001", "End");
|
|
495
|
+
expect(document.activeElement).toBe(selectButtonFor("Region 2000"));
|
|
496
|
+
|
|
497
|
+
fireEvent.keyDown(document.activeElement as HTMLElement, { key: "Home" });
|
|
498
|
+
expect(document.activeElement).toBe(selectButtonFor("Corporate"));
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("the ends do not wrap around", () => {
|
|
502
|
+
renderNavigator();
|
|
503
|
+
|
|
504
|
+
press("Corporate", "ArrowUp");
|
|
505
|
+
expect(document.activeElement).toBe(selectButtonFor("Corporate"));
|
|
506
|
+
|
|
507
|
+
press("Region 2000", "ArrowDown");
|
|
508
|
+
expect(document.activeElement).toBe(selectButtonFor("Region 2000"));
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test("right opens a closed branch, and steps into an open one", () => {
|
|
512
|
+
const onToggleExpand: (statusPageGroupId: string) => void = jest.fn();
|
|
513
|
+
|
|
514
|
+
renderNavigator({
|
|
515
|
+
rows: makeRows({ expandAll: false }),
|
|
516
|
+
onToggleExpand: onToggleExpand,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
press("Corporate", "ArrowRight");
|
|
520
|
+
|
|
521
|
+
expect(onToggleExpand).toHaveBeenCalledWith(CORPORATE.toString());
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("right on an already open branch moves to its first child", () => {
|
|
525
|
+
const onToggleExpand: (statusPageGroupId: string) => void = jest.fn();
|
|
526
|
+
|
|
527
|
+
renderNavigator({ onToggleExpand: onToggleExpand });
|
|
528
|
+
|
|
529
|
+
press("Corporate", "ArrowRight");
|
|
530
|
+
|
|
531
|
+
expect(onToggleExpand).not.toHaveBeenCalled();
|
|
532
|
+
expect(document.activeElement).toBe(selectButtonFor("Region 1000"));
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("left closes the branch you are standing in", () => {
|
|
536
|
+
const onToggleExpand: (statusPageGroupId: string) => void = jest.fn();
|
|
537
|
+
|
|
538
|
+
renderNavigator({ onToggleExpand: onToggleExpand });
|
|
539
|
+
|
|
540
|
+
press("Region 1000", "ArrowLeft");
|
|
541
|
+
|
|
542
|
+
expect(onToggleExpand).toHaveBeenCalledWith(REGION_ONE.toString());
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
/*
|
|
546
|
+
* Skipping a subtree is the thing Tab cannot do, and the only reason a
|
|
547
|
+
* fifteen hundred group hierarchy is navigable from a keyboard at all.
|
|
548
|
+
*/
|
|
549
|
+
test("left on a leaf climbs out to the group that contains it", () => {
|
|
550
|
+
const onToggleExpand: (statusPageGroupId: string) => void = jest.fn();
|
|
551
|
+
|
|
552
|
+
renderNavigator({ onToggleExpand: onToggleExpand });
|
|
553
|
+
|
|
554
|
+
press("Unit 0152", "ArrowLeft");
|
|
555
|
+
|
|
556
|
+
expect(onToggleExpand).not.toHaveBeenCalled();
|
|
557
|
+
expect(document.activeElement).toBe(selectButtonFor("Market 1001"));
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("keys that are not navigation are left alone", () => {
|
|
561
|
+
const onToggleExpand: (statusPageGroupId: string) => void = jest.fn();
|
|
562
|
+
const onSelect: (selection: StatusPageResourceSelection) => void =
|
|
563
|
+
jest.fn();
|
|
564
|
+
|
|
565
|
+
renderNavigator({
|
|
566
|
+
onToggleExpand: onToggleExpand,
|
|
567
|
+
onSelect: onSelect,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
press("Corporate", "a");
|
|
571
|
+
|
|
572
|
+
expect(document.activeElement).toBe(selectButtonFor("Corporate"));
|
|
573
|
+
expect(onToggleExpand).not.toHaveBeenCalled();
|
|
574
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
describe("creating a group from the section header", () => {
|
|
579
|
+
test("is offered beside the list it will join", () => {
|
|
580
|
+
const onCreateGroup: () => void = jest.fn();
|
|
581
|
+
|
|
582
|
+
renderNavigator({ onCreateGroup: onCreateGroup });
|
|
583
|
+
|
|
584
|
+
fireEvent.click(
|
|
585
|
+
screen.getByTestId("status-page-resource-navigator-create-group"),
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
expect(onCreateGroup).toHaveBeenCalled();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("is not offered to someone who cannot create", () => {
|
|
592
|
+
renderNavigator({ isCreateable: false, onCreateGroup: jest.fn() });
|
|
593
|
+
|
|
594
|
+
expect(
|
|
595
|
+
screen.queryByTestId("status-page-resource-navigator-create-group"),
|
|
596
|
+
).not.toBeInTheDocument();
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
describe("the guide rails", () => {
|
|
601
|
+
test("a top level row has no indent columns", () => {
|
|
602
|
+
renderNavigator();
|
|
603
|
+
|
|
604
|
+
expect(
|
|
605
|
+
within(rowFor("Corporate")).queryAllByTestId(
|
|
606
|
+
"status-page-resource-navigator-rail",
|
|
607
|
+
).length,
|
|
608
|
+
).toBe(0);
|
|
609
|
+
expect(
|
|
610
|
+
within(rowFor("Corporate")).queryByTestId(
|
|
611
|
+
"status-page-resource-navigator-connector",
|
|
612
|
+
),
|
|
613
|
+
).not.toBeInTheDocument();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("a nested row hangs off a connector", () => {
|
|
617
|
+
renderNavigator();
|
|
618
|
+
|
|
619
|
+
expect(
|
|
620
|
+
within(rowFor("Region 1000")).getByTestId(
|
|
621
|
+
"status-page-resource-navigator-connector",
|
|
622
|
+
),
|
|
623
|
+
).toBeInTheDocument();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("one ancestor rail is drawn per level between the row and the left edge", () => {
|
|
627
|
+
renderNavigator();
|
|
628
|
+
|
|
629
|
+
expect(
|
|
630
|
+
within(rowFor("Region 1000")).queryAllByTestId(
|
|
631
|
+
"status-page-resource-navigator-rail",
|
|
632
|
+
).length,
|
|
633
|
+
).toBe(0);
|
|
634
|
+
expect(
|
|
635
|
+
within(rowFor("Market 1001")).queryAllByTestId(
|
|
636
|
+
"status-page-resource-navigator-rail",
|
|
637
|
+
).length,
|
|
638
|
+
).toBe(1);
|
|
639
|
+
expect(
|
|
640
|
+
within(rowFor("Unit 0152")).queryAllByTestId(
|
|
641
|
+
"status-page-resource-navigator-rail",
|
|
642
|
+
).length,
|
|
643
|
+
).toBe(2);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
/*
|
|
647
|
+
* "Region 1000" still has "Region 2000" below it, so the rail running past
|
|
648
|
+
* its descendants continues; nothing follows "Region 2000", so it does not.
|
|
649
|
+
*/
|
|
650
|
+
test("a rail continues only while its level still has rows below", () => {
|
|
651
|
+
renderNavigator();
|
|
652
|
+
|
|
653
|
+
type RailsFunction = (name: string) => Array<string | null>;
|
|
654
|
+
|
|
655
|
+
const railsFor: RailsFunction = (name: string): Array<string | null> => {
|
|
656
|
+
return within(rowFor(name))
|
|
657
|
+
.getAllByTestId("status-page-resource-navigator-rail")
|
|
658
|
+
.map((rail: HTMLElement) => {
|
|
659
|
+
return rail.getAttribute("data-rail-continues");
|
|
660
|
+
});
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/*
|
|
664
|
+
* "Region 1000" still has "Region 2000" below it, so its rail runs past
|
|
665
|
+
* everything nested under it; "Market 1001" is the last of its level, so
|
|
666
|
+
* the rail standing for it has already ended by the time "Unit 0152" is
|
|
667
|
+
* drawn.
|
|
668
|
+
*/
|
|
669
|
+
expect(railsFor("Market 1001")).toEqual(["true"]);
|
|
670
|
+
expect(railsFor("Unit 0152")).toEqual(["true", "false"]);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("the last row of a level ends its connector in an elbow", () => {
|
|
674
|
+
renderNavigator();
|
|
675
|
+
|
|
676
|
+
expect(
|
|
677
|
+
within(rowFor("Region 2000"))
|
|
678
|
+
.getByTestId("status-page-resource-navigator-connector")
|
|
679
|
+
.getAttribute("data-is-last-sibling"),
|
|
680
|
+
).toBe("true");
|
|
681
|
+
expect(
|
|
682
|
+
within(rowFor("Region 1000"))
|
|
683
|
+
.getByTestId("status-page-resource-navigator-connector")
|
|
684
|
+
.getAttribute("data-is-last-sibling"),
|
|
685
|
+
).toBe("false");
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("rails are decoration and are hidden from screen readers", () => {
|
|
689
|
+
renderNavigator();
|
|
690
|
+
|
|
691
|
+
for (const rail of screen.getAllByTestId(
|
|
692
|
+
"status-page-resource-navigator-rail",
|
|
693
|
+
)) {
|
|
694
|
+
expect(rail).toHaveAttribute("aria-hidden", "true");
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
describe("the counts", () => {
|
|
700
|
+
/*
|
|
701
|
+
* A group holding nothing itself but with resources nested under it is not
|
|
702
|
+
* empty, and a bare "0" beside it would send an operator hunting through
|
|
703
|
+
* the hierarchy for monitors that are right there.
|
|
704
|
+
*/
|
|
705
|
+
test("show what a group holds, and what is below it when it holds none", () => {
|
|
706
|
+
const countIndex: StatusPageResourceCountIndex = makeCountIndex([
|
|
707
|
+
makeResource({ id: "a", groupId: MARKET }),
|
|
708
|
+
makeResource({ id: "b", groupId: MARKET }),
|
|
709
|
+
makeResource({ id: "c", groupId: REGION_TWO }),
|
|
710
|
+
]);
|
|
711
|
+
|
|
712
|
+
renderNavigator({
|
|
713
|
+
countIndex: countIndex,
|
|
714
|
+
rows: makeRows({ countIndex: countIndex }),
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
expect(
|
|
718
|
+
within(rowFor("Market 1001")).getByTestId(
|
|
719
|
+
"status-page-resource-navigator-count",
|
|
720
|
+
).textContent,
|
|
721
|
+
).toBe("2");
|
|
722
|
+
expect(
|
|
723
|
+
within(rowFor("Corporate")).getByTestId(
|
|
724
|
+
"status-page-resource-navigator-count",
|
|
725
|
+
).textContent,
|
|
726
|
+
).toBe("+3");
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
/*
|
|
730
|
+
* An operator deciding which group still needs monitors is going to trust
|
|
731
|
+
* these numbers, so a count built from a truncated read says nothing.
|
|
732
|
+
*/
|
|
733
|
+
test("say nothing at all when the count pass came back truncated", () => {
|
|
734
|
+
const countIndex: StatusPageResourceCountIndex =
|
|
735
|
+
StatusPageResourceExplorerUtil.buildResourceCountIndex({
|
|
736
|
+
statusPageResources: [makeResource({ id: "a", groupId: MARKET })],
|
|
737
|
+
totalCount: 900,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
renderNavigator({
|
|
741
|
+
countIndex: countIndex,
|
|
742
|
+
rows: makeRows({ countIndex: countIndex }),
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
expect(
|
|
746
|
+
screen.queryAllByTestId("status-page-resource-navigator-count").length,
|
|
747
|
+
).toBe(0);
|
|
748
|
+
expect(
|
|
749
|
+
screen.queryByTestId("status-page-resource-navigator-ungrouped-count"),
|
|
750
|
+
).not.toBeInTheDocument();
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("the ungrouped bucket carries its own count", () => {
|
|
754
|
+
const countIndex: StatusPageResourceCountIndex = makeCountIndex([
|
|
755
|
+
makeResource({ id: "a" }),
|
|
756
|
+
makeResource({ id: "b" }),
|
|
757
|
+
makeResource({ id: "c", groupId: MARKET }),
|
|
758
|
+
]);
|
|
759
|
+
|
|
760
|
+
renderNavigator({
|
|
761
|
+
countIndex: countIndex,
|
|
762
|
+
rows: makeRows({ countIndex: countIndex }),
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
expect(
|
|
766
|
+
screen.getByTestId("status-page-resource-navigator-ungrouped-count")
|
|
767
|
+
.textContent,
|
|
768
|
+
).toBe("2");
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
describe("rows the cap is holding back", () => {
|
|
773
|
+
test("are offered a page at a time", () => {
|
|
774
|
+
const onShowMore: () => void = jest.fn();
|
|
775
|
+
|
|
776
|
+
renderNavigator({ hiddenRowCount: 12, onShowMore: onShowMore });
|
|
777
|
+
|
|
778
|
+
const showMore: HTMLElement = screen.getByTestId(
|
|
779
|
+
"status-page-resource-navigator-show-more",
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
expect(showMore.textContent).toContain("12");
|
|
783
|
+
|
|
784
|
+
fireEvent.click(within(showMore).getByRole("button"));
|
|
785
|
+
|
|
786
|
+
expect(onShowMore).toHaveBeenCalled();
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test("are not mentioned when there are none", () => {
|
|
790
|
+
renderNavigator({ hiddenRowCount: 0 });
|
|
791
|
+
|
|
792
|
+
expect(
|
|
793
|
+
screen.queryByTestId("status-page-resource-navigator-show-more"),
|
|
794
|
+
).not.toBeInTheDocument();
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
describe("group actions", () => {
|
|
799
|
+
test("a group can be given a sub group from its own row", () => {
|
|
800
|
+
const onAddSubGroup: (statusPageGroup: StatusPageGroup) => void =
|
|
801
|
+
jest.fn();
|
|
802
|
+
|
|
803
|
+
renderNavigator({ onAddSubGroup: onAddSubGroup });
|
|
804
|
+
|
|
805
|
+
const addButton: HTMLElement = within(rowFor("Region 1000")).getByTestId(
|
|
806
|
+
"status-page-resource-navigator-add-sub-group",
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
expect(addButton).toHaveAttribute(
|
|
810
|
+
"aria-label",
|
|
811
|
+
"Add a sub group inside Region 1000",
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
fireEvent.click(addButton);
|
|
815
|
+
|
|
816
|
+
expect(
|
|
817
|
+
(
|
|
818
|
+
(onAddSubGroup as jest.Mock).mock.calls[0]![0] as StatusPageGroup
|
|
819
|
+
)._id?.toString(),
|
|
820
|
+
).toBe(REGION_ONE.toString());
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test("editing, reordering, identifying and deleting live behind the row's overflow menu", () => {
|
|
824
|
+
renderNavigator({
|
|
825
|
+
onEditGroup: jest.fn(),
|
|
826
|
+
onMoveGroupUp: jest.fn(),
|
|
827
|
+
onMoveGroupDown: jest.fn(),
|
|
828
|
+
onShowGroupId: jest.fn(),
|
|
829
|
+
onDeleteGroup: jest.fn(),
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
const menu: HTMLElement = openMenuFor("Region 1000");
|
|
833
|
+
const labels: Array<string> = within(menu)
|
|
834
|
+
.getAllByRole("menuitem")
|
|
835
|
+
.map((item: HTMLElement) => {
|
|
836
|
+
return item.textContent || "";
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
expect(labels).toEqual([
|
|
840
|
+
"Edit group",
|
|
841
|
+
"Move up",
|
|
842
|
+
"Move down",
|
|
843
|
+
"Show ID",
|
|
844
|
+
"Delete group",
|
|
845
|
+
]);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("editing reports the group that was chosen", () => {
|
|
849
|
+
const onEditGroup: (statusPageGroup: StatusPageGroup) => void = jest.fn();
|
|
850
|
+
|
|
851
|
+
renderNavigator({ onEditGroup: onEditGroup });
|
|
852
|
+
|
|
853
|
+
fireEvent.click(
|
|
854
|
+
within(openMenuFor("Market 1001")).getByText("Edit group"),
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
expect(
|
|
858
|
+
(
|
|
859
|
+
(onEditGroup as jest.Mock).mock.calls[0]![0] as StatusPageGroup
|
|
860
|
+
)._id?.toString(),
|
|
861
|
+
).toBe(MARKET.toString());
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("deleting reports the group that was chosen", () => {
|
|
865
|
+
const onDeleteGroup: (statusPageGroup: StatusPageGroup) => void =
|
|
866
|
+
jest.fn();
|
|
867
|
+
|
|
868
|
+
renderNavigator({ onDeleteGroup: onDeleteGroup });
|
|
869
|
+
|
|
870
|
+
fireEvent.click(
|
|
871
|
+
within(openMenuFor("Market 1001")).getByText("Delete group"),
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
expect(
|
|
875
|
+
(
|
|
876
|
+
(onDeleteGroup as jest.Mock).mock.calls[0]![0] as StatusPageGroup
|
|
877
|
+
)._id?.toString(),
|
|
878
|
+
).toBe(MARKET.toString());
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
test("moving reports the group and the direction", () => {
|
|
882
|
+
const onMoveGroupUp: (statusPageGroup: StatusPageGroup) => void =
|
|
883
|
+
jest.fn();
|
|
884
|
+
const onMoveGroupDown: (statusPageGroup: StatusPageGroup) => void =
|
|
885
|
+
jest.fn();
|
|
886
|
+
|
|
887
|
+
renderNavigator({
|
|
888
|
+
onMoveGroupUp: onMoveGroupUp,
|
|
889
|
+
onMoveGroupDown: onMoveGroupDown,
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
fireEvent.click(within(openMenuFor("Region 2000")).getByText("Move up"));
|
|
893
|
+
|
|
894
|
+
expect(
|
|
895
|
+
(
|
|
896
|
+
(onMoveGroupUp as jest.Mock).mock.calls[0]![0] as StatusPageGroup
|
|
897
|
+
)._id?.toString(),
|
|
898
|
+
).toBe(REGION_TWO.toString());
|
|
899
|
+
|
|
900
|
+
fireEvent.click(
|
|
901
|
+
within(openMenuFor("Region 1000")).getByText("Move down"),
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
expect(
|
|
905
|
+
(
|
|
906
|
+
(onMoveGroupDown as jest.Mock).mock.calls[0]![0] as StatusPageGroup
|
|
907
|
+
)._id?.toString(),
|
|
908
|
+
).toBe(REGION_ONE.toString());
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
/*
|
|
912
|
+
* Offered but disabled rather than absent: a menu whose contents change
|
|
913
|
+
* shape from row to row is a menu you have to read every time.
|
|
914
|
+
*/
|
|
915
|
+
test("a move with no sibling that way is offered but disabled", () => {
|
|
916
|
+
renderNavigator({
|
|
917
|
+
onMoveGroupUp: jest.fn(),
|
|
918
|
+
onMoveGroupDown: jest.fn(),
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
const menu: HTMLElement = openMenuFor("Region 1000");
|
|
922
|
+
|
|
923
|
+
expect(
|
|
924
|
+
within(menu).getByText("Move up").closest("button"),
|
|
925
|
+
).toBeDisabled();
|
|
926
|
+
expect(
|
|
927
|
+
within(menu).getByText("Move down").closest("button"),
|
|
928
|
+
).not.toBeDisabled();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
test("showing the id reports the group that was chosen", () => {
|
|
932
|
+
const onShowGroupId: (statusPageGroup: StatusPageGroup) => void =
|
|
933
|
+
jest.fn();
|
|
934
|
+
|
|
935
|
+
renderNavigator({ onShowGroupId: onShowGroupId });
|
|
936
|
+
|
|
937
|
+
fireEvent.click(within(openMenuFor("Unit 0152")).getByText("Show ID"));
|
|
938
|
+
|
|
939
|
+
expect(
|
|
940
|
+
(
|
|
941
|
+
(onShowGroupId as jest.Mock).mock.calls[0]![0] as StatusPageGroup
|
|
942
|
+
)._id?.toString(),
|
|
943
|
+
).toBe(UNIT.toString());
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
describe("permissions", () => {
|
|
948
|
+
test("a viewer who cannot create sees no add button", () => {
|
|
949
|
+
renderNavigator({
|
|
950
|
+
isCreateable: false,
|
|
951
|
+
onAddSubGroup: jest.fn(),
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
expect(
|
|
955
|
+
screen.queryAllByTestId("status-page-resource-navigator-add-sub-group")
|
|
956
|
+
.length,
|
|
957
|
+
).toBe(0);
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
test("a viewer who cannot edit is offered neither the edit nor the moves", () => {
|
|
961
|
+
renderNavigator({
|
|
962
|
+
isEditable: false,
|
|
963
|
+
onEditGroup: jest.fn(),
|
|
964
|
+
onMoveGroupUp: jest.fn(),
|
|
965
|
+
onMoveGroupDown: jest.fn(),
|
|
966
|
+
onShowGroupId: jest.fn(),
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
const labels: Array<string> = within(openMenuFor("Region 1000"))
|
|
970
|
+
.getAllByRole("menuitem")
|
|
971
|
+
.map((item: HTMLElement) => {
|
|
972
|
+
return item.textContent || "";
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
expect(labels).toEqual(["Show ID"]);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
test("a viewer who cannot delete is not offered delete", () => {
|
|
979
|
+
renderNavigator({
|
|
980
|
+
isDeleteable: false,
|
|
981
|
+
onShowGroupId: jest.fn(),
|
|
982
|
+
onDeleteGroup: jest.fn(),
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
expect(
|
|
986
|
+
within(openMenuFor("Region 1000")).queryByText("Delete group"),
|
|
987
|
+
).not.toBeInTheDocument();
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
test("a row with no actions at all draws no cluster", () => {
|
|
991
|
+
renderNavigator({
|
|
992
|
+
isCreateable: false,
|
|
993
|
+
isEditable: false,
|
|
994
|
+
isDeleteable: false,
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
expect(
|
|
998
|
+
screen.queryAllByTestId("status-page-resource-navigator-actions")
|
|
999
|
+
.length,
|
|
1000
|
+
).toBe(0);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
/*
|
|
1004
|
+
* The navigator is how a read only viewer gets around the status page, so
|
|
1005
|
+
* losing the actions must not lose the hierarchy or the ability to select.
|
|
1006
|
+
*/
|
|
1007
|
+
test("a read only viewer still sees the whole hierarchy and can select", () => {
|
|
1008
|
+
const onSelect: (selection: StatusPageResourceSelection) => void =
|
|
1009
|
+
jest.fn();
|
|
1010
|
+
|
|
1011
|
+
renderNavigator({
|
|
1012
|
+
isCreateable: false,
|
|
1013
|
+
isEditable: false,
|
|
1014
|
+
isDeleteable: false,
|
|
1015
|
+
onSelect: onSelect,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
expect(rowNames().length).toBe(5);
|
|
1019
|
+
|
|
1020
|
+
fireEvent.click(
|
|
1021
|
+
within(rowFor("Market 1001")).getByTestId(
|
|
1022
|
+
"status-page-resource-navigator-select",
|
|
1023
|
+
),
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
expect(onSelect).toHaveBeenCalled();
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
describe("a row whose write is in flight", () => {
|
|
1031
|
+
const busyProps: Partial<
|
|
1032
|
+
React.ComponentProps<typeof ResourceGroupNavigator>
|
|
1033
|
+
> = {
|
|
1034
|
+
busyGroupId: REGION_ONE.toString(),
|
|
1035
|
+
onAddSubGroup: jest.fn(),
|
|
1036
|
+
onEditGroup: jest.fn(),
|
|
1037
|
+
onMoveGroupUp: jest.fn(),
|
|
1038
|
+
onMoveGroupDown: jest.fn(),
|
|
1039
|
+
onDeleteGroup: jest.fn(),
|
|
1040
|
+
onShowGroupId: jest.fn(),
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
/*
|
|
1044
|
+
* The server renumbers a group's siblings on every reorder, so two writes
|
|
1045
|
+
* in flight at once resolve against a hierarchy neither of them saw.
|
|
1046
|
+
*/
|
|
1047
|
+
test("says so, and refuses further actions", () => {
|
|
1048
|
+
renderNavigator(busyProps);
|
|
1049
|
+
|
|
1050
|
+
expect(rowFor("Region 1000")).toHaveAttribute("aria-busy", "true");
|
|
1051
|
+
expect(
|
|
1052
|
+
within(rowFor("Region 1000")).getByTestId(
|
|
1053
|
+
"status-page-resource-navigator-add-sub-group",
|
|
1054
|
+
),
|
|
1055
|
+
).toBeDisabled();
|
|
1056
|
+
expect(
|
|
1057
|
+
within(rowFor("Region 1000")).getByTestId(
|
|
1058
|
+
"status-page-resource-navigator-more",
|
|
1059
|
+
),
|
|
1060
|
+
).toBeDisabled();
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test("leaves every other row alone", () => {
|
|
1064
|
+
renderNavigator(busyProps);
|
|
1065
|
+
|
|
1066
|
+
expect(rowFor("Region 2000")).not.toHaveAttribute("aria-busy");
|
|
1067
|
+
expect(
|
|
1068
|
+
within(rowFor("Region 2000")).getByTestId(
|
|
1069
|
+
"status-page-resource-navigator-more",
|
|
1070
|
+
),
|
|
1071
|
+
).not.toBeDisabled();
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
test("no row is busy when nothing is in flight", () => {
|
|
1075
|
+
renderNavigator({ busyGroupId: null });
|
|
1076
|
+
|
|
1077
|
+
for (const row of screen.getAllByTestId(
|
|
1078
|
+
"status-page-resource-navigator-row",
|
|
1079
|
+
)) {
|
|
1080
|
+
expect(row).not.toHaveAttribute("aria-busy");
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
describe("search results and their context", () => {
|
|
1086
|
+
/*
|
|
1087
|
+
* A row that is only on screen to give a match its place in the hierarchy
|
|
1088
|
+
* is context, not a result, and is drawn quieter.
|
|
1089
|
+
*/
|
|
1090
|
+
test("rows that are only context are drawn quieter than the matches", () => {
|
|
1091
|
+
renderNavigator({
|
|
1092
|
+
rows: makeRows({ searchText: "Market 1001" }),
|
|
1093
|
+
searchText: "Market 1001",
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
const match: HTMLElement = within(rowFor("Market 1001")).getByTestId(
|
|
1097
|
+
"status-page-resource-navigator-name",
|
|
1098
|
+
);
|
|
1099
|
+
const context: HTMLElement = within(rowFor("Corporate")).getByTestId(
|
|
1100
|
+
"status-page-resource-navigator-name",
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
expect(match.className).not.toContain("text-gray-400");
|
|
1104
|
+
expect(context.className).toContain("text-gray-400");
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
test("with no search running, every row is drawn as itself", () => {
|
|
1108
|
+
renderNavigator();
|
|
1109
|
+
|
|
1110
|
+
for (const name of screen.getAllByTestId(
|
|
1111
|
+
"status-page-resource-navigator-name",
|
|
1112
|
+
)) {
|
|
1113
|
+
expect(name.className).not.toContain("text-gray-400");
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
describe("a grid group", () => {
|
|
1119
|
+
/*
|
|
1120
|
+
* A grid group lays its resources out as a matrix, which is the difference
|
|
1121
|
+
* between "add a monitor" and "add a monitor to a cell". The row says so
|
|
1122
|
+
* before it is selected.
|
|
1123
|
+
*/
|
|
1124
|
+
test("is marked as one, and a list group is not", () => {
|
|
1125
|
+
const groups: Array<StatusPageGroup> = [
|
|
1126
|
+
makeGroup({
|
|
1127
|
+
id: CORPORATE,
|
|
1128
|
+
name: "Matrix",
|
|
1129
|
+
order: 1,
|
|
1130
|
+
viewMode: StatusPageGroupViewMode.Grid,
|
|
1131
|
+
}),
|
|
1132
|
+
makeGroup({ id: REGION_TWO, name: "Plain", order: 2 }),
|
|
1133
|
+
];
|
|
1134
|
+
|
|
1135
|
+
renderNavigator({ rows: makeRows({ statusPageGroups: groups }) });
|
|
1136
|
+
|
|
1137
|
+
expect(rowFor("Matrix")).toHaveAttribute(
|
|
1138
|
+
"data-view-mode",
|
|
1139
|
+
StatusPageGroupViewMode.Grid,
|
|
1140
|
+
);
|
|
1141
|
+
expect(rowFor("Plain")).toHaveAttribute(
|
|
1142
|
+
"data-view-mode",
|
|
1143
|
+
StatusPageGroupViewMode.List,
|
|
1144
|
+
);
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
});
|