@oneuptime/common 11.7.2 → 11.7.4
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/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
- package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Index.ts +5 -0
- package/Models/DatabaseModels/LogDropFilter.ts +89 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
- package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
- package/Server/API/AlertAPI.ts +32 -17
- package/Server/API/BaseAPI.ts +31 -2
- package/Server/API/BaseAnalyticsAPI.ts +36 -2
- package/Server/API/CommonAPI.ts +57 -0
- package/Server/API/IncidentAPI.ts +60 -33
- package/Server/API/IncidentEpisodeAPI.ts +31 -16
- package/Server/API/MonitorAPI.ts +30 -0
- package/Server/API/MonitorTemplateAPI.ts +8 -0
- package/Server/API/ProbeAPI.ts +196 -15
- package/Server/API/ProjectAPI.ts +79 -1
- package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
- package/Server/API/StatusPageAPI.ts +74 -37
- package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Services/AlertEpisodeService.ts +47 -0
- package/Server/Services/AlertService.ts +125 -0
- package/Server/Services/BillingService.ts +98 -0
- package/Server/Services/DatabaseService.ts +184 -20
- package/Server/Services/IncidentEpisodeService.ts +47 -0
- package/Server/Services/IncidentService.ts +123 -0
- package/Server/Services/IncidentTemplateService.ts +123 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LogDropFilterService.ts +133 -0
- package/Server/Services/MonitorProbeService.ts +94 -4
- package/Server/Services/MonitorService.ts +48 -43
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
- package/Server/Services/ProbeService.ts +66 -0
- package/Server/Services/ProjectService.ts +131 -7
- package/Server/Services/RecommendationDismissalService.ts +52 -0
- package/Server/Services/RunbookAgentJobService.ts +68 -7
- package/Server/Services/ScheduledMaintenanceService.ts +103 -0
- package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
- package/Server/Services/StatusPageGroupService.ts +186 -0
- package/Server/Services/TraceDropFilterService.ts +133 -0
- package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
- package/Server/Types/Workflow/Components/API/Get.ts +12 -0
- package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
- package/Server/Types/Workflow/Components/API/Post.ts +12 -0
- package/Server/Types/Workflow/Components/API/Put.ts +13 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
- package/Server/Utils/DropFilterValidation.ts +127 -0
- package/Server/Utils/Errors.ts +5 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
- package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
- package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
- package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
- package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
- package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
- package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
- package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
- package/Tests/Server/API/ProbeAPI.test.ts +422 -53
- package/Tests/Server/API/ProjectAPI.test.ts +180 -0
- package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
- package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
- package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
- package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
- package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
- package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
- package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
- package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
- package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
- package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
- package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
- package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
- package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
- package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
- package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
- package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
- package/Tests/Types/DateUserTimezone.test.ts +52 -0
- package/Tests/Types/JSONFunctions.test.ts +103 -1
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
- package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
- package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
- package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
- package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
- package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
- package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
- package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
- package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
- package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
- package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
- package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
- package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
- package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
- package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
- package/Tests/UI/Components/InfoCard.test.tsx +358 -0
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
- package/Tests/UI/Components/Modal.test.tsx +43 -5
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +999 -0
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
- package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
- package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
- package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
- package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
- package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
- package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
- package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
- package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
- package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
- package/Tests/Utils/UserPreferences.test.ts +563 -0
- package/Types/Date.ts +15 -6
- package/Types/JSONFunctions.ts +7 -2
- package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
- package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
- package/Types/Monitor/MonitorStep.ts +10 -1
- package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
- package/Types/Permission.ts +45 -0
- package/Types/Recommendation/RecommendationType.ts +20 -0
- package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
- package/Types/Runbook/RunbookStepTimeout.ts +202 -0
- package/Types/Telemetry/DropFilterSampling.ts +92 -0
- package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
- package/UI/Components/Accordion/Accordion.tsx +10 -2
- package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
- package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
- package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
- package/UI/Components/Error/PageError.tsx +0 -4
- package/UI/Components/ErrorBoundary.tsx +252 -15
- package/UI/Components/InfoCard/InfoCard.tsx +58 -1
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
- package/UI/Components/MasterPage/MasterPage.tsx +65 -0
- package/UI/Components/Modal/Modal.tsx +16 -60
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
- package/UI/Components/ModelTable/BaseModelTable.tsx +503 -71
- package/UI/Components/ModelTable/Column.ts +17 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +523 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
- package/UI/Components/ModelTable/TableView.tsx +24 -2
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
- package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
- package/UI/Components/SideMenu/SideMenu.tsx +24 -4
- package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
- package/UI/Components/Table/Table.tsx +14 -1
- package/UI/Components/Table/TableRow.tsx +180 -175
- package/UI/Components/Table/Types/Column.ts +2 -0
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
- package/UI/Utils/DropdownAlignment.ts +162 -0
- package/UI/Utils/TableFilterUrlState.ts +59 -20
- package/Utils/API.ts +106 -2
- package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
- package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
- package/Utils/StatusPage/GroupTree.ts +307 -0
- package/Utils/StatusPage/ResourceUptime.ts +90 -17
- package/Utils/UserPreferences.ts +53 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
- package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
- package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TableView.js +40 -0
- package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/API/AlertAPI.js +22 -13
- package/build/dist/Server/API/AlertAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +22 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +42 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +43 -26
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
- package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
- package/build/dist/Server/API/MonitorAPI.js +23 -0
- package/build/dist/Server/API/MonitorAPI.js.map +1 -1
- package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
- package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
- package/build/dist/Server/API/ProbeAPI.js +131 -15
- package/build/dist/Server/API/ProbeAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectAPI.js +62 -3
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +61 -33
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
- package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
- package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +95 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +71 -0
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +140 -17
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +100 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
- package/build/dist/Server/Services/IncidentTemplateService.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/LogDropFilterService.js +93 -0
- package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +73 -5
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +34 -39
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +64 -0
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +103 -7
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
- package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
- package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
- package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
- package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
- package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
- package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
- package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
- package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
- package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
- package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
- package/build/dist/Server/Utils/Errors.js +3 -0
- package/build/dist/Server/Utils/Errors.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
- package/build/dist/Types/Date.js +14 -4
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +8 -3
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +6 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
- package/build/dist/Types/Permission.js +40 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
- package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
- package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
- package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
- package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
- package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
- package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
- package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
- package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
- package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Error/PageError.js +1 -3
- package/build/dist/UI/Components/Error/PageError.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +145 -11
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +4 -32
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +308 -40
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +230 -0
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
- package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
- package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +15 -2
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +11 -6
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
- package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
- package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
- package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
- package/build/dist/Utils/API.js +39 -2
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
- package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
- package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
- package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
- package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/UserPreferences.js +33 -0
- package/build/dist/Utils/UserPreferences.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
import StatusPageGroupNestingLayoutUtil, {
|
|
2
|
+
StatusPageGroupRollupKind,
|
|
3
|
+
} from "../../../Utils/StatusPage/GroupNestingLayout";
|
|
4
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
5
|
+
import StatusPageGroup from "../../../Models/DatabaseModels/StatusPageGroup";
|
|
6
|
+
import StatusPageResource from "../../../Models/DatabaseModels/StatusPageResource";
|
|
7
|
+
import StatusPageGroupViewMode from "../../../Types/StatusPage/StatusPageGroupViewMode";
|
|
8
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
9
|
+
import { describe, expect, test } from "@jest/globals";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Every measurement below is taken from the class strings the util actually
|
|
13
|
+
* emits, not from a number written down beside them. A number kept alongside the
|
|
14
|
+
* classes can drift from them silently - and did: an earlier version of these
|
|
15
|
+
* tests passed unchanged with the real indent inflated six fold.
|
|
16
|
+
*
|
|
17
|
+
* Tailwind's default scale: a spacing step is 0.25rem, so `pl-5` is 20px and
|
|
18
|
+
* `ml-2.5` is 10px. Arbitrary values (`pl-[34px]`) are read literally. Where a
|
|
19
|
+
* class has an `sm:` variant, the variant is the wide viewport value.
|
|
20
|
+
*/
|
|
21
|
+
const TAILWIND_FONT_SIZE_PX: Record<string, number> = {
|
|
22
|
+
"text-xs": 12,
|
|
23
|
+
"text-sm": 14,
|
|
24
|
+
"text-base": 16,
|
|
25
|
+
"text-lg": 18,
|
|
26
|
+
"text-xl": 20,
|
|
27
|
+
"text-2xl": 24,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function tokensOf(className: string): Array<string> {
|
|
31
|
+
return className.split(/\s+/).filter((token: string) => {
|
|
32
|
+
return token.length > 0;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* The value that wins at the wide viewport: the `sm:` variant when there is one,
|
|
38
|
+
* otherwise the unprefixed class.
|
|
39
|
+
*/
|
|
40
|
+
function wideValueOf(
|
|
41
|
+
className: string,
|
|
42
|
+
prefix: string,
|
|
43
|
+
read: (value: string) => number | null,
|
|
44
|
+
): number | null {
|
|
45
|
+
let unprefixed: number | null = null;
|
|
46
|
+
let atSm: number | null = null;
|
|
47
|
+
|
|
48
|
+
for (const token of tokensOf(className)) {
|
|
49
|
+
if (token.startsWith(`sm:${prefix}`)) {
|
|
50
|
+
atSm = read(token.substring(`sm:${prefix}`.length));
|
|
51
|
+
} else if (token.startsWith(prefix)) {
|
|
52
|
+
unprefixed = read(token.substring(prefix.length));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return atSm === null ? unprefixed : atSm;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readSpacing(value: string): number | null {
|
|
60
|
+
const arbitrary: RegExpMatchArray | null = value.match(
|
|
61
|
+
/^\[(\d+(?:\.\d+)?)px]$/,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (arbitrary) {
|
|
65
|
+
return Number(arbitrary[1]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const step: number = Number(value);
|
|
69
|
+
|
|
70
|
+
return Number.isFinite(step) ? step * 4 : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* The px value of `pl-*` / `ml-*` / `pt-*` in a class string. */
|
|
74
|
+
function spacingPx(className: string, prefix: string): number {
|
|
75
|
+
return wideValueOf(className, prefix, readSpacing) || 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* The px font size a class string renders at, at the wide viewport. */
|
|
79
|
+
function fontSizePx(className: string): number {
|
|
80
|
+
for (const token of tokensOf(className)) {
|
|
81
|
+
const bare: string = token.startsWith("sm:") ? token.substring(3) : token;
|
|
82
|
+
|
|
83
|
+
if (TAILWIND_FONT_SIZE_PX[bare] !== undefined && token.startsWith("sm:")) {
|
|
84
|
+
return TAILWIND_FONT_SIZE_PX[bare]!;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const token of tokensOf(className)) {
|
|
89
|
+
if (TAILWIND_FONT_SIZE_PX[token] !== undefined) {
|
|
90
|
+
return TAILWIND_FONT_SIZE_PX[token]!;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
* How far one nesting step moves its contents right: the rail's own offset, the
|
|
99
|
+
* 1px rail itself, and the padding that clears it. Read off the body class, so
|
|
100
|
+
* changing the class changes the number.
|
|
101
|
+
*/
|
|
102
|
+
function nestingStepPx(depth: number): number {
|
|
103
|
+
const bodyClassName: string =
|
|
104
|
+
StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: depth });
|
|
105
|
+
|
|
106
|
+
if (!bodyClassName.includes("border-l")) {
|
|
107
|
+
return spacingPx(bodyClassName, "ml-") + spacingPx(bodyClassName, "pl-");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return spacingPx(bodyClassName, "ml-") + 1 + spacingPx(bodyClassName, "pl-");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Where a group title's text starts: the chevron box plus the gap after it. */
|
|
114
|
+
function titleTextOffsetPx(): number {
|
|
115
|
+
return (
|
|
116
|
+
spacingPx(StatusPageGroupNestingLayoutUtil.getChevronBoxClassName(), "w-") +
|
|
117
|
+
spacingPx(StatusPageGroupNestingLayoutUtil.getHeaderRowClassName(), "gap-")
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/*
|
|
122
|
+
* Contract under test - how the overview page draws nested resource groups.
|
|
123
|
+
*
|
|
124
|
+
* The nesting used to be drawn as a card inside a card inside a card, and at
|
|
125
|
+
* three levels deep that puts four borders around one uptime bar, squeezes the
|
|
126
|
+
* bars off the right of a phone, and renders a leaf resource larger than the
|
|
127
|
+
* group heading above it. Every rule below is one of those failures turned into
|
|
128
|
+
* an assertion:
|
|
129
|
+
*
|
|
130
|
+
* - only the top level is a card; nothing nested adds a surface,
|
|
131
|
+
* - a group title is never smaller than the resources inside it,
|
|
132
|
+
* - the indent stops growing before a deep tree runs out of width,
|
|
133
|
+
* - and no shape of bad data (a negative depth, a parent pointer cycle) may
|
|
134
|
+
* break the layout or hang the walk.
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
const CORPORATE: ObjectID = new ObjectID(
|
|
138
|
+
"11111111-1111-4111-8111-111111111111",
|
|
139
|
+
);
|
|
140
|
+
const REGION_ONE: ObjectID = new ObjectID(
|
|
141
|
+
"22222222-2222-4222-8222-222222222222",
|
|
142
|
+
);
|
|
143
|
+
const REGION_TWO: ObjectID = new ObjectID(
|
|
144
|
+
"33333333-3333-4333-8333-333333333333",
|
|
145
|
+
);
|
|
146
|
+
const MARKET: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
147
|
+
const UNIT: ObjectID = new ObjectID("55555555-5555-4555-8555-555555555555");
|
|
148
|
+
|
|
149
|
+
/* Depths well past anything the write path allows. */
|
|
150
|
+
const ABSURD_DEPTHS: Array<number> = [5, 6, 9, 10, 11, 25, 100, 1000];
|
|
151
|
+
|
|
152
|
+
function makeGroup(data: {
|
|
153
|
+
id: ObjectID;
|
|
154
|
+
name: string;
|
|
155
|
+
parentId?: ObjectID | undefined;
|
|
156
|
+
order?: number | undefined;
|
|
157
|
+
viewMode?: StatusPageGroupViewMode | undefined;
|
|
158
|
+
}): StatusPageGroup {
|
|
159
|
+
const group: StatusPageGroup = new StatusPageGroup();
|
|
160
|
+
group._id = data.id.toString();
|
|
161
|
+
group.name = data.name;
|
|
162
|
+
|
|
163
|
+
if (data.parentId) {
|
|
164
|
+
group.parentStatusPageGroupId = data.parentId;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (data.order !== undefined) {
|
|
168
|
+
group.order = data.order;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (data.viewMode) {
|
|
172
|
+
group.viewMode = data.viewMode;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return group;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function makeResource(data: {
|
|
179
|
+
id: ObjectID;
|
|
180
|
+
name: string;
|
|
181
|
+
groupId?: ObjectID | undefined;
|
|
182
|
+
showStatusHistoryChart?: boolean | undefined;
|
|
183
|
+
withMonitor?: boolean | undefined;
|
|
184
|
+
monitorGroupId?: ObjectID | undefined;
|
|
185
|
+
}): StatusPageResource {
|
|
186
|
+
const resource: StatusPageResource = new StatusPageResource();
|
|
187
|
+
resource._id = data.id.toString();
|
|
188
|
+
resource.displayName = data.name;
|
|
189
|
+
|
|
190
|
+
if (data.groupId) {
|
|
191
|
+
resource.statusPageGroupId = data.groupId;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (data.showStatusHistoryChart !== undefined) {
|
|
195
|
+
resource.showStatusHistoryChart = data.showStatusHistoryChart;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* The page only draws a resource that resolves to a monitor or a monitor
|
|
200
|
+
* group, so a fixture without one is how "nothing to draw" is expressed.
|
|
201
|
+
*/
|
|
202
|
+
if (data.withMonitor !== false && !data.monitorGroupId) {
|
|
203
|
+
const monitor: Monitor = new Monitor();
|
|
204
|
+
monitor._id = data.id.toString();
|
|
205
|
+
resource.monitor = monitor;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (data.monitorGroupId) {
|
|
209
|
+
resource.monitorGroupId = data.monitorGroupId;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return resource;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/*
|
|
216
|
+
* Corporate
|
|
217
|
+
* Region 1000
|
|
218
|
+
* Market 1001
|
|
219
|
+
* Unit 0152
|
|
220
|
+
* Region 2000
|
|
221
|
+
*/
|
|
222
|
+
function makeHierarchy(): Array<StatusPageGroup> {
|
|
223
|
+
return [
|
|
224
|
+
makeGroup({ id: CORPORATE, name: "Corporate", order: 1 }),
|
|
225
|
+
makeGroup({
|
|
226
|
+
id: REGION_ONE,
|
|
227
|
+
name: "Region 1000",
|
|
228
|
+
parentId: CORPORATE,
|
|
229
|
+
order: 2,
|
|
230
|
+
}),
|
|
231
|
+
makeGroup({
|
|
232
|
+
id: MARKET,
|
|
233
|
+
name: "Market 1001",
|
|
234
|
+
parentId: REGION_ONE,
|
|
235
|
+
order: 3,
|
|
236
|
+
}),
|
|
237
|
+
makeGroup({ id: UNIT, name: "Unit 0152", parentId: MARKET, order: 4 }),
|
|
238
|
+
makeGroup({
|
|
239
|
+
id: REGION_TWO,
|
|
240
|
+
name: "Region 2000",
|
|
241
|
+
parentId: CORPORATE,
|
|
242
|
+
order: 5,
|
|
243
|
+
}),
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
describe("StatusPageGroupNestingLayoutUtil", () => {
|
|
248
|
+
describe("getVisualDepth", () => {
|
|
249
|
+
test("passes a normal depth straight through", () => {
|
|
250
|
+
expect(
|
|
251
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 0 }),
|
|
252
|
+
).toBe(0);
|
|
253
|
+
expect(
|
|
254
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 1 }),
|
|
255
|
+
).toBe(1);
|
|
256
|
+
expect(
|
|
257
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 3 }),
|
|
258
|
+
).toBe(3);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("clamps at MaxIndentedDepth so a deep tree keeps its width", () => {
|
|
262
|
+
for (const depth of ABSURD_DEPTHS) {
|
|
263
|
+
expect(
|
|
264
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: depth }),
|
|
265
|
+
).toBe(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
/*
|
|
270
|
+
* Depth comes from a recursive render today, but a bad number must degrade
|
|
271
|
+
* to the top level rather than produce a class like `pl-NaN`.
|
|
272
|
+
*/
|
|
273
|
+
test.each([[-1], [-10], [Number.NEGATIVE_INFINITY], [Number.NaN]])(
|
|
274
|
+
"treats %p as the top level",
|
|
275
|
+
(depth: number) => {
|
|
276
|
+
expect(
|
|
277
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: depth }),
|
|
278
|
+
).toBe(0);
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
test("treats positive infinity as the top level rather than returning it", () => {
|
|
283
|
+
expect(
|
|
284
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({
|
|
285
|
+
depth: Number.POSITIVE_INFINITY,
|
|
286
|
+
}),
|
|
287
|
+
).toBe(0);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("floors a fractional depth", () => {
|
|
291
|
+
expect(
|
|
292
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 2.9 }),
|
|
293
|
+
).toBe(2);
|
|
294
|
+
expect(
|
|
295
|
+
StatusPageGroupNestingLayoutUtil.getVisualDepth({ depth: 0.9 }),
|
|
296
|
+
).toBe(0);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("never decreases as depth increases", () => {
|
|
300
|
+
let previous: number = StatusPageGroupNestingLayoutUtil.getVisualDepth({
|
|
301
|
+
depth: 0,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
for (let depth: number = 1; depth <= 30; depth++) {
|
|
305
|
+
const current: number = StatusPageGroupNestingLayoutUtil.getVisualDepth(
|
|
306
|
+
{
|
|
307
|
+
depth: depth,
|
|
308
|
+
},
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(current).toBeGreaterThanOrEqual(previous);
|
|
312
|
+
previous = current;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe("isRootLevel", () => {
|
|
318
|
+
test("only the top level, and anything that degrades to it, is root", () => {
|
|
319
|
+
expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: 0 })).toBe(
|
|
320
|
+
true,
|
|
321
|
+
);
|
|
322
|
+
expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: -5 })).toBe(
|
|
323
|
+
true,
|
|
324
|
+
);
|
|
325
|
+
expect(
|
|
326
|
+
StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: Number.NaN }),
|
|
327
|
+
).toBe(true);
|
|
328
|
+
expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: 1 })).toBe(
|
|
329
|
+
false,
|
|
330
|
+
);
|
|
331
|
+
expect(StatusPageGroupNestingLayoutUtil.isRootLevel({ depth: 12 })).toBe(
|
|
332
|
+
false,
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("title sizing", () => {
|
|
338
|
+
/*
|
|
339
|
+
* The regression this guards: resource names rendered at text-lg while a
|
|
340
|
+
* nested group heading rendered at text-sm, so the leaf looked more
|
|
341
|
+
* important than the branch it hung off. Both sides are measured off the
|
|
342
|
+
* class strings that render, so shrinking either one fails here.
|
|
343
|
+
*/
|
|
344
|
+
test("a group title is never smaller than the resources inside it", () => {
|
|
345
|
+
const resourcePx: number = fontSizePx(
|
|
346
|
+
StatusPageGroupNestingLayoutUtil.getResourceTitleClassName(),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect(resourcePx).toBeGreaterThan(0);
|
|
350
|
+
|
|
351
|
+
for (let depth: number = 0; depth <= 30; depth++) {
|
|
352
|
+
expect(
|
|
353
|
+
fontSizePx(
|
|
354
|
+
StatusPageGroupNestingLayoutUtil.getTitleClassName({
|
|
355
|
+
depth: depth,
|
|
356
|
+
}),
|
|
357
|
+
),
|
|
358
|
+
).toBeGreaterThanOrEqual(resourcePx);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("title size never grows as the hierarchy gets deeper", () => {
|
|
363
|
+
let previous: number = fontSizePx(
|
|
364
|
+
StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: 0 }),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
expect(previous).toBeGreaterThan(0);
|
|
368
|
+
|
|
369
|
+
for (let depth: number = 1; depth <= 30; depth++) {
|
|
370
|
+
const current: number = fontSizePx(
|
|
371
|
+
StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: depth }),
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
expect(current).toBeGreaterThan(0);
|
|
375
|
+
expect(current).toBeLessThanOrEqual(previous);
|
|
376
|
+
previous = current;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("the top level outranks everything below it", () => {
|
|
381
|
+
expect(
|
|
382
|
+
fontSizePx(
|
|
383
|
+
StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: 0 }),
|
|
384
|
+
),
|
|
385
|
+
).toBeGreaterThan(
|
|
386
|
+
fontSizePx(
|
|
387
|
+
StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: 1 }),
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
/*
|
|
393
|
+
* A group title is heavier than a resource name at the same size - that is
|
|
394
|
+
* what separates a heading from a reading when the rail is the only other
|
|
395
|
+
* cue.
|
|
396
|
+
*/
|
|
397
|
+
test("a group title is heavier than a resource name", () => {
|
|
398
|
+
expect(
|
|
399
|
+
StatusPageGroupNestingLayoutUtil.getResourceTitleClassName(),
|
|
400
|
+
).toContain("font-medium");
|
|
401
|
+
|
|
402
|
+
for (let depth: number = 0; depth <= 12; depth++) {
|
|
403
|
+
const className: string =
|
|
404
|
+
StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: depth });
|
|
405
|
+
|
|
406
|
+
expect(className).toContain("font-semibold");
|
|
407
|
+
expect(className).toContain("text-gray-900");
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("nested titles are all styled the same - depth is shown by the rail", () => {
|
|
412
|
+
const atOne: string = StatusPageGroupNestingLayoutUtil.getTitleClassName({
|
|
413
|
+
depth: 1,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
for (let depth: number = 2; depth <= 12; depth++) {
|
|
417
|
+
expect(
|
|
418
|
+
StatusPageGroupNestingLayoutUtil.getTitleClassName({ depth: depth }),
|
|
419
|
+
).toBe(atOne);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
describe("getContainerClassName", () => {
|
|
425
|
+
test("the top level is a card", () => {
|
|
426
|
+
const className: string =
|
|
427
|
+
StatusPageGroupNestingLayoutUtil.getContainerClassName({ depth: 0 });
|
|
428
|
+
|
|
429
|
+
expect(className).toContain("bg-white");
|
|
430
|
+
expect(className).toContain("rounded-xl");
|
|
431
|
+
expect(className).toContain("shadow");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
/*
|
|
435
|
+
* The headline regression: a nested group must not introduce a surface of
|
|
436
|
+
* its own. Asserted as the absence of each surface class rather than as an
|
|
437
|
+
* empty string, so a future non-surface class (`relative`, say) does not
|
|
438
|
+
* fail the test while a returning card still does.
|
|
439
|
+
*/
|
|
440
|
+
test("nothing nested adds a card, a border or a background", () => {
|
|
441
|
+
for (let depth: number = 1; depth <= 12; depth++) {
|
|
442
|
+
const className: string =
|
|
443
|
+
StatusPageGroupNestingLayoutUtil.getContainerClassName({
|
|
444
|
+
depth: depth,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
expect(className).not.toMatch(/\bbg-/);
|
|
448
|
+
expect(className).not.toMatch(/\bborder/);
|
|
449
|
+
expect(className).not.toMatch(/\brounded/);
|
|
450
|
+
expect(className).not.toMatch(/\bshadow/);
|
|
451
|
+
expect(className).not.toMatch(/\bp[xytblr]?-/);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe("getBodyClassName", () => {
|
|
457
|
+
test("the top level body has no rail - the card edge is the boundary", () => {
|
|
458
|
+
const className: string =
|
|
459
|
+
StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: 0 });
|
|
460
|
+
|
|
461
|
+
expect(className).not.toContain("border-l");
|
|
462
|
+
expect(className).not.toContain("pl-");
|
|
463
|
+
expect(StatusPageGroupNestingLayoutUtil.showRail({ depth: 0 })).toBe(
|
|
464
|
+
false,
|
|
465
|
+
);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("every nested body hangs off exactly one hairline rail", () => {
|
|
469
|
+
for (let depth: number = 1; depth <= 12; depth++) {
|
|
470
|
+
const className: string =
|
|
471
|
+
StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: depth });
|
|
472
|
+
|
|
473
|
+
expect(className).toContain("border-l");
|
|
474
|
+
// border-l, not border-l-2: one hairline, however deep it goes.
|
|
475
|
+
expect(className).not.toContain("border-l-2");
|
|
476
|
+
expect(className).toContain("border-gray-200");
|
|
477
|
+
expect(className).toMatch(/pl-/);
|
|
478
|
+
expect(
|
|
479
|
+
StatusPageGroupNestingLayoutUtil.showRail({ depth: depth }),
|
|
480
|
+
).toBe(true);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("the indent tightens past MaxIndentedDepth instead of growing", () => {
|
|
485
|
+
expect(
|
|
486
|
+
nestingStepPx(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth),
|
|
487
|
+
).toBeLessThan(nestingStepPx(1));
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
/*
|
|
491
|
+
* A sub group is meant to read as hanging off its parent's title, not off
|
|
492
|
+
* the parent's chevron, so one nesting step moves its contents to where the
|
|
493
|
+
* parent's title text starts. Both sides are measured off the classes that
|
|
494
|
+
* render: the chevron box and header gap on one side, the body's margin,
|
|
495
|
+
* rail and padding on the other.
|
|
496
|
+
*/
|
|
497
|
+
test("a nesting step lands a sub group's contents at its parent's title", () => {
|
|
498
|
+
const offset: number = titleTextOffsetPx();
|
|
499
|
+
|
|
500
|
+
expect(offset).toBeGreaterThan(0);
|
|
501
|
+
expect(nestingStepPx(1)).toBeGreaterThan(offset - 3);
|
|
502
|
+
expect(nestingStepPx(1)).toBeLessThanOrEqual(offset);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test("the top level costs no indent at all", () => {
|
|
506
|
+
expect(nestingStepPx(0)).toBe(0);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("every nesting step costs something, so the rails stay apart", () => {
|
|
510
|
+
for (let depth: number = 1; depth <= 12; depth++) {
|
|
511
|
+
expect(nestingStepPx(depth)).toBeGreaterThan(0);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("the indent never grows as the hierarchy gets deeper", () => {
|
|
516
|
+
let previous: number = nestingStepPx(1);
|
|
517
|
+
|
|
518
|
+
for (let depth: number = 2; depth <= 30; depth++) {
|
|
519
|
+
const current: number = nestingStepPx(depth);
|
|
520
|
+
|
|
521
|
+
expect(current).toBeLessThanOrEqual(previous);
|
|
522
|
+
previous = current;
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
/*
|
|
527
|
+
* The write path caps nesting at StatusPageGroupTreeUtil.MaxNestingDepth
|
|
528
|
+
* (10), so a fully nested resource is indented by the sum of ten steps. The
|
|
529
|
+
* status page content column is max-w-5xl minus padding, about 984px, and
|
|
530
|
+
* the uptime bars have to stay readable inside what is left.
|
|
531
|
+
*/
|
|
532
|
+
test("the deepest nesting the write path allows costs a quarter of the column", () => {
|
|
533
|
+
let total: number = 0;
|
|
534
|
+
|
|
535
|
+
for (let depth: number = 1; depth <= 10; depth++) {
|
|
536
|
+
total += nestingStepPx(depth);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
expect(total).toBeGreaterThan(0);
|
|
540
|
+
expect(total).toBeLessThan(250);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
/* Every step past the cap has to be cheap, or depth 20 runs off the page. */
|
|
544
|
+
test("a step past the cap costs no more than 20px", () => {
|
|
545
|
+
expect(
|
|
546
|
+
nestingStepPx(StatusPageGroupNestingLayoutUtil.MaxIndentedDepth + 5),
|
|
547
|
+
).toBeLessThanOrEqual(20);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("the rail is placed under its parent's chevron, not flush left", () => {
|
|
551
|
+
expect(
|
|
552
|
+
spacingPx(
|
|
553
|
+
StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: 1 }),
|
|
554
|
+
"ml-",
|
|
555
|
+
),
|
|
556
|
+
).toBeGreaterThan(0);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("depths beyond the cap all render the same tightened indent", () => {
|
|
560
|
+
const atCap: string = StatusPageGroupNestingLayoutUtil.getBodyClassName({
|
|
561
|
+
depth: StatusPageGroupNestingLayoutUtil.MaxIndentedDepth,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
for (const depth of ABSURD_DEPTHS) {
|
|
565
|
+
expect(
|
|
566
|
+
StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: depth }),
|
|
567
|
+
).toBe(atCap);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("a bad depth falls back to the top level body", () => {
|
|
572
|
+
expect(
|
|
573
|
+
StatusPageGroupNestingLayoutUtil.getBodyClassName({
|
|
574
|
+
depth: Number.NaN,
|
|
575
|
+
}),
|
|
576
|
+
).toBe(StatusPageGroupNestingLayoutUtil.getBodyClassName({ depth: 0 }));
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
describe("getHeaderClassName", () => {
|
|
581
|
+
test("the header is a focusable control that pushes its two ends apart", () => {
|
|
582
|
+
for (let depth: number = 0; depth <= 12; depth++) {
|
|
583
|
+
const className: string =
|
|
584
|
+
StatusPageGroupNestingLayoutUtil.getHeaderClassName({ depth: depth });
|
|
585
|
+
|
|
586
|
+
expect(tokensOf(className)).toContain("flex");
|
|
587
|
+
expect(className).toContain("justify-between");
|
|
588
|
+
expect(className).toContain("focus-visible:ring-2");
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
/*
|
|
593
|
+
* The hover background bleeds to the edge of the content column via equal
|
|
594
|
+
* and opposite margin and padding, and the width has to pay for both margins
|
|
595
|
+
* or the box is over-constrained: the browser then drops margin-right (CSS
|
|
596
|
+
* 2.1 10.3.3) and every rollup lands 16px short of the card edge the resource
|
|
597
|
+
* readings sit on. `w-full` is exactly that mistake.
|
|
598
|
+
*/
|
|
599
|
+
test("the bleed is symmetric and paid for, so the rollup lines up with the readings below", () => {
|
|
600
|
+
for (let depth: number = 0; depth <= 12; depth++) {
|
|
601
|
+
const className: string =
|
|
602
|
+
StatusPageGroupNestingLayoutUtil.getHeaderClassName({ depth: depth });
|
|
603
|
+
|
|
604
|
+
const bleedPerSide: number = spacingPx(className, "-mx-");
|
|
605
|
+
|
|
606
|
+
expect(bleedPerSide).toBeGreaterThan(0);
|
|
607
|
+
expect(spacingPx(className, "px-")).toBe(bleedPerSide);
|
|
608
|
+
|
|
609
|
+
/* A button shrink wraps without a width, even as a flex container. */
|
|
610
|
+
expect(tokensOf(className)).not.toContain("w-full");
|
|
611
|
+
expect(className).toContain(
|
|
612
|
+
`w-[calc(100%+${(bleedPerSide * 2) / 16}rem)]`,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe("spacing and chrome tokens", () => {
|
|
619
|
+
/*
|
|
620
|
+
* Measured, not restated: the description's indent has to equal the chevron
|
|
621
|
+
* box plus the gap after it, both of which are classes on the header.
|
|
622
|
+
*/
|
|
623
|
+
test("the description lines up with the title, not the chevron", () => {
|
|
624
|
+
const offset: number = titleTextOffsetPx();
|
|
625
|
+
|
|
626
|
+
expect(offset).toBeGreaterThan(0);
|
|
627
|
+
expect(
|
|
628
|
+
spacingPx(
|
|
629
|
+
StatusPageGroupNestingLayoutUtil.getDescriptionClassName(),
|
|
630
|
+
"pl-",
|
|
631
|
+
),
|
|
632
|
+
).toBe(offset);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("the ungrouped card is the same surface as a top level group", () => {
|
|
636
|
+
const card: string =
|
|
637
|
+
StatusPageGroupNestingLayoutUtil.getUngroupedResourcesCardClassName();
|
|
638
|
+
|
|
639
|
+
expect(card).toContain("bg-white");
|
|
640
|
+
expect(card).toContain("rounded-xl");
|
|
641
|
+
expect(card).toContain("shadow");
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
/*
|
|
645
|
+
* A rollup and a resource reading are the same kind of number, so they are
|
|
646
|
+
* set at the same size - measured off both class strings rather than
|
|
647
|
+
* asserting the literals one of them happens to contain.
|
|
648
|
+
*/
|
|
649
|
+
test("a rollup is sized like the resource readings below it", () => {
|
|
650
|
+
expect(
|
|
651
|
+
fontSizePx(StatusPageGroupNestingLayoutUtil.getRollupClassName()),
|
|
652
|
+
).toBe(
|
|
653
|
+
fontSizePx(
|
|
654
|
+
StatusPageGroupNestingLayoutUtil.getResourceTitleClassName(),
|
|
655
|
+
),
|
|
656
|
+
);
|
|
657
|
+
expect(StatusPageGroupNestingLayoutUtil.getRollupClassName()).toContain(
|
|
658
|
+
"flex-shrink-0",
|
|
659
|
+
);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test("a rollup is marked with a dot so it does not read as a resource's own", () => {
|
|
663
|
+
const dot: string =
|
|
664
|
+
StatusPageGroupNestingLayoutUtil.getRollupDotClassName();
|
|
665
|
+
|
|
666
|
+
expect(dot).toContain("rounded-full");
|
|
667
|
+
expect(dot).toContain("flex-shrink-0");
|
|
668
|
+
expect(
|
|
669
|
+
StatusPageGroupNestingLayoutUtil.getRollupLabelClassName(),
|
|
670
|
+
).toContain("tabular-nums");
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("the sub group list is spaced at every depth", () => {
|
|
674
|
+
for (let depth: number = 0; depth <= 12; depth++) {
|
|
675
|
+
expect(
|
|
676
|
+
StatusPageGroupNestingLayoutUtil.getSubGroupListClassName({
|
|
677
|
+
depth: depth,
|
|
678
|
+
}),
|
|
679
|
+
).toMatch(/space-y-/);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
/*
|
|
684
|
+
* justify-between is inert without flex, and the axis exists precisely to
|
|
685
|
+
* push "90 days ago" and "Today" to opposite ends.
|
|
686
|
+
*/
|
|
687
|
+
test("the time axis pushes its two ends apart", () => {
|
|
688
|
+
const axis: string =
|
|
689
|
+
StatusPageGroupNestingLayoutUtil.getTimeAxisClassName();
|
|
690
|
+
|
|
691
|
+
expect(tokensOf(axis)).toContain("flex");
|
|
692
|
+
expect(tokensOf(axis)).toContain("justify-between");
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("the resource list carries its own rhythm", () => {
|
|
696
|
+
expect(
|
|
697
|
+
StatusPageGroupNestingLayoutUtil.getResourceListClassName(),
|
|
698
|
+
).toMatch(/space-y-/);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test("the sub group count badge is a quiet pill", () => {
|
|
702
|
+
const badge: string =
|
|
703
|
+
StatusPageGroupNestingLayoutUtil.getSubGroupCountBadgeClassName();
|
|
704
|
+
|
|
705
|
+
expect(badge).toContain("rounded-full");
|
|
706
|
+
expect(badge).toContain("text-gray-500");
|
|
707
|
+
expect(badge).toContain("flex-shrink-0");
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
describe("getRollupKind", () => {
|
|
712
|
+
test("a healthy group configured for uptime shows its uptime percent", () => {
|
|
713
|
+
expect(
|
|
714
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
715
|
+
showUptimePercent: true,
|
|
716
|
+
showCurrentStatus: true,
|
|
717
|
+
isCurrentlyDown: false,
|
|
718
|
+
uptimePercent: 99.9,
|
|
719
|
+
}),
|
|
720
|
+
).toBe(StatusPageGroupRollupKind.UptimePercent);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
/*
|
|
724
|
+
* "99.9% uptime" next to a group that is down right now is not what a
|
|
725
|
+
* visitor came to read.
|
|
726
|
+
*/
|
|
727
|
+
test("a group that is down shows its status, not a percent", () => {
|
|
728
|
+
expect(
|
|
729
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
730
|
+
showUptimePercent: true,
|
|
731
|
+
showCurrentStatus: true,
|
|
732
|
+
isCurrentlyDown: true,
|
|
733
|
+
uptimePercent: 99.9,
|
|
734
|
+
}),
|
|
735
|
+
).toBe(StatusPageGroupRollupKind.CurrentStatus);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
/*
|
|
739
|
+
* A group with nothing under it has no uptime to average. Showing a zero
|
|
740
|
+
* would read as an outage.
|
|
741
|
+
*/
|
|
742
|
+
test("a group with no uptime to report shows nothing rather than a zero", () => {
|
|
743
|
+
expect(
|
|
744
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
745
|
+
showUptimePercent: true,
|
|
746
|
+
showCurrentStatus: true,
|
|
747
|
+
isCurrentlyDown: false,
|
|
748
|
+
uptimePercent: null,
|
|
749
|
+
}),
|
|
750
|
+
).toBe(StatusPageGroupRollupKind.None);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("a group configured for status only shows its status", () => {
|
|
754
|
+
expect(
|
|
755
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
756
|
+
showUptimePercent: false,
|
|
757
|
+
showCurrentStatus: true,
|
|
758
|
+
isCurrentlyDown: false,
|
|
759
|
+
uptimePercent: 100,
|
|
760
|
+
}),
|
|
761
|
+
).toBe(StatusPageGroupRollupKind.CurrentStatus);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test("a group configured for neither shows nothing", () => {
|
|
765
|
+
expect(
|
|
766
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
767
|
+
showUptimePercent: false,
|
|
768
|
+
showCurrentStatus: false,
|
|
769
|
+
isCurrentlyDown: false,
|
|
770
|
+
uptimePercent: 100,
|
|
771
|
+
}),
|
|
772
|
+
).toBe(StatusPageGroupRollupKind.None);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
test("a group that is down and shows no status shows nothing", () => {
|
|
776
|
+
expect(
|
|
777
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
778
|
+
showUptimePercent: true,
|
|
779
|
+
showCurrentStatus: false,
|
|
780
|
+
isCurrentlyDown: true,
|
|
781
|
+
uptimePercent: 99.9,
|
|
782
|
+
}),
|
|
783
|
+
).toBe(StatusPageGroupRollupKind.None);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
test("a zero percent uptime is still a percent, not nothing", () => {
|
|
787
|
+
expect(
|
|
788
|
+
StatusPageGroupNestingLayoutUtil.getRollupKind({
|
|
789
|
+
showUptimePercent: true,
|
|
790
|
+
showCurrentStatus: true,
|
|
791
|
+
isCurrentlyDown: false,
|
|
792
|
+
uptimePercent: 0,
|
|
793
|
+
}),
|
|
794
|
+
).toBe(StatusPageGroupRollupKind.UptimePercent);
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
describe("shouldShowSubGroupDivider", () => {
|
|
799
|
+
/*
|
|
800
|
+
* Without the line, a group's last resource reads as the first row of the
|
|
801
|
+
* first sub group below it.
|
|
802
|
+
*/
|
|
803
|
+
test("a line is drawn only between own resources and sub groups", () => {
|
|
804
|
+
expect(
|
|
805
|
+
StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
|
|
806
|
+
hasOwnResources: true,
|
|
807
|
+
subGroupCount: 1,
|
|
808
|
+
}),
|
|
809
|
+
).toBe(true);
|
|
810
|
+
expect(
|
|
811
|
+
StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
|
|
812
|
+
hasOwnResources: true,
|
|
813
|
+
subGroupCount: 0,
|
|
814
|
+
}),
|
|
815
|
+
).toBe(false);
|
|
816
|
+
expect(
|
|
817
|
+
StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
|
|
818
|
+
hasOwnResources: false,
|
|
819
|
+
subGroupCount: 3,
|
|
820
|
+
}),
|
|
821
|
+
).toBe(false);
|
|
822
|
+
expect(
|
|
823
|
+
StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
|
|
824
|
+
hasOwnResources: false,
|
|
825
|
+
subGroupCount: 0,
|
|
826
|
+
}),
|
|
827
|
+
).toBe(false);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
test("the divider is a hairline with padding, not a margin", () => {
|
|
831
|
+
const divider: string =
|
|
832
|
+
StatusPageGroupNestingLayoutUtil.getSubGroupDividerClassName();
|
|
833
|
+
|
|
834
|
+
expect(divider).toContain("border-t");
|
|
835
|
+
expect(divider).toMatch(/pt-/);
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
describe("shouldRenderOwnResources", () => {
|
|
840
|
+
/*
|
|
841
|
+
* A group that exists only to hold sub groups is not empty - the sub groups
|
|
842
|
+
* are its content - so it must not render the "no resources" message.
|
|
843
|
+
*/
|
|
844
|
+
test("a holder group with sub groups renders no resource list of its own", () => {
|
|
845
|
+
expect(
|
|
846
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
|
|
847
|
+
ownResourceCount: 0,
|
|
848
|
+
subGroupCount: 2,
|
|
849
|
+
}),
|
|
850
|
+
).toBe(false);
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
test("a group with resources always renders them", () => {
|
|
854
|
+
expect(
|
|
855
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
|
|
856
|
+
ownResourceCount: 1,
|
|
857
|
+
subGroupCount: 0,
|
|
858
|
+
}),
|
|
859
|
+
).toBe(true);
|
|
860
|
+
expect(
|
|
861
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
|
|
862
|
+
ownResourceCount: 4,
|
|
863
|
+
subGroupCount: 3,
|
|
864
|
+
}),
|
|
865
|
+
).toBe(true);
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
/*
|
|
869
|
+
* A truly empty group still renders the list, because that is where the
|
|
870
|
+
* "no resources added to this group" message lives.
|
|
871
|
+
*/
|
|
872
|
+
test("a group with neither resources nor sub groups still renders the list", () => {
|
|
873
|
+
expect(
|
|
874
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderOwnResources({
|
|
875
|
+
ownResourceCount: 0,
|
|
876
|
+
subGroupCount: 0,
|
|
877
|
+
}),
|
|
878
|
+
).toBe(true);
|
|
879
|
+
});
|
|
880
|
+
});
|
|
881
|
+
describe("shouldRenderTimeAxis", () => {
|
|
882
|
+
const CHARTED: ObjectID = new ObjectID(
|
|
883
|
+
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
884
|
+
);
|
|
885
|
+
const UNCHARTED: ObjectID = new ObjectID(
|
|
886
|
+
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
test("a group with a charted resource of its own draws one axis", () => {
|
|
890
|
+
const groups: Array<StatusPageGroup> = makeHierarchy();
|
|
891
|
+
|
|
892
|
+
expect(
|
|
893
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
894
|
+
statusPageGroup: groups[0]!,
|
|
895
|
+
statusPageResources: [
|
|
896
|
+
makeResource({
|
|
897
|
+
id: CHARTED,
|
|
898
|
+
name: "Corporate API",
|
|
899
|
+
groupId: CORPORATE,
|
|
900
|
+
showStatusHistoryChart: true,
|
|
901
|
+
}),
|
|
902
|
+
],
|
|
903
|
+
}),
|
|
904
|
+
).toBe(true);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
/*
|
|
908
|
+
* The axis belongs next to the bars it labels. A sub group's bars are
|
|
909
|
+
* indented past this group's, and a sub group may be collapsed, so this
|
|
910
|
+
* group's axis must not claim to describe them.
|
|
911
|
+
*/
|
|
912
|
+
test("a descendant's charted resource does not put an axis on the parent", () => {
|
|
913
|
+
const groups: Array<StatusPageGroup> = makeHierarchy();
|
|
914
|
+
|
|
915
|
+
expect(
|
|
916
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
917
|
+
statusPageGroup: groups[0]!,
|
|
918
|
+
statusPageResources: [
|
|
919
|
+
makeResource({
|
|
920
|
+
id: CHARTED,
|
|
921
|
+
name: "Unit 0152 API",
|
|
922
|
+
groupId: UNIT,
|
|
923
|
+
showStatusHistoryChart: true,
|
|
924
|
+
}),
|
|
925
|
+
],
|
|
926
|
+
}),
|
|
927
|
+
).toBe(false);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test("a group whose resources draw no history chart draws no axis", () => {
|
|
931
|
+
const groups: Array<StatusPageGroup> = makeHierarchy();
|
|
932
|
+
|
|
933
|
+
expect(
|
|
934
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
935
|
+
statusPageGroup: groups[0]!,
|
|
936
|
+
statusPageResources: [
|
|
937
|
+
makeResource({
|
|
938
|
+
id: UNCHARTED,
|
|
939
|
+
name: "Corporate API",
|
|
940
|
+
groupId: CORPORATE,
|
|
941
|
+
showStatusHistoryChart: false,
|
|
942
|
+
}),
|
|
943
|
+
],
|
|
944
|
+
}),
|
|
945
|
+
).toBe(false);
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
/*
|
|
949
|
+
* A grid group draws a matrix of cells rather than bars over a window, so a
|
|
950
|
+
* "90 days ago ... Today" axis would be labelling something that is not
|
|
951
|
+
* there.
|
|
952
|
+
*/
|
|
953
|
+
test("a grid view group draws no axis", () => {
|
|
954
|
+
const gridGroup: StatusPageGroup = makeGroup({
|
|
955
|
+
id: CORPORATE,
|
|
956
|
+
name: "Corporate",
|
|
957
|
+
order: 1,
|
|
958
|
+
viewMode: StatusPageGroupViewMode.Grid,
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
expect(
|
|
962
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
963
|
+
statusPageGroup: gridGroup,
|
|
964
|
+
statusPageResources: [
|
|
965
|
+
makeResource({
|
|
966
|
+
id: CHARTED,
|
|
967
|
+
name: "Corporate API",
|
|
968
|
+
groupId: CORPORATE,
|
|
969
|
+
showStatusHistoryChart: true,
|
|
970
|
+
}),
|
|
971
|
+
],
|
|
972
|
+
}),
|
|
973
|
+
).toBe(false);
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
test("a list view group draws one", () => {
|
|
977
|
+
const listGroup: StatusPageGroup = makeGroup({
|
|
978
|
+
id: CORPORATE,
|
|
979
|
+
name: "Corporate",
|
|
980
|
+
order: 1,
|
|
981
|
+
viewMode: StatusPageGroupViewMode.List,
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
expect(
|
|
985
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
986
|
+
statusPageGroup: listGroup,
|
|
987
|
+
statusPageResources: [
|
|
988
|
+
makeResource({
|
|
989
|
+
id: CHARTED,
|
|
990
|
+
name: "Corporate API",
|
|
991
|
+
groupId: CORPORATE,
|
|
992
|
+
showStatusHistoryChart: true,
|
|
993
|
+
}),
|
|
994
|
+
],
|
|
995
|
+
}),
|
|
996
|
+
).toBe(true);
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
test("the ungrouped block draws an axis for its own resources", () => {
|
|
1000
|
+
expect(
|
|
1001
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
1002
|
+
statusPageGroup: null,
|
|
1003
|
+
statusPageResources: [
|
|
1004
|
+
makeResource({
|
|
1005
|
+
id: CHARTED,
|
|
1006
|
+
name: "Ungrouped",
|
|
1007
|
+
showStatusHistoryChart: true,
|
|
1008
|
+
}),
|
|
1009
|
+
],
|
|
1010
|
+
}),
|
|
1011
|
+
).toBe(true);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
test("a grouped resource does not put an axis on the ungrouped block", () => {
|
|
1015
|
+
expect(
|
|
1016
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
1017
|
+
statusPageGroup: null,
|
|
1018
|
+
statusPageResources: [
|
|
1019
|
+
makeResource({
|
|
1020
|
+
id: CHARTED,
|
|
1021
|
+
name: "Corporate API",
|
|
1022
|
+
groupId: CORPORATE,
|
|
1023
|
+
showStatusHistoryChart: true,
|
|
1024
|
+
}),
|
|
1025
|
+
],
|
|
1026
|
+
}),
|
|
1027
|
+
).toBe(false);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
test("a resource with nothing to draw cannot bring an axis with it", () => {
|
|
1031
|
+
expect(
|
|
1032
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
1033
|
+
statusPageGroup: null,
|
|
1034
|
+
statusPageResources: [
|
|
1035
|
+
makeResource({
|
|
1036
|
+
id: CHARTED,
|
|
1037
|
+
name: "Ungrouped",
|
|
1038
|
+
showStatusHistoryChart: true,
|
|
1039
|
+
withMonitor: false,
|
|
1040
|
+
}),
|
|
1041
|
+
],
|
|
1042
|
+
}),
|
|
1043
|
+
).toBe(false);
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
test("a monitor group resource can bring an axis with it", () => {
|
|
1047
|
+
expect(
|
|
1048
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
1049
|
+
statusPageGroup: null,
|
|
1050
|
+
statusPageResources: [
|
|
1051
|
+
makeResource({
|
|
1052
|
+
id: CHARTED,
|
|
1053
|
+
name: "Ungrouped",
|
|
1054
|
+
showStatusHistoryChart: true,
|
|
1055
|
+
withMonitor: false,
|
|
1056
|
+
monitorGroupId: UNCHARTED,
|
|
1057
|
+
}),
|
|
1058
|
+
],
|
|
1059
|
+
}),
|
|
1060
|
+
).toBe(true);
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test("an empty page has no axis", () => {
|
|
1064
|
+
expect(
|
|
1065
|
+
StatusPageGroupNestingLayoutUtil.shouldRenderTimeAxis({
|
|
1066
|
+
statusPageGroup: null,
|
|
1067
|
+
statusPageResources: [],
|
|
1068
|
+
}),
|
|
1069
|
+
).toBe(false);
|
|
1070
|
+
});
|
|
1071
|
+
});
|
|
1072
|
+
});
|