@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,1452 @@
|
|
|
1
|
+
import StatusPageGroupHierarchyViewUtil, {
|
|
2
|
+
STATUS_PAGE_GROUP_PATH_SEPARATOR,
|
|
3
|
+
StatusPageGroupHierarchyRow,
|
|
4
|
+
StatusPageGroupHierarchySummary,
|
|
5
|
+
} from "../../../Utils/StatusPage/GroupHierarchyView";
|
|
6
|
+
import StatusPageGroupTreeUtil from "../../../Utils/StatusPage/GroupTree";
|
|
7
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
8
|
+
import StatusPageGroup from "../../../Models/DatabaseModels/StatusPageGroup";
|
|
9
|
+
import StatusPageGroupViewMode from "../../../Types/StatusPage/StatusPageGroupViewMode";
|
|
10
|
+
import { describe, expect, test } from "@jest/globals";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Contract under test - the admin view of the status page group hierarchy.
|
|
14
|
+
*
|
|
15
|
+
* The page this backs replaced a flat table, so everything a table gave away
|
|
16
|
+
* for free has to be earned here instead: which rows are on screen once
|
|
17
|
+
* subtrees are collapsed, how deep each row sits, which guide rails are drawn
|
|
18
|
+
* beside it, which way it may be moved, and what a search leaves visible.
|
|
19
|
+
*
|
|
20
|
+
* Two things run through all of it. First, the same bad data the tree utils
|
|
21
|
+
* defend against - a group that points at itself, at a parent that was never
|
|
22
|
+
* fetched, or around a cycle - must not drop a row or hang a walk here either;
|
|
23
|
+
* a group missing from this page is a group an operator cannot fix. Second,
|
|
24
|
+
* anything that will be written to the API (a reorder, the parent picker) has
|
|
25
|
+
* to agree with what StatusPageGroupService will accept, because the only thing
|
|
26
|
+
* worse than not offering a choice is offering one that always fails.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const CORPORATE: ObjectID = new ObjectID(
|
|
30
|
+
"11111111-1111-4111-8111-111111111111",
|
|
31
|
+
);
|
|
32
|
+
const REGION_ONE: ObjectID = new ObjectID(
|
|
33
|
+
"22222222-2222-4222-8222-222222222222",
|
|
34
|
+
);
|
|
35
|
+
const REGION_TWO: ObjectID = new ObjectID(
|
|
36
|
+
"33333333-3333-4333-8333-333333333333",
|
|
37
|
+
);
|
|
38
|
+
const MARKET_ONE: ObjectID = new ObjectID(
|
|
39
|
+
"44444444-4444-4444-8444-444444444444",
|
|
40
|
+
);
|
|
41
|
+
const MARKET_TWO: ObjectID = new ObjectID(
|
|
42
|
+
"66666666-6666-4666-8666-666666666666",
|
|
43
|
+
);
|
|
44
|
+
const UNIT: ObjectID = new ObjectID("55555555-5555-4555-8555-555555555555");
|
|
45
|
+
const STANDALONE: ObjectID = new ObjectID(
|
|
46
|
+
"77777777-7777-4777-8777-777777777777",
|
|
47
|
+
);
|
|
48
|
+
const MISSING: ObjectID = new ObjectID("99999999-9999-4999-8999-999999999999");
|
|
49
|
+
|
|
50
|
+
function makeGroup(data: {
|
|
51
|
+
id: ObjectID;
|
|
52
|
+
name: string;
|
|
53
|
+
parentId?: ObjectID | undefined;
|
|
54
|
+
order?: number | undefined;
|
|
55
|
+
description?: string | undefined;
|
|
56
|
+
viewMode?: StatusPageGroupViewMode | undefined;
|
|
57
|
+
}): StatusPageGroup {
|
|
58
|
+
const group: StatusPageGroup = new StatusPageGroup();
|
|
59
|
+
group._id = data.id.toString();
|
|
60
|
+
group.name = data.name;
|
|
61
|
+
|
|
62
|
+
if (data.parentId) {
|
|
63
|
+
group.parentStatusPageGroupId = data.parentId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (data.order !== undefined) {
|
|
67
|
+
group.order = data.order;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (data.description !== undefined) {
|
|
71
|
+
group.description = data.description;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (data.viewMode !== undefined) {
|
|
75
|
+
group.viewMode = data.viewMode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return group;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Corporate
|
|
83
|
+
* Region 1000
|
|
84
|
+
* Market 1001
|
|
85
|
+
* Unit 0152
|
|
86
|
+
* Market 1002
|
|
87
|
+
* Region 2000
|
|
88
|
+
* Standalone
|
|
89
|
+
*
|
|
90
|
+
* Chosen so every rail case appears at least once: a middle child with
|
|
91
|
+
* children, a last child, and a level that is three deep under a parent that
|
|
92
|
+
* still has siblings of its own.
|
|
93
|
+
*/
|
|
94
|
+
function makeHierarchy(): Array<StatusPageGroup> {
|
|
95
|
+
return [
|
|
96
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
97
|
+
makeGroup({
|
|
98
|
+
id: REGION_ONE,
|
|
99
|
+
name: "Region 1000",
|
|
100
|
+
parentId: CORPORATE,
|
|
101
|
+
order: 2,
|
|
102
|
+
}),
|
|
103
|
+
makeGroup({
|
|
104
|
+
id: MARKET_ONE,
|
|
105
|
+
name: "Market 1001",
|
|
106
|
+
parentId: REGION_ONE,
|
|
107
|
+
order: 3,
|
|
108
|
+
}),
|
|
109
|
+
makeGroup({
|
|
110
|
+
id: UNIT,
|
|
111
|
+
name: "Unit 0152",
|
|
112
|
+
parentId: MARKET_ONE,
|
|
113
|
+
order: 4,
|
|
114
|
+
}),
|
|
115
|
+
makeGroup({
|
|
116
|
+
id: MARKET_TWO,
|
|
117
|
+
name: "Market 1002",
|
|
118
|
+
parentId: REGION_ONE,
|
|
119
|
+
order: 5,
|
|
120
|
+
}),
|
|
121
|
+
makeGroup({
|
|
122
|
+
id: REGION_TWO,
|
|
123
|
+
name: "Region 2000",
|
|
124
|
+
parentId: CORPORATE,
|
|
125
|
+
order: 6,
|
|
126
|
+
}),
|
|
127
|
+
makeGroup({ id: STANDALONE, name: "Standalone", order: 7 }),
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function allIds(statusPageGroups: Array<StatusPageGroup>): Set<string> {
|
|
132
|
+
return new Set<string>(
|
|
133
|
+
statusPageGroups.map((group: StatusPageGroup) => {
|
|
134
|
+
return group._id?.toString() || "";
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function rowNames(rows: Array<StatusPageGroupHierarchyRow>): Array<string> {
|
|
140
|
+
return rows.map((row: StatusPageGroupHierarchyRow) => {
|
|
141
|
+
return row.name;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function rowById(
|
|
146
|
+
rows: Array<StatusPageGroupHierarchyRow>,
|
|
147
|
+
id: ObjectID,
|
|
148
|
+
): StatusPageGroupHierarchyRow {
|
|
149
|
+
const row: StatusPageGroupHierarchyRow | undefined = rows.find(
|
|
150
|
+
(candidate: StatusPageGroupHierarchyRow) => {
|
|
151
|
+
return candidate.id === id.toString();
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!row) {
|
|
156
|
+
throw new Error(`No row for ${id.toString()}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return row;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
describe("StatusPageGroupHierarchyViewUtil", () => {
|
|
163
|
+
describe("getRows - what is on screen", () => {
|
|
164
|
+
test("a status page with no groups produces no rows", () => {
|
|
165
|
+
expect(
|
|
166
|
+
StatusPageGroupHierarchyViewUtil.getRows({ statusPageGroups: [] }),
|
|
167
|
+
).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
/*
|
|
171
|
+
* Collapsed has to mean absent: the component renders one flat list, so a
|
|
172
|
+
* subtree that is still in the array is a subtree that is still on screen.
|
|
173
|
+
*/
|
|
174
|
+
test("nothing is expanded by default, so only the top level is drawn", () => {
|
|
175
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
176
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
177
|
+
statusPageGroups: makeHierarchy(),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(rowNames(rows)).toEqual(["Corporate", "Standalone"]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("expanding a group reveals only its direct children", () => {
|
|
184
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
185
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
186
|
+
statusPageGroups: makeHierarchy(),
|
|
187
|
+
expandedGroupIds: new Set<string>([CORPORATE.toString()]),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(rowNames(rows)).toEqual([
|
|
191
|
+
"Corporate",
|
|
192
|
+
"Region 1000",
|
|
193
|
+
"Region 2000",
|
|
194
|
+
"Standalone",
|
|
195
|
+
]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("the whole hierarchy comes back in render order when everything is open", () => {
|
|
199
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
200
|
+
|
|
201
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
202
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
203
|
+
statusPageGroups: statusPageGroups,
|
|
204
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(rowNames(rows)).toEqual([
|
|
208
|
+
"Corporate",
|
|
209
|
+
"Region 1000",
|
|
210
|
+
"Market 1001",
|
|
211
|
+
"Unit 0152",
|
|
212
|
+
"Market 1002",
|
|
213
|
+
"Region 2000",
|
|
214
|
+
"Standalone",
|
|
215
|
+
]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("siblings come back in order, not in the order they were fetched", () => {
|
|
219
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
220
|
+
makeGroup({ id: STANDALONE, name: "Standalone", order: 9 }),
|
|
221
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
222
|
+
makeGroup({ id: REGION_TWO, name: "Region 2000", order: 5 }),
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
expect(
|
|
226
|
+
rowNames(
|
|
227
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
228
|
+
statusPageGroups: statusPageGroups,
|
|
229
|
+
}),
|
|
230
|
+
),
|
|
231
|
+
).toEqual(["Corporate", "Region 2000", "Standalone"]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("each row carries its depth and its ancestors", () => {
|
|
235
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
236
|
+
|
|
237
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
238
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
239
|
+
statusPageGroups: statusPageGroups,
|
|
240
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const unit: StatusPageGroupHierarchyRow = rowById(rows, UNIT);
|
|
244
|
+
|
|
245
|
+
expect(unit.depth).toBe(3);
|
|
246
|
+
expect(unit.ancestorNames).toEqual([
|
|
247
|
+
"Corporate",
|
|
248
|
+
"Region 1000",
|
|
249
|
+
"Market 1001",
|
|
250
|
+
]);
|
|
251
|
+
expect(rowById(rows, CORPORATE).depth).toBe(0);
|
|
252
|
+
expect(rowById(rows, CORPORATE).ancestorNames).toEqual([]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/*
|
|
256
|
+
* The badge counts the hierarchy, not the screen: an operator collapsing a
|
|
257
|
+
* branch has not changed how many groups are inside it.
|
|
258
|
+
*/
|
|
259
|
+
test("sub group and descendant counts describe the real subtree", () => {
|
|
260
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
261
|
+
|
|
262
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
263
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
264
|
+
statusPageGroups: statusPageGroups,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const corporate: StatusPageGroupHierarchyRow = rowById(rows, CORPORATE);
|
|
268
|
+
|
|
269
|
+
expect(corporate.hasSubGroups).toBe(true);
|
|
270
|
+
expect(corporate.subGroupCount).toBe(2);
|
|
271
|
+
expect(corporate.descendantCount).toBe(5);
|
|
272
|
+
|
|
273
|
+
const standalone: StatusPageGroupHierarchyRow = rowById(rows, STANDALONE);
|
|
274
|
+
|
|
275
|
+
expect(standalone.hasSubGroups).toBe(false);
|
|
276
|
+
expect(standalone.subGroupCount).toBe(0);
|
|
277
|
+
expect(standalone.descendantCount).toBe(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("a group is only reported as expanded when it has children to show", () => {
|
|
281
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
282
|
+
|
|
283
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
284
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
285
|
+
statusPageGroups: statusPageGroups,
|
|
286
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(rowById(rows, CORPORATE).isExpanded).toBe(true);
|
|
290
|
+
expect(rowById(rows, STANDALONE).isExpanded).toBe(false);
|
|
291
|
+
expect(rowById(rows, STANDALONE).hasVisibleSubGroups).toBe(false);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("a group with no name still produces a row", () => {
|
|
295
|
+
const nameless: StatusPageGroup = new StatusPageGroup();
|
|
296
|
+
nameless._id = CORPORATE.toString();
|
|
297
|
+
|
|
298
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
299
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
300
|
+
statusPageGroups: [nameless],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(rows).toHaveLength(1);
|
|
304
|
+
expect(rows[0]!.name).toBe("");
|
|
305
|
+
expect(rows[0]!.id).toBe(CORPORATE.toString());
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("getRows - the guide rails", () => {
|
|
310
|
+
/*
|
|
311
|
+
* The rails are the whole reason a tree reads as a tree. Each indent column
|
|
312
|
+
* left of a row's own connector belongs to one ancestor level, and carries a
|
|
313
|
+
* line only while that level still has a row below this one.
|
|
314
|
+
*/
|
|
315
|
+
test("a top level row has no indent columns at all", () => {
|
|
316
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
317
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
318
|
+
statusPageGroups: makeHierarchy(),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(rowById(rows, CORPORATE).ancestorRails).toEqual([]);
|
|
322
|
+
expect(rowById(rows, CORPORATE).depth).toBe(0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("a first level row has only its own connector", () => {
|
|
326
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
327
|
+
|
|
328
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
329
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
330
|
+
statusPageGroups: statusPageGroups,
|
|
331
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(rowById(rows, REGION_ONE).ancestorRails).toEqual([]);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("a rail is drawn for every ancestor level that still has rows below", () => {
|
|
338
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
339
|
+
|
|
340
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
341
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
342
|
+
statusPageGroups: statusPageGroups,
|
|
343
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
/*
|
|
347
|
+
* Unit 0152 sits under Market 1001 (which is followed by Market 1002)
|
|
348
|
+
* under Region 1000 (which is followed by Region 2000), so both of its
|
|
349
|
+
* ancestor columns carry a line.
|
|
350
|
+
*/
|
|
351
|
+
expect(rowById(rows, UNIT).ancestorRails).toEqual([true, true]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("a rail stops once its level's last row has been drawn", () => {
|
|
355
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
356
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
357
|
+
makeGroup({
|
|
358
|
+
id: REGION_TWO,
|
|
359
|
+
name: "Region 2000",
|
|
360
|
+
parentId: CORPORATE,
|
|
361
|
+
order: 2,
|
|
362
|
+
}),
|
|
363
|
+
makeGroup({
|
|
364
|
+
id: MARKET_ONE,
|
|
365
|
+
name: "Market 1001",
|
|
366
|
+
parentId: REGION_TWO,
|
|
367
|
+
order: 3,
|
|
368
|
+
}),
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
372
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
373
|
+
statusPageGroups: statusPageGroups,
|
|
374
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Region 2000 is Corporate's only child, so its column is blank.
|
|
378
|
+
expect(rowById(rows, MARKET_ONE).ancestorRails).toEqual([false]);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("the last child of a level ends its connector in an elbow", () => {
|
|
382
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
383
|
+
|
|
384
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
385
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
386
|
+
statusPageGroups: statusPageGroups,
|
|
387
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
expect(rowById(rows, REGION_ONE).isLastVisibleSibling).toBe(false);
|
|
391
|
+
expect(rowById(rows, REGION_TWO).isLastVisibleSibling).toBe(true);
|
|
392
|
+
expect(rowById(rows, STANDALONE).isLastVisibleSibling).toBe(true);
|
|
393
|
+
expect(rowById(rows, CORPORATE).isLastVisibleSibling).toBe(false);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
/*
|
|
397
|
+
* "Last" has to mean last among the rows that are drawn. During a search
|
|
398
|
+
* the row that used to be in the middle of a level can become the only one
|
|
399
|
+
* left in it, and an elbow that says otherwise draws a rail into thin air.
|
|
400
|
+
*/
|
|
401
|
+
test("a filtered level ends its elbow on the last row that survives the filter", () => {
|
|
402
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
403
|
+
|
|
404
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
405
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
406
|
+
statusPageGroups: statusPageGroups,
|
|
407
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
408
|
+
searchText: "Region 1000",
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
expect(rowById(rows, REGION_ONE).isLastVisibleSibling).toBe(true);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("sibling positions describe the rows on screen", () => {
|
|
415
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
416
|
+
|
|
417
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
418
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
419
|
+
statusPageGroups: statusPageGroups,
|
|
420
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
expect(rowById(rows, REGION_ONE).visibleSiblingIndex).toBe(0);
|
|
424
|
+
expect(rowById(rows, REGION_ONE).visibleSiblingCount).toBe(2);
|
|
425
|
+
expect(rowById(rows, REGION_TWO).visibleSiblingIndex).toBe(1);
|
|
426
|
+
expect(rowById(rows, STANDALONE).visibleSiblingIndex).toBe(1);
|
|
427
|
+
expect(rowById(rows, STANDALONE).visibleSiblingCount).toBe(2);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
/*
|
|
431
|
+
* Nesting is capped on write at 10 levels, but rows written straight to the
|
|
432
|
+
* database are not, and a row that has indented itself off the right of the
|
|
433
|
+
* card tells the operator nothing at all.
|
|
434
|
+
*/
|
|
435
|
+
test("indent columns stop growing past the cap, keeping the nearest rails", () => {
|
|
436
|
+
const statusPageGroups: Array<StatusPageGroup> = [];
|
|
437
|
+
const ids: Array<ObjectID> = [];
|
|
438
|
+
|
|
439
|
+
for (let level: number = 0; level < 16; level++) {
|
|
440
|
+
const id: ObjectID = new ObjectID(
|
|
441
|
+
`aaaaaaaa-0000-4000-8000-${level.toString().padStart(12, "0")}`,
|
|
442
|
+
);
|
|
443
|
+
ids.push(id);
|
|
444
|
+
|
|
445
|
+
statusPageGroups.push(
|
|
446
|
+
makeGroup({
|
|
447
|
+
id: id,
|
|
448
|
+
name: `Level ${level}`,
|
|
449
|
+
order: level + 1,
|
|
450
|
+
...(level === 0 ? {} : { parentId: ids[level - 1]! }),
|
|
451
|
+
}),
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
456
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
457
|
+
statusPageGroups: statusPageGroups,
|
|
458
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const deepest: StatusPageGroupHierarchyRow = rows[rows.length - 1]!;
|
|
462
|
+
|
|
463
|
+
expect(deepest.depth).toBe(15);
|
|
464
|
+
expect(deepest.ancestorRails).toHaveLength(
|
|
465
|
+
StatusPageGroupHierarchyViewUtil.MaxIndentColumns - 1,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const atCap: StatusPageGroupHierarchyRow = rows[
|
|
469
|
+
StatusPageGroupHierarchyViewUtil.MaxIndentColumns
|
|
470
|
+
] as StatusPageGroupHierarchyRow;
|
|
471
|
+
|
|
472
|
+
expect(atCap.ancestorRails).toHaveLength(
|
|
473
|
+
StatusPageGroupHierarchyViewUtil.MaxIndentColumns - 1,
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("getIndentColumnCount refuses to be broken by a nonsense depth", () => {
|
|
478
|
+
expect(
|
|
479
|
+
StatusPageGroupHierarchyViewUtil.getIndentColumnCount({ depth: 0 }),
|
|
480
|
+
).toBe(0);
|
|
481
|
+
expect(
|
|
482
|
+
StatusPageGroupHierarchyViewUtil.getIndentColumnCount({ depth: -3 }),
|
|
483
|
+
).toBe(0);
|
|
484
|
+
expect(
|
|
485
|
+
StatusPageGroupHierarchyViewUtil.getIndentColumnCount({ depth: NaN }),
|
|
486
|
+
).toBe(0);
|
|
487
|
+
expect(
|
|
488
|
+
StatusPageGroupHierarchyViewUtil.getIndentColumnCount({
|
|
489
|
+
depth: Number.POSITIVE_INFINITY,
|
|
490
|
+
}),
|
|
491
|
+
).toBe(0);
|
|
492
|
+
expect(
|
|
493
|
+
StatusPageGroupHierarchyViewUtil.getIndentColumnCount({ depth: 2.7 }),
|
|
494
|
+
).toBe(2);
|
|
495
|
+
expect(
|
|
496
|
+
StatusPageGroupHierarchyViewUtil.getIndentColumnCount({ depth: 99 }),
|
|
497
|
+
).toBe(StatusPageGroupHierarchyViewUtil.MaxIndentColumns);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
describe("search", () => {
|
|
502
|
+
test("matches on the group name, ignoring case", () => {
|
|
503
|
+
const matched: Set<string> =
|
|
504
|
+
StatusPageGroupHierarchyViewUtil.getMatchingGroupIds({
|
|
505
|
+
statusPageGroups: makeHierarchy(),
|
|
506
|
+
searchText: "unit 0152",
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
expect(Array.from(matched)).toEqual([UNIT.toString()]);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test("matches on the description too", () => {
|
|
513
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
514
|
+
makeGroup({
|
|
515
|
+
id: CORPORATE,
|
|
516
|
+
name: "Corporate",
|
|
517
|
+
description: "Everything the payments team owns",
|
|
518
|
+
}),
|
|
519
|
+
makeGroup({ id: STANDALONE, name: "Standalone" }),
|
|
520
|
+
];
|
|
521
|
+
|
|
522
|
+
expect(
|
|
523
|
+
Array.from(
|
|
524
|
+
StatusPageGroupHierarchyViewUtil.getMatchingGroupIds({
|
|
525
|
+
statusPageGroups: statusPageGroups,
|
|
526
|
+
searchText: "payments",
|
|
527
|
+
}),
|
|
528
|
+
),
|
|
529
|
+
).toEqual([CORPORATE.toString()]);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("an empty or blank search matches nothing in particular", () => {
|
|
533
|
+
expect(
|
|
534
|
+
StatusPageGroupHierarchyViewUtil.getMatchingGroupIds({
|
|
535
|
+
statusPageGroups: makeHierarchy(),
|
|
536
|
+
searchText: " ",
|
|
537
|
+
}).size,
|
|
538
|
+
).toBe(0);
|
|
539
|
+
|
|
540
|
+
expect(
|
|
541
|
+
StatusPageGroupHierarchyViewUtil.getMatchingGroupIds({
|
|
542
|
+
statusPageGroups: makeHierarchy(),
|
|
543
|
+
}).size,
|
|
544
|
+
).toBe(0);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("a blank search leaves the tree exactly as it was", () => {
|
|
548
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
549
|
+
|
|
550
|
+
expect(
|
|
551
|
+
rowNames(
|
|
552
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
553
|
+
statusPageGroups: statusPageGroups,
|
|
554
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
555
|
+
searchText: " ",
|
|
556
|
+
}),
|
|
557
|
+
),
|
|
558
|
+
).toEqual(
|
|
559
|
+
rowNames(
|
|
560
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
561
|
+
statusPageGroups: statusPageGroups,
|
|
562
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
563
|
+
}),
|
|
564
|
+
),
|
|
565
|
+
);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
/*
|
|
569
|
+
* A match behind a collapsed parent is a match the operator cannot see,
|
|
570
|
+
* which reads exactly like a search that found nothing.
|
|
571
|
+
*/
|
|
572
|
+
test("a match is revealed even when its ancestors were collapsed", () => {
|
|
573
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
574
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
575
|
+
statusPageGroups: makeHierarchy(),
|
|
576
|
+
expandedGroupIds: new Set<string>(),
|
|
577
|
+
searchText: "Unit 0152",
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
expect(rowNames(rows)).toEqual([
|
|
581
|
+
"Corporate",
|
|
582
|
+
"Region 1000",
|
|
583
|
+
"Market 1001",
|
|
584
|
+
"Unit 0152",
|
|
585
|
+
]);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test("ancestors are context, and are marked as such", () => {
|
|
589
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
590
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
591
|
+
statusPageGroups: makeHierarchy(),
|
|
592
|
+
searchText: "Unit 0152",
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
expect(rowById(rows, UNIT).isSearchMatch).toBe(true);
|
|
596
|
+
expect(rowById(rows, CORPORATE).isSearchMatch).toBe(false);
|
|
597
|
+
expect(rowById(rows, MARKET_ONE).isSearchMatch).toBe(false);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("every row is a match when nothing is being searched for", () => {
|
|
601
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
602
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
603
|
+
statusPageGroups: makeHierarchy(),
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
for (const row of rows) {
|
|
607
|
+
expect(row.isSearchMatch).toBe(true);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
/*
|
|
612
|
+
* Searching for a branch and being shown an empty branch would be answering
|
|
613
|
+
* a different question from the one that was asked.
|
|
614
|
+
*/
|
|
615
|
+
test("a matched group keeps its own subtree available", () => {
|
|
616
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
617
|
+
|
|
618
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
619
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
620
|
+
statusPageGroups: statusPageGroups,
|
|
621
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
622
|
+
searchText: "Region 1000",
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
expect(rowNames(rows)).toEqual([
|
|
626
|
+
"Corporate",
|
|
627
|
+
"Region 1000",
|
|
628
|
+
"Market 1001",
|
|
629
|
+
"Unit 0152",
|
|
630
|
+
"Market 1002",
|
|
631
|
+
]);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test("branches with nothing matching in them are gone entirely", () => {
|
|
635
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
636
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
637
|
+
statusPageGroups: makeHierarchy(),
|
|
638
|
+
searchText: "Standalone",
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
expect(rowNames(rows)).toEqual(["Standalone"]);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
test("a search that matches nothing produces no rows at all", () => {
|
|
645
|
+
expect(
|
|
646
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
647
|
+
statusPageGroups: makeHierarchy(),
|
|
648
|
+
searchText: "nothing here by that name",
|
|
649
|
+
}),
|
|
650
|
+
).toEqual([]);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test("getSearchVisibleGroupIds covers the match, its ancestors and its subtree", () => {
|
|
654
|
+
const visible: Set<string> =
|
|
655
|
+
StatusPageGroupHierarchyViewUtil.getSearchVisibleGroupIds({
|
|
656
|
+
statusPageGroups: makeHierarchy(),
|
|
657
|
+
searchText: "Region 1000",
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
expect(visible.has(REGION_ONE.toString())).toBe(true);
|
|
661
|
+
expect(visible.has(CORPORATE.toString())).toBe(true);
|
|
662
|
+
expect(visible.has(MARKET_ONE.toString())).toBe(true);
|
|
663
|
+
expect(visible.has(UNIT.toString())).toBe(true);
|
|
664
|
+
expect(visible.has(REGION_TWO.toString())).toBe(false);
|
|
665
|
+
expect(visible.has(STANDALONE.toString())).toBe(false);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test("matchesSearch treats no search text as matching everything", () => {
|
|
669
|
+
expect(
|
|
670
|
+
StatusPageGroupHierarchyViewUtil.matchesSearch({
|
|
671
|
+
statusPageGroup: makeGroup({ id: CORPORATE, name: "Corporate" }),
|
|
672
|
+
}),
|
|
673
|
+
).toBe(true);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
describe("reordering", () => {
|
|
678
|
+
test("a row knows the siblings it can swap with", () => {
|
|
679
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
680
|
+
|
|
681
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
682
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
683
|
+
statusPageGroups: statusPageGroups,
|
|
684
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
const marketOne: StatusPageGroupHierarchyRow = rowById(rows, MARKET_ONE);
|
|
688
|
+
|
|
689
|
+
expect(marketOne.canMoveUp).toBe(false);
|
|
690
|
+
expect(marketOne.canMoveDown).toBe(true);
|
|
691
|
+
expect(marketOne.nextSiblingId).toBe(MARKET_TWO.toString());
|
|
692
|
+
|
|
693
|
+
const marketTwo: StatusPageGroupHierarchyRow = rowById(rows, MARKET_TWO);
|
|
694
|
+
|
|
695
|
+
expect(marketTwo.canMoveUp).toBe(true);
|
|
696
|
+
expect(marketTwo.previousSiblingId).toBe(MARKET_ONE.toString());
|
|
697
|
+
expect(marketTwo.canMoveDown).toBe(false);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
test("an only child can be moved neither way", () => {
|
|
701
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
702
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
703
|
+
makeGroup({
|
|
704
|
+
id: REGION_ONE,
|
|
705
|
+
name: "Region 1000",
|
|
706
|
+
parentId: CORPORATE,
|
|
707
|
+
order: 2,
|
|
708
|
+
}),
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
712
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
713
|
+
statusPageGroups: statusPageGroups,
|
|
714
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
expect(rowById(rows, REGION_ONE).canMoveUp).toBe(false);
|
|
718
|
+
expect(rowById(rows, REGION_ONE).canMoveDown).toBe(false);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
/*
|
|
722
|
+
* A move is a change to the hierarchy, so it has to mean the same thing
|
|
723
|
+
* whatever the operator has typed into the search box. Reading neighbours
|
|
724
|
+
* off the filtered view would let "move up" swap two rows that are not
|
|
725
|
+
* actually adjacent.
|
|
726
|
+
*/
|
|
727
|
+
test("neighbours are the real siblings, not the ones a search left visible", () => {
|
|
728
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
729
|
+
|
|
730
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
731
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
732
|
+
statusPageGroups: statusPageGroups,
|
|
733
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
734
|
+
searchText: "Market 1002",
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
const marketTwo: StatusPageGroupHierarchyRow = rowById(rows, MARKET_TWO);
|
|
738
|
+
|
|
739
|
+
expect(marketTwo.previousSiblingId).toBe(MARKET_ONE.toString());
|
|
740
|
+
expect(marketTwo.canMoveUp).toBe(true);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
test("moving up targets the order of the sibling above", () => {
|
|
744
|
+
expect(
|
|
745
|
+
StatusPageGroupHierarchyViewUtil.getReorderTargetOrder({
|
|
746
|
+
statusPageGroups: makeHierarchy(),
|
|
747
|
+
statusPageGroupId: MARKET_TWO.toString(),
|
|
748
|
+
direction: "up",
|
|
749
|
+
}),
|
|
750
|
+
).toBe(3);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("moving down targets the order of the sibling below", () => {
|
|
754
|
+
expect(
|
|
755
|
+
StatusPageGroupHierarchyViewUtil.getReorderTargetOrder({
|
|
756
|
+
statusPageGroups: makeHierarchy(),
|
|
757
|
+
statusPageGroupId: REGION_ONE.toString(),
|
|
758
|
+
direction: "down",
|
|
759
|
+
}),
|
|
760
|
+
).toBe(6);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
test("a group with no neighbour that way has nothing to write", () => {
|
|
764
|
+
expect(
|
|
765
|
+
StatusPageGroupHierarchyViewUtil.getReorderTargetOrder({
|
|
766
|
+
statusPageGroups: makeHierarchy(),
|
|
767
|
+
statusPageGroupId: MARKET_ONE.toString(),
|
|
768
|
+
direction: "up",
|
|
769
|
+
}),
|
|
770
|
+
).toBeNull();
|
|
771
|
+
|
|
772
|
+
expect(
|
|
773
|
+
StatusPageGroupHierarchyViewUtil.getReorderTargetOrder({
|
|
774
|
+
statusPageGroups: makeHierarchy(),
|
|
775
|
+
statusPageGroupId: STANDALONE.toString(),
|
|
776
|
+
direction: "down",
|
|
777
|
+
}),
|
|
778
|
+
).toBeNull();
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
test("a group that is not in the list at all has nothing to write", () => {
|
|
782
|
+
expect(
|
|
783
|
+
StatusPageGroupHierarchyViewUtil.getReorderTargetOrder({
|
|
784
|
+
statusPageGroups: makeHierarchy(),
|
|
785
|
+
statusPageGroupId: MISSING.toString(),
|
|
786
|
+
direction: "up",
|
|
787
|
+
}),
|
|
788
|
+
).toBeNull();
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
/*
|
|
792
|
+
* `order` is nullable in the database. Writing `undefined` as a target order
|
|
793
|
+
* would blank the column rather than move the row.
|
|
794
|
+
*/
|
|
795
|
+
test("a neighbour with no order of its own is not a move target", () => {
|
|
796
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
797
|
+
makeGroup({ id: CORPORATE, name: "Corporate" }),
|
|
798
|
+
makeGroup({ id: STANDALONE, name: "Standalone", order: 2 }),
|
|
799
|
+
];
|
|
800
|
+
|
|
801
|
+
expect(
|
|
802
|
+
StatusPageGroupHierarchyViewUtil.getReorderTargetOrder({
|
|
803
|
+
statusPageGroups: statusPageGroups,
|
|
804
|
+
statusPageGroupId: STANDALONE.toString(),
|
|
805
|
+
direction: "up",
|
|
806
|
+
}),
|
|
807
|
+
).toBeNull();
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
test("top level neighbours are read from the top level, not from a parent bucket", () => {
|
|
811
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
812
|
+
|
|
813
|
+
const siblings: Array<StatusPageGroup> =
|
|
814
|
+
StatusPageGroupHierarchyViewUtil.getSiblingGroups({
|
|
815
|
+
statusPageGroup: statusPageGroups[0]!,
|
|
816
|
+
statusPageGroups: statusPageGroups,
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
expect(
|
|
820
|
+
siblings.map((group: StatusPageGroup) => {
|
|
821
|
+
return group.name;
|
|
822
|
+
}),
|
|
823
|
+
).toEqual(["Corporate", "Standalone"]);
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
describe("expansion helpers", () => {
|
|
828
|
+
test("every group with sub groups is expandable", () => {
|
|
829
|
+
const expandable: Set<string> =
|
|
830
|
+
StatusPageGroupHierarchyViewUtil.getExpandableGroupIds({
|
|
831
|
+
statusPageGroups: makeHierarchy(),
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
expect(expandable.has(CORPORATE.toString())).toBe(true);
|
|
835
|
+
expect(expandable.has(REGION_ONE.toString())).toBe(true);
|
|
836
|
+
expect(expandable.has(MARKET_ONE.toString())).toBe(true);
|
|
837
|
+
expect(expandable.has(MARKET_TWO.toString())).toBe(false);
|
|
838
|
+
expect(expandable.has(STANDALONE.toString())).toBe(false);
|
|
839
|
+
expect(expandable.size).toBe(3);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
test("expanding everything really does draw every group", () => {
|
|
843
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
844
|
+
|
|
845
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
846
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
847
|
+
statusPageGroups: statusPageGroups,
|
|
848
|
+
expandedGroupIds:
|
|
849
|
+
StatusPageGroupHierarchyViewUtil.getExpandableGroupIds({
|
|
850
|
+
statusPageGroups: statusPageGroups,
|
|
851
|
+
}),
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
expect(rows).toHaveLength(statusPageGroups.length);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
/*
|
|
858
|
+
* A hierarchy that arrives fully expanded is unreadable at any real size,
|
|
859
|
+
* and one that arrives fully collapsed hides the fact that it nests at all -
|
|
860
|
+
* which is the single thing this page exists to show.
|
|
861
|
+
*/
|
|
862
|
+
test("the default opens one level of children and no more", () => {
|
|
863
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
864
|
+
|
|
865
|
+
const expanded: Set<string> =
|
|
866
|
+
StatusPageGroupHierarchyViewUtil.getDefaultExpandedGroupIds({
|
|
867
|
+
statusPageGroups: statusPageGroups,
|
|
868
|
+
maxAutoExpandDepth: 1,
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
expect(expanded.has(CORPORATE.toString())).toBe(true);
|
|
872
|
+
expect(expanded.has(REGION_ONE.toString())).toBe(true);
|
|
873
|
+
expect(expanded.has(MARKET_ONE.toString())).toBe(false);
|
|
874
|
+
|
|
875
|
+
expect(
|
|
876
|
+
rowNames(
|
|
877
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
878
|
+
statusPageGroups: statusPageGroups,
|
|
879
|
+
expandedGroupIds: expanded,
|
|
880
|
+
}),
|
|
881
|
+
),
|
|
882
|
+
).toEqual([
|
|
883
|
+
"Corporate",
|
|
884
|
+
"Region 1000",
|
|
885
|
+
"Market 1001",
|
|
886
|
+
"Market 1002",
|
|
887
|
+
"Region 2000",
|
|
888
|
+
"Standalone",
|
|
889
|
+
]);
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
test("a depth of zero opens only the top level", () => {
|
|
893
|
+
const expanded: Set<string> =
|
|
894
|
+
StatusPageGroupHierarchyViewUtil.getDefaultExpandedGroupIds({
|
|
895
|
+
statusPageGroups: makeHierarchy(),
|
|
896
|
+
maxAutoExpandDepth: 0,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
expect(Array.from(expanded)).toEqual([CORPORATE.toString()]);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
test("leaves are never in the expanded set - there is nothing to open", () => {
|
|
903
|
+
const expanded: Set<string> =
|
|
904
|
+
StatusPageGroupHierarchyViewUtil.getDefaultExpandedGroupIds({
|
|
905
|
+
statusPageGroups: makeHierarchy(),
|
|
906
|
+
maxAutoExpandDepth: 9,
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
expect(expanded.has(STANDALONE.toString())).toBe(false);
|
|
910
|
+
expect(expanded.has(UNIT.toString())).toBe(false);
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
describe("getSummary", () => {
|
|
915
|
+
test("counts the whole hierarchy and how deep it goes", () => {
|
|
916
|
+
const summary: StatusPageGroupHierarchySummary =
|
|
917
|
+
StatusPageGroupHierarchyViewUtil.getSummary({
|
|
918
|
+
statusPageGroups: makeHierarchy(),
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
expect(summary).toEqual({
|
|
922
|
+
totalCount: 7,
|
|
923
|
+
topLevelCount: 2,
|
|
924
|
+
nestedCount: 5,
|
|
925
|
+
levelCount: 4,
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
test("a flat list is one level deep, not zero", () => {
|
|
930
|
+
expect(
|
|
931
|
+
StatusPageGroupHierarchyViewUtil.getSummary({
|
|
932
|
+
statusPageGroups: [
|
|
933
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
934
|
+
makeGroup({ id: STANDALONE, name: "Standalone", order: 2 }),
|
|
935
|
+
],
|
|
936
|
+
}),
|
|
937
|
+
).toEqual({
|
|
938
|
+
totalCount: 2,
|
|
939
|
+
topLevelCount: 2,
|
|
940
|
+
nestedCount: 0,
|
|
941
|
+
levelCount: 1,
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test("an empty status page has no levels", () => {
|
|
946
|
+
expect(
|
|
947
|
+
StatusPageGroupHierarchyViewUtil.getSummary({ statusPageGroups: [] }),
|
|
948
|
+
).toEqual({
|
|
949
|
+
totalCount: 0,
|
|
950
|
+
topLevelCount: 0,
|
|
951
|
+
nestedCount: 0,
|
|
952
|
+
levelCount: 0,
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
describe("getGroupPathLabel", () => {
|
|
958
|
+
test("reads top down and ends on the group itself", () => {
|
|
959
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
960
|
+
|
|
961
|
+
expect(
|
|
962
|
+
StatusPageGroupHierarchyViewUtil.getGroupPathLabel({
|
|
963
|
+
statusPageGroup: statusPageGroups[3]!,
|
|
964
|
+
statusPageGroups: statusPageGroups,
|
|
965
|
+
}),
|
|
966
|
+
).toBe(
|
|
967
|
+
["Corporate", "Region 1000", "Market 1001", "Unit 0152"].join(
|
|
968
|
+
STATUS_PAGE_GROUP_PATH_SEPARATOR,
|
|
969
|
+
),
|
|
970
|
+
);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
test("a top level group is just its own name", () => {
|
|
974
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
975
|
+
|
|
976
|
+
expect(
|
|
977
|
+
StatusPageGroupHierarchyViewUtil.getGroupPathLabel({
|
|
978
|
+
statusPageGroup: statusPageGroups[0]!,
|
|
979
|
+
statusPageGroups: statusPageGroups,
|
|
980
|
+
}),
|
|
981
|
+
).toBe("Corporate");
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
/*
|
|
985
|
+
* The rest of the admin - Resources, Monitor Rules, the CSV import - all
|
|
986
|
+
* spell a path the same way, and an operator reads them side by side.
|
|
987
|
+
*/
|
|
988
|
+
test("uses the separator the rest of the admin uses", () => {
|
|
989
|
+
expect(STATUS_PAGE_GROUP_PATH_SEPARATOR).toBe(" › ");
|
|
990
|
+
});
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
describe("getSubtreeHeight", () => {
|
|
994
|
+
test("a leaf has no height", () => {
|
|
995
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
996
|
+
|
|
997
|
+
expect(
|
|
998
|
+
StatusPageGroupHierarchyViewUtil.getSubtreeHeight({
|
|
999
|
+
statusPageGroup: statusPageGroups[6]!,
|
|
1000
|
+
statusPageGroups: statusPageGroups,
|
|
1001
|
+
}),
|
|
1002
|
+
).toBe(0);
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
test("counts the levels below the group, not its own depth", () => {
|
|
1006
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
1007
|
+
|
|
1008
|
+
expect(
|
|
1009
|
+
StatusPageGroupHierarchyViewUtil.getSubtreeHeight({
|
|
1010
|
+
statusPageGroup: statusPageGroups[0]!,
|
|
1011
|
+
statusPageGroups: statusPageGroups,
|
|
1012
|
+
}),
|
|
1013
|
+
).toBe(3);
|
|
1014
|
+
|
|
1015
|
+
expect(
|
|
1016
|
+
StatusPageGroupHierarchyViewUtil.getSubtreeHeight({
|
|
1017
|
+
statusPageGroup: statusPageGroups[1]!,
|
|
1018
|
+
statusPageGroups: statusPageGroups,
|
|
1019
|
+
}),
|
|
1020
|
+
).toBe(2);
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
describe("getParentGroupCandidates", () => {
|
|
1025
|
+
test("creating a group may pick any group as its parent", () => {
|
|
1026
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
1027
|
+
|
|
1028
|
+
const candidates: Array<StatusPageGroup> =
|
|
1029
|
+
StatusPageGroupHierarchyViewUtil.getParentGroupCandidates({
|
|
1030
|
+
statusPageGroups: statusPageGroups,
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
expect(
|
|
1034
|
+
candidates.map((group: StatusPageGroup) => {
|
|
1035
|
+
return group.name;
|
|
1036
|
+
}),
|
|
1037
|
+
).toEqual([
|
|
1038
|
+
"Corporate",
|
|
1039
|
+
"Region 1000",
|
|
1040
|
+
"Market 1001",
|
|
1041
|
+
"Unit 0152",
|
|
1042
|
+
"Market 1002",
|
|
1043
|
+
"Region 2000",
|
|
1044
|
+
"Standalone",
|
|
1045
|
+
]);
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
/* "A group cannot be its own parent group." - StatusPageGroupService. */
|
|
1049
|
+
test("a group is never offered itself", () => {
|
|
1050
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
1051
|
+
|
|
1052
|
+
const candidateIds: Array<string | undefined> =
|
|
1053
|
+
StatusPageGroupHierarchyViewUtil.getParentGroupCandidates({
|
|
1054
|
+
statusPageGroups: statusPageGroups,
|
|
1055
|
+
statusPageGroupId: REGION_ONE.toString(),
|
|
1056
|
+
}).map((group: StatusPageGroup) => {
|
|
1057
|
+
return group._id?.toString();
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
expect(candidateIds).not.toContain(REGION_ONE.toString());
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
/*
|
|
1064
|
+
* "This group cannot be nested under one of its own sub groups." A cycle
|
|
1065
|
+
* would make every rolled up number on the status page meaningless, so the
|
|
1066
|
+
* picker does not offer the move in the first place.
|
|
1067
|
+
*/
|
|
1068
|
+
test("a group is never offered one of its own sub groups", () => {
|
|
1069
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
1070
|
+
|
|
1071
|
+
const candidateIds: Array<string | undefined> =
|
|
1072
|
+
StatusPageGroupHierarchyViewUtil.getParentGroupCandidates({
|
|
1073
|
+
statusPageGroups: statusPageGroups,
|
|
1074
|
+
statusPageGroupId: REGION_ONE.toString(),
|
|
1075
|
+
}).map((group: StatusPageGroup) => {
|
|
1076
|
+
return group._id?.toString();
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
expect(candidateIds).not.toContain(MARKET_ONE.toString());
|
|
1080
|
+
expect(candidateIds).not.toContain(MARKET_TWO.toString());
|
|
1081
|
+
expect(candidateIds).not.toContain(UNIT.toString());
|
|
1082
|
+
expect(candidateIds).toContain(CORPORATE.toString());
|
|
1083
|
+
expect(candidateIds).toContain(REGION_TWO.toString());
|
|
1084
|
+
expect(candidateIds).toContain(STANDALONE.toString());
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
/*
|
|
1088
|
+
* The service refuses a move when the parent's depth plus the moved
|
|
1089
|
+
* subtree's own height reaches MaxNestingDepth. The picker applies exactly
|
|
1090
|
+
* the same arithmetic so it cannot offer a parent the API will reject.
|
|
1091
|
+
*/
|
|
1092
|
+
test("a parent too deep to hold the group is not offered", () => {
|
|
1093
|
+
const statusPageGroups: Array<StatusPageGroup> = [];
|
|
1094
|
+
const ids: Array<ObjectID> = [];
|
|
1095
|
+
|
|
1096
|
+
for (
|
|
1097
|
+
let level: number = 0;
|
|
1098
|
+
level < StatusPageGroupTreeUtil.MaxNestingDepth;
|
|
1099
|
+
level++
|
|
1100
|
+
) {
|
|
1101
|
+
const id: ObjectID = new ObjectID(
|
|
1102
|
+
`bbbbbbbb-0000-4000-8000-${level.toString().padStart(12, "0")}`,
|
|
1103
|
+
);
|
|
1104
|
+
ids.push(id);
|
|
1105
|
+
|
|
1106
|
+
statusPageGroups.push(
|
|
1107
|
+
makeGroup({
|
|
1108
|
+
id: id,
|
|
1109
|
+
name: `Level ${level}`,
|
|
1110
|
+
order: level + 1,
|
|
1111
|
+
...(level === 0 ? {} : { parentId: ids[level - 1]! }),
|
|
1112
|
+
}),
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const loose: ObjectID = new ObjectID(
|
|
1117
|
+
"cccccccc-0000-4000-8000-000000000000",
|
|
1118
|
+
);
|
|
1119
|
+
statusPageGroups.push(
|
|
1120
|
+
makeGroup({ id: loose, name: "Loose", order: 100 }),
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
const candidateNames: Array<string | undefined> =
|
|
1124
|
+
StatusPageGroupHierarchyViewUtil.getParentGroupCandidates({
|
|
1125
|
+
statusPageGroups: statusPageGroups,
|
|
1126
|
+
statusPageGroupId: loose.toString(),
|
|
1127
|
+
}).map((group: StatusPageGroup) => {
|
|
1128
|
+
return group.name;
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
/* The deepest legal parent leaves room for exactly one more level. */
|
|
1132
|
+
expect(candidateNames).toContain(
|
|
1133
|
+
`Level ${StatusPageGroupTreeUtil.MaxNestingDepth - 2}`,
|
|
1134
|
+
);
|
|
1135
|
+
expect(candidateNames).not.toContain(
|
|
1136
|
+
`Level ${StatusPageGroupTreeUtil.MaxNestingDepth - 1}`,
|
|
1137
|
+
);
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
test("a group carrying a tall subtree loses parents a leaf could still use", () => {
|
|
1141
|
+
const statusPageGroups: Array<StatusPageGroup> = [];
|
|
1142
|
+
const ids: Array<ObjectID> = [];
|
|
1143
|
+
|
|
1144
|
+
for (let level: number = 0; level < 8; level++) {
|
|
1145
|
+
const id: ObjectID = new ObjectID(
|
|
1146
|
+
`dddddddd-0000-4000-8000-${level.toString().padStart(12, "0")}`,
|
|
1147
|
+
);
|
|
1148
|
+
ids.push(id);
|
|
1149
|
+
|
|
1150
|
+
statusPageGroups.push(
|
|
1151
|
+
makeGroup({
|
|
1152
|
+
id: id,
|
|
1153
|
+
name: `Level ${level}`,
|
|
1154
|
+
order: level + 1,
|
|
1155
|
+
...(level === 0 ? {} : { parentId: ids[level - 1]! }),
|
|
1156
|
+
}),
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const branchRoot: ObjectID = new ObjectID(
|
|
1161
|
+
"eeeeeeee-0000-4000-8000-000000000000",
|
|
1162
|
+
);
|
|
1163
|
+
const branchChild: ObjectID = new ObjectID(
|
|
1164
|
+
"eeeeeeee-0000-4000-8000-000000000001",
|
|
1165
|
+
);
|
|
1166
|
+
const branchGrandChild: ObjectID = new ObjectID(
|
|
1167
|
+
"eeeeeeee-0000-4000-8000-000000000002",
|
|
1168
|
+
);
|
|
1169
|
+
|
|
1170
|
+
statusPageGroups.push(
|
|
1171
|
+
makeGroup({ id: branchRoot, name: "Branch", order: 50 }),
|
|
1172
|
+
makeGroup({
|
|
1173
|
+
id: branchChild,
|
|
1174
|
+
name: "Branch Child",
|
|
1175
|
+
parentId: branchRoot,
|
|
1176
|
+
order: 51,
|
|
1177
|
+
}),
|
|
1178
|
+
makeGroup({
|
|
1179
|
+
id: branchGrandChild,
|
|
1180
|
+
name: "Branch Grandchild",
|
|
1181
|
+
parentId: branchChild,
|
|
1182
|
+
order: 52,
|
|
1183
|
+
}),
|
|
1184
|
+
);
|
|
1185
|
+
|
|
1186
|
+
const leafCandidates: Array<string | undefined> =
|
|
1187
|
+
StatusPageGroupHierarchyViewUtil.getParentGroupCandidates({
|
|
1188
|
+
statusPageGroups: statusPageGroups,
|
|
1189
|
+
statusPageGroupId: branchGrandChild.toString(),
|
|
1190
|
+
}).map((group: StatusPageGroup) => {
|
|
1191
|
+
return group.name;
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
const branchCandidates: Array<string | undefined> =
|
|
1195
|
+
StatusPageGroupHierarchyViewUtil.getParentGroupCandidates({
|
|
1196
|
+
statusPageGroups: statusPageGroups,
|
|
1197
|
+
statusPageGroupId: branchRoot.toString(),
|
|
1198
|
+
}).map((group: StatusPageGroup) => {
|
|
1199
|
+
return group.name;
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
expect(leafCandidates).toContain("Level 7");
|
|
1203
|
+
expect(branchCandidates).not.toContain("Level 7");
|
|
1204
|
+
expect(branchCandidates).toContain("Level 5");
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
test("a group id that is not on this status page excludes nothing", () => {
|
|
1208
|
+
expect(
|
|
1209
|
+
StatusPageGroupHierarchyViewUtil.getParentGroupCandidates({
|
|
1210
|
+
statusPageGroups: makeHierarchy(),
|
|
1211
|
+
statusPageGroupId: MISSING.toString(),
|
|
1212
|
+
}),
|
|
1213
|
+
).toHaveLength(7);
|
|
1214
|
+
});
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
/*
|
|
1218
|
+
* The parent pointer is a plain nullable column. Rows written straight to the
|
|
1219
|
+
* database, or a group whose parent the page did not fetch, can point at a
|
|
1220
|
+
* missing parent, at themselves, or around a cycle - and a group that
|
|
1221
|
+
* disappears from this page is a group nobody can repair.
|
|
1222
|
+
*/
|
|
1223
|
+
describe("data the database allows and the page has to survive", () => {
|
|
1224
|
+
test("a group that points at itself renders at the top level", () => {
|
|
1225
|
+
const selfParented: StatusPageGroup = makeGroup({
|
|
1226
|
+
id: CORPORATE,
|
|
1227
|
+
name: "Corporate",
|
|
1228
|
+
parentId: CORPORATE,
|
|
1229
|
+
order: 1,
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
1233
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1234
|
+
statusPageGroups: [selfParented],
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
expect(rows).toHaveLength(1);
|
|
1238
|
+
expect(rows[0]!.depth).toBe(0);
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
test("a group whose parent was never fetched still renders", () => {
|
|
1242
|
+
const orphan: StatusPageGroup = makeGroup({
|
|
1243
|
+
id: REGION_ONE,
|
|
1244
|
+
name: "Region 1000",
|
|
1245
|
+
parentId: MISSING,
|
|
1246
|
+
order: 2,
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
1250
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1251
|
+
statusPageGroups: [
|
|
1252
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
1253
|
+
orphan,
|
|
1254
|
+
],
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
expect(rowNames(rows)).toEqual(["Corporate", "Region 1000"]);
|
|
1258
|
+
expect(rowById(rows, REGION_ONE).depth).toBe(0);
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
test("an orphan can still be reordered against the top level", () => {
|
|
1262
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
1263
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
1264
|
+
makeGroup({
|
|
1265
|
+
id: REGION_ONE,
|
|
1266
|
+
name: "Region 1000",
|
|
1267
|
+
parentId: MISSING,
|
|
1268
|
+
order: 2,
|
|
1269
|
+
}),
|
|
1270
|
+
];
|
|
1271
|
+
|
|
1272
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
1273
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1274
|
+
statusPageGroups: statusPageGroups,
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
expect(rowById(rows, REGION_ONE).canMoveUp).toBe(true);
|
|
1278
|
+
expect(
|
|
1279
|
+
StatusPageGroupHierarchyViewUtil.getReorderTargetOrder({
|
|
1280
|
+
statusPageGroups: statusPageGroups,
|
|
1281
|
+
statusPageGroupId: REGION_ONE.toString(),
|
|
1282
|
+
direction: "up",
|
|
1283
|
+
}),
|
|
1284
|
+
).toBe(1);
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
test("a cycle neither hangs the walk nor loses a group", () => {
|
|
1288
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
1289
|
+
makeGroup({
|
|
1290
|
+
id: CORPORATE,
|
|
1291
|
+
name: "Corporate",
|
|
1292
|
+
parentId: REGION_ONE,
|
|
1293
|
+
order: 1,
|
|
1294
|
+
}),
|
|
1295
|
+
makeGroup({
|
|
1296
|
+
id: REGION_ONE,
|
|
1297
|
+
name: "Region 1000",
|
|
1298
|
+
parentId: CORPORATE,
|
|
1299
|
+
order: 2,
|
|
1300
|
+
}),
|
|
1301
|
+
];
|
|
1302
|
+
|
|
1303
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
1304
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1305
|
+
statusPageGroups: statusPageGroups,
|
|
1306
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
expect(rows).toHaveLength(2);
|
|
1310
|
+
expect(rowNames(rows).sort()).toEqual(["Corporate", "Region 1000"]);
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
test("a cycle does not hang the summary either", () => {
|
|
1314
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
1315
|
+
makeGroup({
|
|
1316
|
+
id: CORPORATE,
|
|
1317
|
+
name: "Corporate",
|
|
1318
|
+
parentId: REGION_ONE,
|
|
1319
|
+
order: 1,
|
|
1320
|
+
}),
|
|
1321
|
+
makeGroup({
|
|
1322
|
+
id: REGION_ONE,
|
|
1323
|
+
name: "Region 1000",
|
|
1324
|
+
parentId: CORPORATE,
|
|
1325
|
+
order: 2,
|
|
1326
|
+
}),
|
|
1327
|
+
];
|
|
1328
|
+
|
|
1329
|
+
expect(
|
|
1330
|
+
StatusPageGroupHierarchyViewUtil.getSummary({
|
|
1331
|
+
statusPageGroups: statusPageGroups,
|
|
1332
|
+
}).totalCount,
|
|
1333
|
+
).toBe(2);
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
test("groups without an order keep the position they arrived in", () => {
|
|
1337
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
1338
|
+
makeGroup({ id: STANDALONE, name: "Standalone" }),
|
|
1339
|
+
makeGroup({ id: CORPORATE, name: "Corporate" }),
|
|
1340
|
+
];
|
|
1341
|
+
|
|
1342
|
+
expect(
|
|
1343
|
+
rowNames(
|
|
1344
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1345
|
+
statusPageGroups: statusPageGroups,
|
|
1346
|
+
}),
|
|
1347
|
+
),
|
|
1348
|
+
).toEqual(["Standalone", "Corporate"]);
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
test("every group appears exactly once, whatever shape the data is in", () => {
|
|
1352
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
1353
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
1354
|
+
makeGroup({
|
|
1355
|
+
id: REGION_ONE,
|
|
1356
|
+
name: "Region 1000",
|
|
1357
|
+
parentId: CORPORATE,
|
|
1358
|
+
order: 2,
|
|
1359
|
+
}),
|
|
1360
|
+
makeGroup({
|
|
1361
|
+
id: REGION_TWO,
|
|
1362
|
+
name: "Region 2000",
|
|
1363
|
+
parentId: MISSING,
|
|
1364
|
+
order: 3,
|
|
1365
|
+
}),
|
|
1366
|
+
makeGroup({
|
|
1367
|
+
id: MARKET_ONE,
|
|
1368
|
+
name: "Market 1001",
|
|
1369
|
+
parentId: MARKET_TWO,
|
|
1370
|
+
order: 4,
|
|
1371
|
+
}),
|
|
1372
|
+
makeGroup({
|
|
1373
|
+
id: MARKET_TWO,
|
|
1374
|
+
name: "Market 1002",
|
|
1375
|
+
parentId: MARKET_ONE,
|
|
1376
|
+
order: 5,
|
|
1377
|
+
}),
|
|
1378
|
+
makeGroup({
|
|
1379
|
+
id: UNIT,
|
|
1380
|
+
name: "Unit 0152",
|
|
1381
|
+
parentId: UNIT,
|
|
1382
|
+
order: 6,
|
|
1383
|
+
}),
|
|
1384
|
+
];
|
|
1385
|
+
|
|
1386
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
1387
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1388
|
+
statusPageGroups: statusPageGroups,
|
|
1389
|
+
expandedGroupIds: allIds(statusPageGroups),
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
expect(rows).toHaveLength(statusPageGroups.length);
|
|
1393
|
+
expect(new Set<string>(rowNames(rows)).size).toBe(
|
|
1394
|
+
statusPageGroups.length,
|
|
1395
|
+
);
|
|
1396
|
+
});
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
describe("what the row hands the renderer", () => {
|
|
1400
|
+
test("the group itself comes along, so the row can draw its settings", () => {
|
|
1401
|
+
const grid: StatusPageGroup = makeGroup({
|
|
1402
|
+
id: CORPORATE,
|
|
1403
|
+
name: "Corporate",
|
|
1404
|
+
order: 1,
|
|
1405
|
+
viewMode: StatusPageGroupViewMode.Grid,
|
|
1406
|
+
description: "The whole corporate unit",
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
const rows: Array<StatusPageGroupHierarchyRow> =
|
|
1410
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1411
|
+
statusPageGroups: [grid],
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
expect(rows[0]!.statusPageGroup).toBe(grid);
|
|
1415
|
+
expect(rows[0]!.statusPageGroup.viewMode).toBe(
|
|
1416
|
+
StatusPageGroupViewMode.Grid,
|
|
1417
|
+
);
|
|
1418
|
+
expect(rows[0]!.statusPageGroup.description).toBe(
|
|
1419
|
+
"The whole corporate unit",
|
|
1420
|
+
);
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
/*
|
|
1424
|
+
* Callers hold the expanded set in React state and hand the same Set in on
|
|
1425
|
+
* every render; a util that wrote into it would be mutating state.
|
|
1426
|
+
*/
|
|
1427
|
+
test("the expanded set handed in is never written to", () => {
|
|
1428
|
+
const statusPageGroups: Array<StatusPageGroup> = makeHierarchy();
|
|
1429
|
+
const expanded: Set<string> = new Set<string>([CORPORATE.toString()]);
|
|
1430
|
+
|
|
1431
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1432
|
+
statusPageGroups: statusPageGroups,
|
|
1433
|
+
expandedGroupIds: expanded,
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
expect(Array.from(expanded)).toEqual([CORPORATE.toString()]);
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
test("the group list handed in is never reordered in place", () => {
|
|
1440
|
+
const statusPageGroups: Array<StatusPageGroup> = [
|
|
1441
|
+
makeGroup({ id: STANDALONE, name: "Standalone", order: 9 }),
|
|
1442
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
1443
|
+
];
|
|
1444
|
+
|
|
1445
|
+
StatusPageGroupHierarchyViewUtil.getRows({
|
|
1446
|
+
statusPageGroups: statusPageGroups,
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
expect(statusPageGroups[0]!.name).toBe("Standalone");
|
|
1450
|
+
});
|
|
1451
|
+
});
|
|
1452
|
+
});
|