@oneuptime/common 11.7.4 → 12.0.0
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/ExceptionInstance.ts +67 -0
- package/Models/AnalyticsModels/Index.ts +10 -0
- package/Models/AnalyticsModels/Log.ts +60 -0
- package/Models/AnalyticsModels/RumSession.ts +1401 -0
- package/Models/AnalyticsModels/RumSessionChunk.ts +798 -0
- package/Models/AnalyticsModels/Span.ts +60 -0
- package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +52 -0
- package/Models/DatabaseModels/AIRun.ts +31 -0
- package/Models/DatabaseModels/Alert.ts +29 -0
- package/Models/DatabaseModels/AlertCustomField.ts +1 -1
- package/Models/DatabaseModels/AlertFeed.ts +1 -0
- package/Models/DatabaseModels/AutoRemediationRule.ts +989 -0
- package/Models/DatabaseModels/AutoRemediationSuggestion.ts +874 -0
- package/Models/DatabaseModels/CodeRepository.ts +129 -0
- package/Models/DatabaseModels/GlobalConfig.ts +312 -0
- package/Models/DatabaseModels/Incident.ts +29 -0
- package/Models/DatabaseModels/IncidentCustomField.ts +1 -1
- package/Models/DatabaseModels/IncidentFeed.ts +1 -0
- package/Models/DatabaseModels/Index.ts +21 -8
- package/Models/DatabaseModels/InstanceHealthLog.ts +8 -0
- package/Models/DatabaseModels/MonitorCustomField.ts +1 -1
- package/Models/DatabaseModels/MonitorStatus.ts +1 -1
- package/Models/DatabaseModels/NetworkDeviceLabelRule.ts +2 -2
- package/Models/DatabaseModels/NetworkDeviceOwnerRule.ts +2 -2
- package/Models/DatabaseModels/NetworkSiteAssignmentRule.ts +1 -1
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +1 -1
- package/Models/DatabaseModels/Project.ts +231 -0
- package/Models/DatabaseModels/RumApplication.ts +906 -0
- package/Models/DatabaseModels/RumSessionErasureRequest.ts +561 -0
- package/Models/DatabaseModels/RumSessionPin.ts +457 -0
- package/Models/DatabaseModels/RumSessionReplayView.ts +406 -0
- package/Models/DatabaseModels/RunbookCredential.ts +673 -0
- package/Models/DatabaseModels/RunbookSecret.ts +7 -7
- package/Models/DatabaseModels/{RunbookAgent.ts → Runner.ts} +206 -46
- package/Models/DatabaseModels/{RunbookAgentJob.ts → RunnerJob.ts} +151 -18
- package/Models/DatabaseModels/{RunbookAgentOwnerTeam.ts → RunnerOwnerTeam.ts} +41 -43
- package/Models/DatabaseModels/{RunbookAgentOwnerUser.ts → RunnerOwnerUser.ts} +41 -43
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +1 -1
- package/Models/DatabaseModels/StatusPageCustomField.ts +1 -1
- package/Models/DatabaseModels/TeamCustomField.ts +1 -1
- package/Models/DatabaseModels/TeamMemberCustomField.ts +1 -1
- package/Server/API/AIAgentDataAPI.ts +179 -48
- package/Server/API/AIAgentTaskAPI.ts +36 -37
- package/Server/API/AIAgentTaskLogAPI.ts +19 -33
- package/Server/API/AutoRemediationAPI.ts +557 -0
- package/Server/API/BaseAnalyticsAPI.ts +4 -1
- package/Server/API/CommonAPI.ts +35 -0
- package/Server/API/DashboardAPI.ts +340 -15
- package/Server/API/TelemetryAPI.ts +1622 -3
- package/Server/API/UserIncomingCallNumberAPI.ts +31 -0
- package/Server/DatabaseConfig.ts +26 -1
- package/Server/EnvironmentConfig.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.ts +293 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.ts +54 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.ts +104 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.ts +349 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.ts +152 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.ts +151 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.ts +597 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.ts +71 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +32 -0
- package/Server/Services/AIRunService.ts +10 -0
- package/Server/Services/AIService.ts +30 -0
- package/Server/Services/AlertService.ts +41 -4
- package/Server/Services/AlertSeverityService.ts +25 -11
- package/Server/Services/AlertStateService.ts +25 -11
- package/Server/Services/AutoRemediationRuleEngineService.ts +1101 -0
- package/Server/Services/{RunbookAgentOwnerTeamService.ts → AutoRemediationRuleService.ts} +1 -1
- package/Server/Services/AutoRemediationSuggestionService.ts +183 -0
- package/Server/Services/BillingService.ts +50 -1
- package/Server/Services/DatabaseService.ts +92 -4
- package/Server/Services/IncidentService.ts +56 -4
- package/Server/Services/IncidentSeverityService.ts +25 -11
- package/Server/Services/IncidentStateService.ts +25 -11
- package/Server/Services/Index.ts +19 -0
- package/Server/Services/MonitorService.ts +24 -0
- package/Server/Services/MonitorStatusService.ts +25 -11
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +18 -19
- package/Server/Services/NetworkDeviceLabelRuleService.ts +39 -0
- package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +18 -19
- package/Server/Services/NetworkDeviceOwnerRuleService.ts +37 -0
- package/Server/Services/NetworkDeviceService.ts +172 -21
- package/Server/Services/NetworkSiteAssignmentRuleService.ts +92 -12
- package/Server/Services/NetworkSiteService.ts +27 -10
- package/Server/Services/ProjectService.ts +47 -0
- package/Server/Services/RumApplicationService.ts +240 -1
- package/Server/Services/RumSessionChunkService.ts +11 -0
- package/Server/Services/RumSessionErasureRequestService.ts +197 -0
- package/Server/Services/RumSessionPinService.ts +213 -0
- package/Server/Services/RumSessionReplayViewService.ts +201 -0
- package/Server/Services/RumSessionService.ts +123 -0
- package/Server/Services/RunbookCredentialService.ts +76 -0
- package/Server/Services/{RunbookAgentJobService.ts → RunnerJobService.ts} +202 -51
- package/Server/Services/{RunbookAgentOwnerUserService.ts → RunnerOwnerTeamService.ts} +1 -1
- package/Server/Services/RunnerOwnerUserService.ts +10 -0
- package/Server/Services/RunnerService.ts +172 -0
- package/Server/Services/ScheduledMaintenanceStateService.ts +25 -11
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +15 -1
- package/Server/Services/ServiceService.ts +8 -2
- package/Server/Services/StatusPageService.ts +240 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +20 -1
- package/Server/Services/TelemetryExceptionService.ts +3 -16
- package/Server/Services/TelemetryUsageBillingService.ts +33 -2
- package/Server/Services/TraceAggregationService.ts +196 -32
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +17 -0
- package/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.ts +10 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +63 -7
- package/Server/Utils/AI/CodeFix/CodeFixAgentAuth.ts +128 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.ts +54 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +17 -10
- package/Server/Utils/AI/CodeFix/CodeFixRunQueue.ts +8 -6
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +943 -0
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +866 -0
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +823 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +103 -12
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +75 -17
- package/Server/Utils/AI/SRE/ConfidenceSignal.ts +28 -6
- package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +202 -12
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +264 -14
- package/Server/Utils/AI/SRE/Insights/Triage.ts +2 -7
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +5 -24
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +259 -20
- package/Server/Utils/AI/SRE/PostedRootCause.ts +82 -0
- package/Server/Utils/AI/SRE/README.md +3 -2
- package/Server/Utils/AI/SRE/RemediationHandoff.ts +82 -0
- package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +6 -19
- package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +2 -2
- package/Server/Utils/Alert/AlertPrivacyFilter.ts +58 -0
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +604 -0
- package/Server/Utils/AutoRemediation/RemediationVerifier.ts +619 -0
- package/Server/Utils/CorsOptions.ts +52 -0
- package/Server/Utils/Database/RelationIdUtil.ts +68 -0
- package/Server/Utils/Incident/IncidentPrivacyFilter.ts +60 -0
- package/Server/Utils/InstanceHealth/PostgresHealth.ts +286 -0
- package/Server/Utils/InstanceHealth/RedisHealth.ts +434 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +20 -0
- package/Server/Utils/Monitor/MonitorIncident.ts +20 -0
- package/Server/Utils/Monitor/MonitorResource.ts +19 -0
- package/Server/Utils/Monitor/MonitorSummaryCapture.ts +104 -0
- package/Server/Utils/NetworkDevice/RulePatternValidator.ts +44 -0
- package/Server/Utils/OpenAPI.ts +2 -1
- package/Server/Utils/Runbook/RunbookExecutePermission.ts +94 -0
- package/Server/Utils/SessionReplay/SessionReplayErasureTombstone.ts +128 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +586 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +188 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1449 -0
- package/Server/Utils/SessionReplay/SessionReplayRetention.ts +114 -0
- package/Server/Utils/SessionReplay/SessionReplayTargeting.ts +144 -0
- package/Server/Utils/SessionReplay/SessionReplayUsage.ts +92 -0
- package/Server/Utils/StartServer.ts +47 -3
- package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +44 -10
- package/Tests/App/Dashboard/AddWidgetModal.test.tsx +557 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +479 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +343 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +320 -0
- package/Tests/Models/AnalyticsModels/RumSessionSchema.test.ts +455 -0
- package/Tests/Models/AnalyticsModels/SessionIdCorrelation.test.ts +158 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +447 -0
- package/Tests/Models/RunnerVersionLabel.test.ts +223 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +1287 -0
- package/Tests/Server/API/LegacyRunbookAgentRoutes.test.ts +117 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +2093 -0
- package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +429 -0
- package/Tests/Server/API/UserSmsApi.test.ts +68 -2
- package/Tests/Server/DatabaseConfig.test.ts +150 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +2 -2
- package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +2 -3
- package/Tests/Server/Services/AIRunCodeFixClaimScoping.test.ts +100 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +8 -0
- package/Tests/Server/Services/AutoRemediationCommandRuleEngine.test.ts +334 -0
- package/Tests/Server/Services/AutoRemediationRuleEngineService.test.ts +517 -0
- package/Tests/Server/Services/BillingService.test.ts +158 -1
- package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +24 -2
- package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +17 -10
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +128 -0
- package/Tests/Server/Services/DatabaseServiceUpdateWriteRouting.test.ts +178 -0
- package/Tests/Server/Services/DatabaseServiceUpdateWriteRoutingMonitor.test.ts +206 -0
- package/Tests/Server/Services/MonitorServiceOperationalStatusSelection.test.ts +145 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +726 -0
- package/Tests/Server/Services/NetworkDeviceSiteAssignment.test.ts +570 -9
- package/Tests/Server/Services/NetworkSiteAssignmentRuleService.test.ts +94 -1
- package/Tests/Server/Services/NetworkSiteService.test.ts +85 -0
- package/Tests/Server/Services/RumApplicationReplayHealth.test.ts +114 -0
- package/Tests/Server/Services/RunbookCredentialService.test.ts +657 -0
- package/Tests/Server/Services/SessionReplayCreatePath.test.ts +376 -0
- package/Tests/Server/Services/SessionReplayGateCacheInvalidation.test.ts +211 -0
- package/Tests/Server/Services/SessionReplayMetering.test.ts +174 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +555 -0
- package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +25 -4
- package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +17 -2
- package/Tests/Server/Services/TraceAggregationService.test.ts +48 -0
- package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +855 -0
- package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +38 -27
- package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +44 -0
- package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +475 -0
- package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAuth.test.ts +257 -0
- package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.test.ts +236 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +44 -6
- package/Tests/Server/Utils/AI/CodeFixRunQueue.test.ts +47 -1
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +2 -2
- package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +294 -0
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +2 -2
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +47 -3
- package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +233 -0
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +463 -0
- package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
- package/Tests/Server/Utils/AI/RemediationAllowlistNormalization.test.ts +248 -0
- package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +1008 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +887 -0
- package/Tests/Server/Utils/AI/RemediationHandoff.test.ts +306 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +745 -0
- package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +291 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +1050 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +350 -0
- package/Tests/Server/Utils/AutoRemediation/RemediationVerifier.test.ts +341 -0
- package/Tests/Server/Utils/CorsOptions.test.ts +119 -0
- package/Tests/Server/Utils/Database/RelationIdUtil.test.ts +120 -0
- package/Tests/Server/Utils/Database/ResolveReferenceId.test.ts +60 -0
- package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +337 -0
- package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +671 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryCapture.test.ts +205 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +313 -0
- package/Tests/Server/Utils/PushNotificationUtil.test.ts +228 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +177 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayOriginAllowlist.test.ts +108 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayRetention.test.ts +229 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayTargeting.test.ts +246 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +110 -0
- package/Tests/Types/AI/AIRunStatus.test.ts +25 -0
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +1063 -0
- package/Tests/Types/BaseDatabase/AggregationType.test.ts +56 -0
- package/Tests/Types/CustomField/CustomFieldDropdownOption.test.ts +227 -0
- package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +811 -0
- package/Tests/Types/Dashboard/RumDashboardTemplate.test.ts +1238 -0
- package/Tests/Types/Day/DayOfWeek.test.ts +46 -0
- package/Tests/Types/Docker/DockerInventoryExtractor.test.ts +249 -0
- package/Tests/Types/Kubernetes/KubernetesInventoryExtractor.test.ts +231 -0
- package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +323 -0
- package/Tests/Types/Metrics/MetricDownsamplingRetentionDays.test.ts +77 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +42 -14
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +2 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +3 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +1 -1
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +6 -3
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +285 -0
- package/Tests/Types/Podman/PodmanInventoryExtractor.test.ts +159 -0
- package/Tests/Types/Runbook/RunbookExecutionDeadline.test.ts +268 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +242 -0
- package/Tests/Types/Telemetry/TelemetryRetentionConfig.test.ts +225 -0
- package/Tests/Types/Text.test.ts +60 -0
- package/Tests/UI/Components/AutocompleteTextInputMenu.test.tsx +555 -0
- package/Tests/UI/Components/BulkLabelActions.test.tsx +1502 -0
- package/Tests/UI/Components/BulkOwnerActions.test.tsx +1255 -0
- package/Tests/UI/Components/CustomFields/DropdownOptionsInput.test.tsx +228 -0
- package/Tests/UI/Components/Detail/DropdownColors.test.tsx +128 -0
- package/Tests/UI/Components/Dropdown/DropdownValueBadge.test.tsx +60 -0
- package/Tests/UI/Components/FilterModalOperatorMenu.test.tsx +674 -0
- package/Tests/UI/Components/Forms/ColorPicker.test.tsx +48 -0
- package/Tests/UI/Components/Forms/ColorPickerPopup.test.tsx +222 -0
- package/Tests/UI/Components/Forms/IconPickerPopup.test.tsx +270 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +125 -6
- package/Tests/UI/Components/OperatorSelectorFilterTypes.test.tsx +1595 -0
- package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +851 -0
- package/Tests/UI/Components/OperatorSelectorMenu.test.tsx +1229 -0
- package/Tests/UI/Components/Pagination.test.tsx +85 -0
- package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +248 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +790 -0
- package/Tests/UI/Rum/ReplayScrubber.test.tsx +236 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +606 -0
- package/Tests/UI/Utils/Timezone.test.ts +86 -0
- package/Tests/UI/Utils/UseClockTick.test.tsx +276 -0
- package/Tests/Utils/AiRemediation/CommandPolicy.test.ts +1063 -0
- package/Tests/Utils/Dashboard/ClockWidgetFormat.test.ts +1374 -0
- package/Tests/Utils/Dashboard/DashboardClockComponent.test.ts +362 -0
- package/Tests/Utils/Dashboard/DashboardHtmlComponent.test.ts +217 -0
- package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +656 -0
- package/Tests/Utils/Dashboard/ValueWidgetLayout.test.ts +1007 -0
- package/Tests/Utils/Metrics/MetricSeriesScope.test.ts +1197 -0
- package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +839 -0
- package/Tests/Utils/NetworkSite/CidrMatchUtil.test.ts +69 -0
- package/Tests/Utils/Rules/RulePatternMatchUtil.test.ts +274 -0
- package/Tests/Utils/Rum/ChunkMath.test.ts +286 -0
- package/Tests/Utils/Rum/Masking.test.ts +186 -0
- package/Tests/Utils/Rum/SessionIdentity.test.ts +272 -0
- package/Tests/Utils/Rum/SessionSampling.test.ts +164 -0
- package/Tests/Utils/Rum/UrlScrubber.test.ts +175 -0
- package/Tests/Utils/StatusPage/Report.test.ts +708 -0
- package/Tests/Utils/ValueFormatter.test.ts +299 -1
- package/Types/AI/AIRunStatus.ts +18 -7
- package/Types/AI/AIRunType.ts +18 -0
- package/Types/AI/FixVerificationStatus.ts +21 -0
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +16 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +304 -0
- package/Types/AutoRemediation/AutoRemediationExecutionMode.ts +17 -0
- package/Types/AutoRemediation/AutoRemediationSuggestionStatus.ts +35 -0
- package/Types/AutoRemediation/AutoRemediationSuggestionType.ts +17 -0
- package/Types/AutoRemediation/AutoRemediationTriggerEntity.ts +11 -0
- package/Types/AutoRemediation/AutoRemediationVerificationStatus.ts +21 -0
- package/Types/CustomField/CustomFieldDropdownOption.ts +190 -0
- package/Types/Dashboard/DashboardComponentType.ts +2 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +4 -0
- package/Types/Dashboard/DashboardComponents/DashboardClockComponent.ts +45 -0
- package/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.ts +27 -0
- package/Types/Dashboard/DashboardComponents/DashboardValueComponent.ts +8 -0
- package/Types/Dashboard/DashboardTemplates.ts +516 -0
- package/Types/Date.ts +6 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/Kubernetes/KubernetesRightSizing.ts +353 -0
- package/Types/MeteredPlan/ProductType.ts +1 -0
- package/Types/Monitor/MonitorStepMetricMonitor.ts +27 -1
- package/Types/Monitor/MonitorSteps.ts +5 -0
- package/Types/Monitor/MonitorSummarySnapshot.ts +88 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +52 -10
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +7 -5
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +101 -20
- package/Types/Monitor/RumAlertTemplates.ts +386 -0
- package/Types/Permission.ts +204 -24
- package/Types/Rum/SessionReplay.ts +452 -0
- package/Types/Rum/SessionReplayCaptureTrigger.ts +26 -0
- package/Types/Rum/SessionReplayConsentMode.ts +27 -0
- package/Types/Rum/SessionReplayMaskingMode.ts +86 -0
- package/Types/Rum/SessionReplayTriggerReason.ts +31 -0
- package/Types/Runbook/RunbookCredentialType.ts +6 -0
- package/Types/Runbook/RunbookStep.ts +66 -1
- package/Types/Runbook/RunbookStepType.ts +26 -0
- package/Types/Runbook/RunnerJobOrigin.ts +20 -0
- package/Types/Runbook/{RunbookAgentJobStatus.ts → RunnerJobStatus.ts} +2 -2
- package/Types/StatusPage/StatusPageReport.ts +99 -0
- package/Types/Telemetry/TelemetryRetentionConfig.ts +18 -1
- package/Types/Text.ts +33 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +260 -75
- package/UI/Components/BulkUpdate/BulkLabelActions.tsx +99 -9
- package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +11 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +19 -14
- package/UI/Components/CustomFields/DropdownOptionsInput.tsx +144 -54
- package/UI/Components/Detail/Detail.tsx +9 -22
- package/UI/Components/Dropdown/Dropdown.tsx +2 -5
- package/UI/Components/Dropdown/DropdownMenuZIndex.ts +7 -0
- package/UI/Components/Dropdown/DropdownValueBadge.tsx +61 -0
- package/UI/Components/Filters/OperatorSelector.tsx +350 -39
- package/UI/Components/Forms/Fields/ColorPicker.tsx +60 -27
- package/UI/Components/Forms/Fields/IconPicker.tsx +89 -60
- package/UI/Components/Forms/Validation.ts +9 -4
- package/UI/Components/List/List.tsx +1 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +41 -32
- package/UI/Components/Pagination/Pagination.tsx +33 -10
- package/UI/Components/Table/Table.tsx +1 -0
- package/UI/Types/UseAnchoredFieldPopup.ts +321 -0
- package/UI/Utils/Dropdown.ts +26 -0
- package/UI/Utils/Timezone.ts +18 -2
- package/UI/Utils/UseClockTick.ts +78 -0
- package/Utils/AiRemediation/CommandPolicy.ts +398 -0
- package/Utils/Dashboard/ClockWidgetFormat.ts +604 -0
- package/Utils/Dashboard/Components/DashboardClockComponent.ts +181 -0
- package/Utils/Dashboard/Components/DashboardHtmlComponent.ts +162 -0
- package/Utils/Dashboard/Components/DashboardValueComponent.ts +12 -1
- package/Utils/Dashboard/Components/Index.ts +14 -0
- package/Utils/Dashboard/HtmlWidgetDocument.ts +309 -0
- package/Utils/Dashboard/ValueWidgetLayout.ts +471 -0
- package/Utils/Metrics/MetricSeriesScope.ts +301 -0
- package/Utils/Monitor/MonitorSummarySnapshotUtil.ts +593 -0
- package/Utils/NetworkSite/CidrMatchUtil.ts +30 -17
- package/Utils/Rules/RulePatternMatchUtil.ts +103 -0
- package/Utils/Rum/ChunkMath.ts +211 -0
- package/Utils/Rum/Masking.ts +168 -0
- package/Utils/Rum/SessionIdentity.ts +230 -0
- package/Utils/Rum/SessionSampling.ts +108 -0
- package/Utils/Rum/UrlScrubber.ts +185 -0
- package/Utils/Schema/ModelSchema.ts +27 -3
- package/Utils/StatusPage/Report.ts +304 -0
- package/Utils/ValueFormatter.ts +300 -54
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +63 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Index.js +10 -0
- package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +56 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/RumSession.js +1159 -0
- package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +670 -0
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -0
- package/build/dist/Models/AnalyticsModels/Span.js +56 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +54 -0
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRun.js +32 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Alert.js +30 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AutoRemediationRule.js +998 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +917 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CodeRepository.js +132 -0
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +327 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +30 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +20 -8
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InstanceHealthLog.js +8 -0
- package/build/dist/Models/DatabaseModels/InstanceHealthLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatus.js +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatus.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js +2 -2
- package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js +2 -2
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +235 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +930 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumSessionErasureRequest.js +591 -0
- package/build/dist/Models/DatabaseModels/RumSessionErasureRequest.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumSessionPin.js +483 -0
- package/build/dist/Models/DatabaseModels/RumSessionPin.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumSessionReplayView.js +433 -0
- package/build/dist/Models/DatabaseModels/RumSessionReplayView.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RunbookCredential.js +693 -0
- package/build/dist/Models/DatabaseModels/RunbookCredential.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RunbookSecret.js +8 -8
- package/build/dist/Models/DatabaseModels/RunbookSecret.js.map +1 -1
- package/build/dist/Models/DatabaseModels/{RunbookAgent.js → Runner.js} +235 -70
- package/build/dist/Models/DatabaseModels/Runner.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{RunbookAgentJob.js → RunnerJob.js} +173 -40
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{RunbookAgentOwnerTeam.js → RunnerOwnerTeam.js} +57 -57
- package/build/dist/Models/DatabaseModels/RunnerOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/{RunbookAgentOwnerUser.js → RunnerOwnerUser.js} +57 -57
- package/build/dist/Models/DatabaseModels/RunnerOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +130 -45
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskAPI.js +26 -36
- package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskLogAPI.js +11 -28
- package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +416 -0
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -0
- package/build/dist/Server/API/BaseAnalyticsAPI.js +4 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +24 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +265 -6
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +940 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js +18 -0
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
- package/build/dist/Server/DatabaseConfig.js +23 -4
- package/build/dist/Server/DatabaseConfig.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +8 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.js +35 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.js +128 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.js +27 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.js +88 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.js +59 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.js +76 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.js +214 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +1 -4
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +26 -0
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +37 -4
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +24 -11
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/AlertStateService.js +24 -11
- package/build/dist/Server/Services/AlertStateService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +917 -0
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/{RunbookAgentOwnerTeamService.js → AutoRemediationRuleService.js} +2 -2
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -0
- package/build/dist/Server/Services/AutoRemediationSuggestionService.js +127 -0
- package/build/dist/Server/Services/AutoRemediationSuggestionService.js.map +1 -0
- package/build/dist/Server/Services/BillingService.js +45 -1
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +72 -4
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +52 -4
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +24 -11
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateService.js +24 -11
- package/build/dist/Server/Services/IncidentStateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +18 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +24 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusService.js +25 -11
- package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +12 -11
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js +43 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +12 -11
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js +41 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +129 -20
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js +67 -10
- package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +19 -8
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +43 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +202 -0
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionChunkService.js +9 -0
- package/build/dist/Server/Services/RumSessionChunkService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionErasureRequestService.js +190 -0
- package/build/dist/Server/Services/RumSessionErasureRequestService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionPinService.js +208 -0
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionReplayViewService.js +180 -0
- package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionService.js +102 -0
- package/build/dist/Server/Services/RumSessionService.js.map +1 -0
- package/build/dist/Server/Services/RunbookCredentialService.js +67 -0
- package/build/dist/Server/Services/RunbookCredentialService.js.map +1 -0
- package/build/dist/Server/Services/{RunbookAgentJobService.js → RunnerJobService.js} +158 -46
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -0
- package/build/dist/Server/Services/{RunbookAgentOwnerUserService.js → RunnerOwnerTeamService.js} +2 -2
- package/build/dist/Server/Services/RunnerOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/RunnerOwnerUserService.js +9 -0
- package/build/dist/Server/Services/RunnerOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/RunnerService.js +168 -0
- package/build/dist/Server/Services/RunnerService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateService.js +24 -11
- package/build/dist/Server/Services/ScheduledMaintenanceStateService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +15 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +8 -2
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +173 -16
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +3 -16
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +31 -3
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +106 -2
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +16 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.js +9 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +38 -6
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js +104 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js.map +1 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.js +54 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.js.map +1 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +11 -7
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js +3 -4
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +724 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +668 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +657 -0
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +52 -7
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +53 -5
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +25 -6
- package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +131 -17
- package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +200 -5
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js +2 -7
- package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +4 -17
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +203 -19
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +89 -0
- package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/RemediationHandoff.js +82 -0
- package/build/dist/Server/Utils/AI/SRE/RemediationHandoff.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +4 -19
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +2 -2
- package/build/dist/Server/Utils/Alert/AlertPrivacyFilter.js +36 -0
- package/build/dist/Server/Utils/Alert/AlertPrivacyFilter.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +498 -0
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -0
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +492 -0
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -0
- package/build/dist/Server/Utils/CorsOptions.js +47 -0
- package/build/dist/Server/Utils/CorsOptions.js.map +1 -0
- package/build/dist/Server/Utils/Database/RelationIdUtil.js +51 -0
- package/build/dist/Server/Utils/Database/RelationIdUtil.js.map +1 -0
- package/build/dist/Server/Utils/Incident/IncidentPrivacyFilter.js +38 -0
- package/build/dist/Server/Utils/Incident/IncidentPrivacyFilter.js.map +1 -1
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +194 -0
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +1 -0
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +292 -0
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +16 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorSummaryCapture.js +92 -0
- package/build/dist/Server/Utils/Monitor/MonitorSummaryCapture.js.map +1 -0
- package/build/dist/Server/Utils/NetworkDevice/RulePatternValidator.js +27 -0
- package/build/dist/Server/Utils/NetworkDevice/RulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/OpenAPI.js +2 -1
- package/build/dist/Server/Utils/OpenAPI.js.map +1 -1
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js +67 -0
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayErasureTombstone.js +98 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayErasureTombstone.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +378 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +137 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +998 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayRetention.js +59 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayRetention.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayTargeting.js +98 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayTargeting.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +69 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +40 -3
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +27 -6
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
- package/build/dist/Types/AI/AIRunStatus.js +17 -5
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
- package/build/dist/Types/AI/AIRunType.js +18 -0
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AI/FixVerificationStatus.js +22 -0
- package/build/dist/Types/AI/FixVerificationStatus.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +16 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +192 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationExecutionMode.js +18 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationExecutionMode.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionStatus.js +31 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionStatus.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionType.js +18 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionType.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationTriggerEntity.js +12 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationTriggerEntity.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationVerificationStatus.js +22 -0
- package/build/dist/Types/AutoRemediation/AutoRemediationVerificationStatus.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldDropdownOption.js +110 -0
- package/build/dist/Types/CustomField/CustomFieldDropdownOption.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +4 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardClockComponent.js +19 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardClockComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +487 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Date.js +6 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesRightSizing.js +229 -0
- package/build/dist/Types/Kubernetes/KubernetesRightSizing.js.map +1 -0
- package/build/dist/Types/MeteredPlan/ProductType.js +1 -0
- package/build/dist/Types/MeteredPlan/ProductType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +12 -2
- package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorSteps.js +5 -0
- package/build/dist/Types/Monitor/MonitorSteps.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorSummarySnapshot.js +13 -0
- package/build/dist/Types/Monitor/MonitorSummarySnapshot.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +40 -8
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +4 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +50 -10
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/RumAlertTemplates.js +297 -0
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +184 -24
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +155 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +26 -0
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayConsentMode.js +27 -0
- package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayMaskingMode.js +79 -0
- package/build/dist/Types/Rum/SessionReplayMaskingMode.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayTriggerReason.js +28 -0
- package/build/dist/Types/Rum/SessionReplayTriggerReason.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookCredentialType.js +7 -0
- package/build/dist/Types/Runbook/RunbookCredentialType.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStep.js +20 -1
- package/build/dist/Types/Runbook/RunbookStep.js.map +1 -1
- package/build/dist/Types/Runbook/RunbookStepType.js +24 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/Types/Runbook/RunnerJobOrigin.js +21 -0
- package/build/dist/Types/Runbook/RunnerJobOrigin.js.map +1 -0
- package/build/dist/Types/Runbook/RunnerJobStatus.js +22 -0
- package/build/dist/Types/Runbook/RunnerJobStatus.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageReport.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReport.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -1
- package/build/dist/Types/Text.js +29 -0
- package/build/dist/Types/Text.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +143 -28
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +82 -9
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +11 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +11 -13
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js +76 -45
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +3 -8
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +2 -5
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/DropdownMenuZIndex.js +7 -0
- package/build/dist/UI/Components/Dropdown/DropdownMenuZIndex.js.map +1 -0
- package/build/dist/UI/Components/Dropdown/DropdownValueBadge.js +32 -0
- package/build/dist/UI/Components/Dropdown/DropdownValueBadge.js.map +1 -0
- package/build/dist/UI/Components/Filters/OperatorSelector.js +210 -20
- package/build/dist/UI/Components/Filters/OperatorSelector.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +26 -10
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -13
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +9 -5
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +1 -1
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +21 -19
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +14 -3
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +1 -1
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js +197 -0
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -0
- package/build/dist/UI/Utils/Dropdown.js +18 -0
- package/build/dist/UI/Utils/Dropdown.js.map +1 -1
- package/build/dist/UI/Utils/Timezone.js +13 -2
- package/build/dist/UI/Utils/Timezone.js.map +1 -1
- package/build/dist/UI/Utils/UseClockTick.js +41 -0
- package/build/dist/UI/Utils/UseClockTick.js.map +1 -0
- package/build/dist/Utils/AiRemediation/CommandPolicy.js +303 -0
- package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -0
- package/build/dist/Utils/Dashboard/ClockWidgetFormat.js +232 -0
- package/build/dist/Utils/Dashboard/ClockWidgetFormat.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js +146 -0
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardHtmlComponent.js +133 -0
- package/build/dist/Utils/Dashboard/Components/DashboardHtmlComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardValueComponent.js +10 -1
- package/build/dist/Utils/Dashboard/Components/DashboardValueComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/Index.js +8 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +198 -0
- package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -0
- package/build/dist/Utils/Dashboard/ValueWidgetLayout.js +276 -0
- package/build/dist/Utils/Dashboard/ValueWidgetLayout.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricSeriesScope.js +203 -0
- package/build/dist/Utils/Metrics/MetricSeriesScope.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js +382 -0
- package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/CidrMatchUtil.js +13 -7
- package/build/dist/Utils/NetworkSite/CidrMatchUtil.js.map +1 -1
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js +87 -0
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js.map +1 -0
- package/build/dist/Utils/Rum/ChunkMath.js +147 -0
- package/build/dist/Utils/Rum/ChunkMath.js.map +1 -0
- package/build/dist/Utils/Rum/Masking.js +143 -0
- package/build/dist/Utils/Rum/Masking.js.map +1 -0
- package/build/dist/Utils/Rum/SessionIdentity.js +161 -0
- package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -0
- package/build/dist/Utils/Rum/SessionSampling.js +91 -0
- package/build/dist/Utils/Rum/SessionSampling.js.map +1 -0
- package/build/dist/Utils/Rum/UrlScrubber.js +144 -0
- package/build/dist/Utils/Rum/UrlScrubber.js.map +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js +28 -3
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/build/dist/Utils/StatusPage/Report.js +194 -0
- package/build/dist/Utils/StatusPage/Report.js.map +1 -0
- package/build/dist/Utils/ValueFormatter.js +239 -46
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/jest.config.json +3 -0
- package/package.json +1 -1
- package/tsconfig.json +19 -1
- package/Server/Services/RunbookAgentService.ts +0 -93
- package/build/dist/Models/DatabaseModels/RunbookAgent.js.map +0 -1
- package/build/dist/Models/DatabaseModels/RunbookAgentJob.js.map +0 -1
- package/build/dist/Models/DatabaseModels/RunbookAgentOwnerTeam.js.map +0 -1
- package/build/dist/Models/DatabaseModels/RunbookAgentOwnerUser.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentJobService.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentOwnerTeamService.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentOwnerUserService.js.map +0 -1
- package/build/dist/Server/Services/RunbookAgentService.js +0 -91
- package/build/dist/Server/Services/RunbookAgentService.js.map +0 -1
- package/build/dist/Types/Runbook/RunbookAgentJobStatus.js +0 -22
- package/build/dist/Types/Runbook/RunbookAgentJobStatus.js.map +0 -1
|
@@ -0,0 +1,1502 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
jest,
|
|
7
|
+
test,
|
|
8
|
+
} from "@jest/globals";
|
|
9
|
+
import "@testing-library/jest-dom";
|
|
10
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
import { act, FunctionComponent, ReactElement } from "react";
|
|
13
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* useBulkLabelActions is the single mutator behind "Add Labels" and "Remove
|
|
17
|
+
* Labels" on every resource list in the product, and it had no test coverage at
|
|
18
|
+
* all before this change. Most of what follows is therefore baseline coverage
|
|
19
|
+
* of a previously untested shared hook - the merge/subtract arithmetic, the
|
|
20
|
+
* per-item failure isolation, and the progress accounting the bulk progress bar
|
|
21
|
+
* is drawn from - not coverage of the SLO tables that prompted the work. Its
|
|
22
|
+
* failure mode is silent: each write is a read-modify-write of the item's own
|
|
23
|
+
* label array, so anything that goes wrong in the middle overwrites labels the
|
|
24
|
+
* user never selected and still reports the item as a success.
|
|
25
|
+
*
|
|
26
|
+
* The tests that pin the fixes actually made in this change are:
|
|
27
|
+
* - "does not fetch labels at all when there is no current project"
|
|
28
|
+
* (projectId: null went out as a filter matching nothing)
|
|
29
|
+
* - "drops a dropdown option that came back without an id before merging"
|
|
30
|
+
* (a blank id in the labels array failed the whole item)
|
|
31
|
+
* - "reads the item's current label ids before merging" (the select now asks
|
|
32
|
+
* for _id, which the guard below depends on)
|
|
33
|
+
* - "fails an item whose current read carries no id ..." and
|
|
34
|
+
* "... comes back as null ..." (a deleted or filtered row answers 200 with
|
|
35
|
+
* {}, which hydrates truthy, so add mode used to clobber its whole label
|
|
36
|
+
* set)
|
|
37
|
+
* - "starts nothing and reports nothing when every submitted label id is
|
|
38
|
+
* blank" (that path used to report every item as a success)
|
|
39
|
+
* - "keeps the newest selection's remove options when an older fetch settles
|
|
40
|
+
* last" (the stale response used to win)
|
|
41
|
+
* - the two "opening X closes an open Y modal" tests (stacked modals share
|
|
42
|
+
* one bulkActionProps and the loser goes silently inert)
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* react-i18next is not initialized in the test environment. Mock the hook so
|
|
47
|
+
* translate helpers echo their input and the component renders synchronously.
|
|
48
|
+
*/
|
|
49
|
+
jest.mock("react-i18next", () => {
|
|
50
|
+
return {
|
|
51
|
+
useTranslation: () => {
|
|
52
|
+
return {
|
|
53
|
+
t: (key: string, opts?: { defaultValue?: string }): string => {
|
|
54
|
+
return opts?.defaultValue ?? key;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
62
|
+
const getItemMock: MockFunction = getJestMockFunction();
|
|
63
|
+
const updateByIdMock: MockFunction = getJestMockFunction();
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* The arrow wrappers are load bearing: jest.mock is hoisted above the compiled
|
|
67
|
+
* requires, so naming the mocks directly would capture them before their
|
|
68
|
+
* initializers have run.
|
|
69
|
+
*/
|
|
70
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
71
|
+
return {
|
|
72
|
+
__esModule: true,
|
|
73
|
+
default: {
|
|
74
|
+
getList: (...args: Array<any>) => {
|
|
75
|
+
return getListMock(...args);
|
|
76
|
+
},
|
|
77
|
+
getItem: (...args: Array<any>) => {
|
|
78
|
+
return getItemMock(...args);
|
|
79
|
+
},
|
|
80
|
+
updateById: (...args: Array<any>) => {
|
|
81
|
+
return updateByIdMock(...args);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
import useBulkLabelActions, {
|
|
88
|
+
BulkLabelActionsResult,
|
|
89
|
+
} from "../../../UI/Components/BulkUpdate/BulkLabelActions";
|
|
90
|
+
import {
|
|
91
|
+
BulkActionButtonSchema,
|
|
92
|
+
BulkActionFailed,
|
|
93
|
+
BulkActionOnClickProps,
|
|
94
|
+
ProgressInfo,
|
|
95
|
+
} from "../../../UI/Components/BulkUpdate/BulkUpdateForm";
|
|
96
|
+
import { ButtonStyleType } from "../../../UI/Components/Button/Button";
|
|
97
|
+
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
98
|
+
import Label from "../../../Models/DatabaseModels/Label";
|
|
99
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
100
|
+
import API from "../../../UI/Utils/API/API";
|
|
101
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
102
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
103
|
+
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
104
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
105
|
+
import { Red } from "../../../Types/BrandColors";
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
* These tests drive real Formik + react-select trees behind a modal, so the
|
|
109
|
+
* testing-library default of 1s flakes on a loaded box.
|
|
110
|
+
*/
|
|
111
|
+
const WAIT_TIMEOUT: number = 20000;
|
|
112
|
+
|
|
113
|
+
const PROJECT_ID: string = "11111111-1111-4111-8111-111111111111";
|
|
114
|
+
const ITEM_ONE_ID: string = "22222222-2222-4222-8222-222222222221";
|
|
115
|
+
const ITEM_TWO_ID: string = "22222222-2222-4222-8222-222222222222";
|
|
116
|
+
const ITEM_THREE_ID: string = "22222222-2222-4222-8222-222222222223";
|
|
117
|
+
const LABEL_ALPHA_ID: string = "33333333-3333-4333-8333-333333333331";
|
|
118
|
+
const LABEL_BETA_ID: string = "33333333-3333-4333-8333-333333333332";
|
|
119
|
+
const LABEL_GAMMA_ID: string = "33333333-3333-4333-8333-333333333333";
|
|
120
|
+
|
|
121
|
+
type MakeLabelFunction = (id: string, name: string) => Label;
|
|
122
|
+
|
|
123
|
+
const makeLabel: MakeLabelFunction = (id: string, name: string): Label => {
|
|
124
|
+
const label: Label = new Label();
|
|
125
|
+
label._id = id;
|
|
126
|
+
label.name = name;
|
|
127
|
+
return label;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
type MakeItemFunction = (id: string) => Monitor;
|
|
131
|
+
|
|
132
|
+
const makeItem: MakeItemFunction = (id: string): Monitor => {
|
|
133
|
+
const item: Monitor = new Monitor();
|
|
134
|
+
item._id = id;
|
|
135
|
+
return item;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type MakeItemWithLabelsFunction = (id: string, labels: Array<Label>) => Monitor;
|
|
139
|
+
|
|
140
|
+
const makeItemWithLabels: MakeItemWithLabelsFunction = (
|
|
141
|
+
id: string,
|
|
142
|
+
labels: Array<Label>,
|
|
143
|
+
): Monitor => {
|
|
144
|
+
const item: Monitor = makeItem(id);
|
|
145
|
+
item.labels = labels;
|
|
146
|
+
return item;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
interface ProgressSnapshot {
|
|
150
|
+
totalIds: Array<string>;
|
|
151
|
+
inProgressIds: Array<string>;
|
|
152
|
+
successIds: Array<string>;
|
|
153
|
+
failedIds: Array<string>;
|
|
154
|
+
failedMessages: Array<string>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
type IdsOfFunction = (items: Array<Monitor>) => Array<string>;
|
|
158
|
+
|
|
159
|
+
const idsOf: IdsOfFunction = (items: Array<Monitor>): Array<string> => {
|
|
160
|
+
return items.map((item: Monitor) => {
|
|
161
|
+
return item._id || "";
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
type SnapshotProgressFunction = (
|
|
166
|
+
progressInfo: ProgressInfo<Monitor>,
|
|
167
|
+
) => ProgressSnapshot;
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
* The hook allocates the four progress arrays once and hands the same
|
|
171
|
+
* references to every onProgressInfo call, so anything held by reference
|
|
172
|
+
* mutates retroactively into the final state. Copy out of them on arrival or
|
|
173
|
+
* the per-step assertions below are meaningless.
|
|
174
|
+
*/
|
|
175
|
+
const snapshotProgress: SnapshotProgressFunction = (
|
|
176
|
+
progressInfo: ProgressInfo<Monitor>,
|
|
177
|
+
): ProgressSnapshot => {
|
|
178
|
+
return {
|
|
179
|
+
totalIds: idsOf(progressInfo.totalItems),
|
|
180
|
+
inProgressIds: idsOf(progressInfo.inProgressItems),
|
|
181
|
+
successIds: idsOf(progressInfo.successItems),
|
|
182
|
+
failedIds: idsOf(
|
|
183
|
+
progressInfo.failed.map((failure: BulkActionFailed<Monitor>) => {
|
|
184
|
+
return failure.item;
|
|
185
|
+
}),
|
|
186
|
+
),
|
|
187
|
+
failedMessages: progressInfo.failed.map(
|
|
188
|
+
(failure: BulkActionFailed<Monitor>) => {
|
|
189
|
+
return String(failure.failedMessage);
|
|
190
|
+
},
|
|
191
|
+
),
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const onProgressInfoMock: MockFunction = getJestMockFunction();
|
|
196
|
+
const onBulkActionStartMock: MockFunction = getJestMockFunction();
|
|
197
|
+
const onBulkActionEndMock: MockFunction = getJestMockFunction();
|
|
198
|
+
|
|
199
|
+
let progressSnapshots: Array<ProgressSnapshot> = [];
|
|
200
|
+
let callOrder: Array<string> = [];
|
|
201
|
+
let capturedActions: Array<BulkActionButtonSchema<Monitor>> = [];
|
|
202
|
+
let capturedModals: ReactElement | null = null;
|
|
203
|
+
|
|
204
|
+
type MakeActionPropsFunction = (
|
|
205
|
+
items: Array<Monitor>,
|
|
206
|
+
) => BulkActionOnClickProps<Monitor>;
|
|
207
|
+
|
|
208
|
+
const makeActionProps: MakeActionPropsFunction = (
|
|
209
|
+
items: Array<Monitor>,
|
|
210
|
+
): BulkActionOnClickProps<Monitor> => {
|
|
211
|
+
return {
|
|
212
|
+
items: items,
|
|
213
|
+
onProgressInfo:
|
|
214
|
+
onProgressInfoMock as unknown as BulkActionOnClickProps<Monitor>["onProgressInfo"],
|
|
215
|
+
onBulkActionStart: onBulkActionStartMock as unknown as () => void,
|
|
216
|
+
onBulkActionEnd: onBulkActionEndMock as unknown as () => void,
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
interface HarnessProps {
|
|
221
|
+
items: Array<Monitor>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* `modals` has to be mounted for any of the modal behavior to exist, which
|
|
226
|
+
* renderHook cannot do, so the hook is driven through a component that renders
|
|
227
|
+
* a plain trigger per action.
|
|
228
|
+
*/
|
|
229
|
+
const Harness: FunctionComponent<HarnessProps> = (
|
|
230
|
+
props: HarnessProps,
|
|
231
|
+
): ReactElement => {
|
|
232
|
+
const { bulkActions, modals }: BulkLabelActionsResult<Monitor> =
|
|
233
|
+
useBulkLabelActions<Monitor>({ modelType: Monitor });
|
|
234
|
+
|
|
235
|
+
capturedActions = bulkActions;
|
|
236
|
+
capturedModals = modals;
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<div>
|
|
240
|
+
{bulkActions.map((action: BulkActionButtonSchema<Monitor>) => {
|
|
241
|
+
return (
|
|
242
|
+
<button
|
|
243
|
+
key={action.title}
|
|
244
|
+
type="button"
|
|
245
|
+
onClick={() => {
|
|
246
|
+
void action.onClick(makeActionProps(props.items));
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{`Trigger ${action.title}`}
|
|
250
|
+
</button>
|
|
251
|
+
);
|
|
252
|
+
})}
|
|
253
|
+
{modals}
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
type ClickTriggerFunction = (title: string) => void;
|
|
259
|
+
|
|
260
|
+
const clickTrigger: ClickTriggerFunction = (title: string): void => {
|
|
261
|
+
fireEvent.click(screen.getByText(`Trigger ${title}`));
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
type WaitForModalTitleFunction = (title: string) => Promise<void>;
|
|
265
|
+
|
|
266
|
+
const waitForModalTitle: WaitForModalTitleFunction = async (
|
|
267
|
+
title: string,
|
|
268
|
+
): Promise<void> => {
|
|
269
|
+
await waitFor(
|
|
270
|
+
() => {
|
|
271
|
+
expect(screen.getByTestId("modal-title")).toHaveTextContent(title);
|
|
272
|
+
},
|
|
273
|
+
{ timeout: WAIT_TIMEOUT },
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
type WaitForMountedLabelFetchFunction = () => Promise<void>;
|
|
278
|
+
|
|
279
|
+
/*
|
|
280
|
+
* The mount effect writes state after its fetch settles; waiting for the call
|
|
281
|
+
* keeps that write inside act() instead of leaking into the next test.
|
|
282
|
+
*/
|
|
283
|
+
const waitForMountedLabelFetch: WaitForMountedLabelFetchFunction =
|
|
284
|
+
async (): Promise<void> => {
|
|
285
|
+
await waitFor(() => {
|
|
286
|
+
expect(getListMock).toHaveBeenCalledTimes(1);
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
type OpenMenuFunction = () => Promise<Array<HTMLElement>>;
|
|
291
|
+
|
|
292
|
+
/*
|
|
293
|
+
* react-select renders its menu lazily; ArrowDown on the combobox is the only
|
|
294
|
+
* interaction that mounts the option list.
|
|
295
|
+
*/
|
|
296
|
+
const openMenu: OpenMenuFunction = async (): Promise<Array<HTMLElement>> => {
|
|
297
|
+
const combobox: HTMLElement = await screen.findByRole("combobox", undefined, {
|
|
298
|
+
timeout: WAIT_TIMEOUT,
|
|
299
|
+
});
|
|
300
|
+
fireEvent.keyDown(combobox, { key: "ArrowDown", code: "ArrowDown" });
|
|
301
|
+
return screen.queryAllByRole("option");
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
type OptionTextsFunction = (options: Array<HTMLElement>) => Array<string>;
|
|
305
|
+
|
|
306
|
+
const optionTexts: OptionTextsFunction = (
|
|
307
|
+
options: Array<HTMLElement>,
|
|
308
|
+
): Array<string> => {
|
|
309
|
+
return options.map((option: HTMLElement) => {
|
|
310
|
+
return option.textContent || "";
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
type SelectLabelFunction = (name: string) => Promise<void>;
|
|
315
|
+
|
|
316
|
+
const selectLabel: SelectLabelFunction = async (
|
|
317
|
+
name: string,
|
|
318
|
+
): Promise<void> => {
|
|
319
|
+
const combobox: HTMLElement = await screen.findByRole("combobox", undefined, {
|
|
320
|
+
timeout: WAIT_TIMEOUT,
|
|
321
|
+
});
|
|
322
|
+
fireEvent.keyDown(combobox, { key: "ArrowDown", code: "ArrowDown" });
|
|
323
|
+
fireEvent.click(
|
|
324
|
+
await screen.findByText(name, undefined, { timeout: WAIT_TIMEOUT }),
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
type SubmitModalFunction = () => void;
|
|
329
|
+
|
|
330
|
+
const submitModal: SubmitModalFunction = (): void => {
|
|
331
|
+
fireEvent.click(screen.getByTestId("modal-footer-submit-button"));
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
interface LabelFormData {
|
|
335
|
+
labelIds: Array<string>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
type ModalSubmitFunction = (formData: LabelFormData) => Promise<void>;
|
|
339
|
+
|
|
340
|
+
type GetModalSubmitFunction = (title: string) => ModalSubmitFunction;
|
|
341
|
+
|
|
342
|
+
/*
|
|
343
|
+
* react-select cannot deliver an all-blank selection through the UI: the form's
|
|
344
|
+
* required check rejects a lone blank-valued option (`[""].toString()` is "")
|
|
345
|
+
* and hideSelectedOptions hides every other option sharing that same blank
|
|
346
|
+
* value the moment one is picked. The hook's contract with BasicFormModal is
|
|
347
|
+
* the onSubmit prop, so that boundary is driven directly instead.
|
|
348
|
+
*/
|
|
349
|
+
const getModalSubmit: GetModalSubmitFunction = (
|
|
350
|
+
title: string,
|
|
351
|
+
): ModalSubmitFunction => {
|
|
352
|
+
const modal: any = React.Children.toArray(
|
|
353
|
+
(capturedModals as any).props.children,
|
|
354
|
+
).find((child: any) => {
|
|
355
|
+
return child?.props?.title === title;
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
if (!modal) {
|
|
359
|
+
throw new Error(`No "${title}" modal is mounted.`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return modal.props.onSubmit as ModalSubmitFunction;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
interface DeferredListResult {
|
|
366
|
+
promise: Promise<any>;
|
|
367
|
+
resolve: (value: any) => void;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
type CreateDeferredListResultFunction = () => DeferredListResult;
|
|
371
|
+
|
|
372
|
+
const createDeferredListResult: CreateDeferredListResultFunction =
|
|
373
|
+
(): DeferredListResult => {
|
|
374
|
+
let settle: (value: any) => void = (): void => {};
|
|
375
|
+
|
|
376
|
+
const promise: Promise<any> = new Promise<any>(
|
|
377
|
+
(resolve: (value: any) => void): void => {
|
|
378
|
+
settle = resolve;
|
|
379
|
+
},
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
promise: promise,
|
|
384
|
+
resolve: (value: any): void => {
|
|
385
|
+
settle(value);
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
type ActAsyncFunction = (body: () => Promise<void>) => Promise<void>;
|
|
391
|
+
|
|
392
|
+
/*
|
|
393
|
+
* zone.js reschedules the microtask React's act() uses to check that its
|
|
394
|
+
* thenable was awaited, so it runs before `await` gets round to attaching a
|
|
395
|
+
* handler and every plain `await act(...)` is reported as un-awaited. Hooking
|
|
396
|
+
* then() up synchronously answers that check in time; the scope still exits
|
|
397
|
+
* exactly when act says it does.
|
|
398
|
+
*/
|
|
399
|
+
const actAsync: ActAsyncFunction = (
|
|
400
|
+
body: () => Promise<void>,
|
|
401
|
+
): Promise<void> => {
|
|
402
|
+
const scope: PromiseLike<void> = act(body) as unknown as PromiseLike<void>;
|
|
403
|
+
|
|
404
|
+
return new Promise<void>(
|
|
405
|
+
(resolve: () => void, reject: (reason: unknown) => void): void => {
|
|
406
|
+
scope.then(resolve, reject);
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
type GetUpdatedLabelIdsFunction = (callIndex: number) => Array<string>;
|
|
412
|
+
|
|
413
|
+
const getUpdatedLabelIds: GetUpdatedLabelIdsFunction = (
|
|
414
|
+
callIndex: number,
|
|
415
|
+
): Array<string> => {
|
|
416
|
+
const call: Array<any> = updateByIdMock.mock.calls[callIndex] as Array<any>;
|
|
417
|
+
return (call[0] as any).data.labels as Array<string>;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
type GetListCallFunction = (callIndex: number) => any;
|
|
421
|
+
|
|
422
|
+
const getListCall: GetListCallFunction = (callIndex: number): any => {
|
|
423
|
+
const call: Array<any> = getListMock.mock.calls[callIndex] as Array<any>;
|
|
424
|
+
return call[0];
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
type WaitForBulkRunToEndFunction = () => Promise<void>;
|
|
428
|
+
|
|
429
|
+
const waitForBulkRunToEnd: WaitForBulkRunToEndFunction =
|
|
430
|
+
async (): Promise<void> => {
|
|
431
|
+
await waitFor(
|
|
432
|
+
() => {
|
|
433
|
+
expect(onBulkActionEndMock).toHaveBeenCalledTimes(1);
|
|
434
|
+
},
|
|
435
|
+
{ timeout: WAIT_TIMEOUT },
|
|
436
|
+
);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
type AddAlphaOverCurrentReadFunction = (
|
|
440
|
+
currentRead: Monitor | null,
|
|
441
|
+
) => Promise<void>;
|
|
442
|
+
|
|
443
|
+
/*
|
|
444
|
+
* Shared driver for the two "the current read is unusable" cases: one item,
|
|
445
|
+
* one project label, add mode, with getItem answering whatever the caller
|
|
446
|
+
* wants. Assertions stay in the tests so a failure points at the real case.
|
|
447
|
+
*/
|
|
448
|
+
const addAlphaOverCurrentRead: AddAlphaOverCurrentReadFunction = async (
|
|
449
|
+
currentRead: Monitor | null,
|
|
450
|
+
): Promise<void> => {
|
|
451
|
+
getListMock.mockResolvedValue({
|
|
452
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
453
|
+
count: 1,
|
|
454
|
+
});
|
|
455
|
+
getItemMock.mockImplementation((): Promise<Monitor | null> => {
|
|
456
|
+
return Promise.resolve(currentRead);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
460
|
+
|
|
461
|
+
await waitForMountedLabelFetch();
|
|
462
|
+
|
|
463
|
+
clickTrigger("Add Labels");
|
|
464
|
+
await waitForModalTitle("Add Labels");
|
|
465
|
+
await selectLabel("Alpha");
|
|
466
|
+
submitModal();
|
|
467
|
+
|
|
468
|
+
await waitForBulkRunToEnd();
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
/*
|
|
472
|
+
* These mount the real Modal, Formik and react-select trees, so a cold
|
|
473
|
+
* ts-jest transform of that import graph can push individual tests past
|
|
474
|
+
* jest's 5s default - which CI, always starting cold, would report as a
|
|
475
|
+
* red build with no assertion having failed. The slowest test here runs
|
|
476
|
+
* in ~1.3s warm; 30s is the same ceiling App/jest.config.json already
|
|
477
|
+
* sets for its whole suite.
|
|
478
|
+
*/
|
|
479
|
+
jest.setTimeout(30000);
|
|
480
|
+
|
|
481
|
+
describe("useBulkLabelActions", () => {
|
|
482
|
+
beforeEach(() => {
|
|
483
|
+
/*
|
|
484
|
+
* resetAllMocks rather than clearAllMocks: implementations set by one test
|
|
485
|
+
* would otherwise silently answer another test's requests.
|
|
486
|
+
*/
|
|
487
|
+
jest.resetAllMocks();
|
|
488
|
+
|
|
489
|
+
progressSnapshots = [];
|
|
490
|
+
callOrder = [];
|
|
491
|
+
capturedActions = [];
|
|
492
|
+
capturedModals = null;
|
|
493
|
+
|
|
494
|
+
onProgressInfoMock.mockImplementation((progressInfo: any) => {
|
|
495
|
+
callOrder.push("progress");
|
|
496
|
+
progressSnapshots.push(
|
|
497
|
+
snapshotProgress(progressInfo as ProgressInfo<Monitor>),
|
|
498
|
+
);
|
|
499
|
+
});
|
|
500
|
+
onBulkActionStartMock.mockImplementation(() => {
|
|
501
|
+
callOrder.push("start");
|
|
502
|
+
});
|
|
503
|
+
onBulkActionEndMock.mockImplementation(() => {
|
|
504
|
+
callOrder.push("end");
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
getListMock.mockResolvedValue({ data: [], count: 0, skip: 0, limit: 0 });
|
|
508
|
+
updateByIdMock.mockResolvedValue({});
|
|
509
|
+
|
|
510
|
+
window.localStorage.clear();
|
|
511
|
+
window.sessionStorage.clear();
|
|
512
|
+
// ProjectUtil.getCurrentProjectId() reads pathname.split("/")[2].
|
|
513
|
+
window.history.replaceState({}, "", `/dashboard/${PROJECT_ID}/monitors`);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
afterEach(() => {
|
|
517
|
+
jest.restoreAllMocks();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("exposes Add Labels then Remove Labels, in that order", async () => {
|
|
521
|
+
render(<Harness items={[]} />);
|
|
522
|
+
|
|
523
|
+
await waitForMountedLabelFetch();
|
|
524
|
+
|
|
525
|
+
/*
|
|
526
|
+
* Consumers spread these into ModelTable's button list and BulkUpdateForm
|
|
527
|
+
* renders them in array order, so the order is part of the contract.
|
|
528
|
+
*/
|
|
529
|
+
expect(capturedActions).toHaveLength(2);
|
|
530
|
+
expect(capturedActions[0]!.title).toBe("Add Labels");
|
|
531
|
+
expect(capturedActions[1]!.title).toBe("Remove Labels");
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("keeps both actions in the non-destructive button group", async () => {
|
|
535
|
+
render(<Harness items={[]} />);
|
|
536
|
+
|
|
537
|
+
await waitForMountedLabelFetch();
|
|
538
|
+
|
|
539
|
+
expect(capturedActions[0]!.icon).toBe(IconProp.Label);
|
|
540
|
+
expect(capturedActions[0]!.buttonStyleType).toBe(ButtonStyleType.NORMAL);
|
|
541
|
+
expect(capturedActions[1]!.icon).toBe(IconProp.Close);
|
|
542
|
+
|
|
543
|
+
/*
|
|
544
|
+
* Remove Labels borrows the Close icon but must not borrow a danger style:
|
|
545
|
+
* BulkUpdateForm splits danger buttons into a separate red menu group
|
|
546
|
+
* behind a divider, which is for irreversible actions.
|
|
547
|
+
*/
|
|
548
|
+
expect(capturedActions[1]!.buttonStyleType).toBe(ButtonStyleType.NORMAL);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("leaves the click contract unconditional on both actions", async () => {
|
|
552
|
+
render(<Harness items={[]} />);
|
|
553
|
+
|
|
554
|
+
await waitForMountedLabelFetch();
|
|
555
|
+
|
|
556
|
+
expect(capturedActions).toHaveLength(2);
|
|
557
|
+
|
|
558
|
+
for (const action of capturedActions) {
|
|
559
|
+
/*
|
|
560
|
+
* A confirmMessage would make BulkUpdateForm open a ConfirmModal instead
|
|
561
|
+
* of calling onClick, and isVisible/disabled can drop the click entirely
|
|
562
|
+
* - each of those quietly changes how the modal gets opened.
|
|
563
|
+
*/
|
|
564
|
+
expect(action.confirmMessage).toBeUndefined();
|
|
565
|
+
expect(action.confirmTitle).toBeUndefined();
|
|
566
|
+
expect(action.isVisible).toBeUndefined();
|
|
567
|
+
expect(action.disabled).toBeUndefined();
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("renders no modal until an action is clicked", async () => {
|
|
572
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
573
|
+
|
|
574
|
+
await waitForMountedLabelFetch();
|
|
575
|
+
|
|
576
|
+
expect(screen.queryByTestId("modal")).toBeNull();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("fetches the project's labels once on mount with the full query", async () => {
|
|
580
|
+
render(<Harness items={[]} />);
|
|
581
|
+
|
|
582
|
+
await waitForMountedLabelFetch();
|
|
583
|
+
|
|
584
|
+
const call: any = getListCall(0);
|
|
585
|
+
|
|
586
|
+
expect(call.modelType).toBe(Label);
|
|
587
|
+
expect(call.query.projectId.toString()).toBe(PROJECT_ID);
|
|
588
|
+
expect(call.limit).toBe(LIMIT_PER_PROJECT);
|
|
589
|
+
expect(call.skip).toBe(0);
|
|
590
|
+
/*
|
|
591
|
+
* `color` has to be selected or the add dropdown loses the colour swatch;
|
|
592
|
+
* `labels` must not be, since Label has no labels of its own.
|
|
593
|
+
*/
|
|
594
|
+
expect(call.select).toEqual({ _id: true, name: true, color: true });
|
|
595
|
+
expect(call.sort).toEqual({ name: SortOrder.Ascending });
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("does not fetch labels at all when there is no current project", async () => {
|
|
599
|
+
window.history.replaceState({}, "", "/dashboard");
|
|
600
|
+
|
|
601
|
+
render(<Harness items={[]} />);
|
|
602
|
+
|
|
603
|
+
await waitFor(() => {
|
|
604
|
+
expect(screen.getByText("Trigger Add Labels")).toBeInTheDocument();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
/*
|
|
608
|
+
* The query used to go out as projectId: null, which matches nothing, so
|
|
609
|
+
* the user got an empty dropdown after a pointless round trip.
|
|
610
|
+
*/
|
|
611
|
+
expect(getListMock).not.toHaveBeenCalled();
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test("leaves the add dropdown empty when the label fetch fails", async () => {
|
|
615
|
+
/*
|
|
616
|
+
* Throwing from inside the call rather than handing back a promise that is
|
|
617
|
+
* already rejected (mockRejectedValue, or an equivalent Promise.reject):
|
|
618
|
+
* zone.js sees the rejection before the hook's catch is attached and
|
|
619
|
+
* prints the whole stack as a console error on every run.
|
|
620
|
+
*/
|
|
621
|
+
getListMock.mockImplementation(async (): Promise<never> => {
|
|
622
|
+
throw new Error("network down");
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
626
|
+
|
|
627
|
+
await waitForMountedLabelFetch();
|
|
628
|
+
|
|
629
|
+
clickTrigger("Add Labels");
|
|
630
|
+
await waitForModalTitle("Add Labels");
|
|
631
|
+
|
|
632
|
+
const options: Array<HTMLElement> = await openMenu();
|
|
633
|
+
|
|
634
|
+
expect(options).toHaveLength(0);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("keeps Remove Labels working when the project label fetch failed", async () => {
|
|
638
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
639
|
+
if (data.modelType === Label) {
|
|
640
|
+
throw new Error("network down");
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return {
|
|
644
|
+
data: [
|
|
645
|
+
makeItemWithLabels(ITEM_ONE_ID, [makeLabel(LABEL_BETA_ID, "Beta")]),
|
|
646
|
+
],
|
|
647
|
+
count: 1,
|
|
648
|
+
};
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
652
|
+
|
|
653
|
+
clickTrigger("Remove Labels");
|
|
654
|
+
await waitForModalTitle("Remove Labels");
|
|
655
|
+
|
|
656
|
+
/*
|
|
657
|
+
* Remove reads only from the per-item fetch, so the mount failure that
|
|
658
|
+
* empties the add dropdown must not degrade it.
|
|
659
|
+
*/
|
|
660
|
+
const options: Array<HTMLElement> = await openMenu();
|
|
661
|
+
|
|
662
|
+
expect(options).toHaveLength(1);
|
|
663
|
+
expect(options[0]!).toHaveTextContent("Beta");
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("adds the picked label without clobbering labels the user did not pick", async () => {
|
|
667
|
+
getListMock.mockResolvedValue({
|
|
668
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
669
|
+
count: 1,
|
|
670
|
+
});
|
|
671
|
+
getItemMock.mockResolvedValue(
|
|
672
|
+
makeItemWithLabels(ITEM_ONE_ID, [
|
|
673
|
+
makeLabel(LABEL_BETA_ID, "Beta"),
|
|
674
|
+
makeLabel(LABEL_GAMMA_ID, "Gamma"),
|
|
675
|
+
]),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
679
|
+
|
|
680
|
+
await waitForMountedLabelFetch();
|
|
681
|
+
|
|
682
|
+
clickTrigger("Add Labels");
|
|
683
|
+
await waitForModalTitle("Add Labels");
|
|
684
|
+
await selectLabel("Alpha");
|
|
685
|
+
submitModal();
|
|
686
|
+
|
|
687
|
+
await waitForBulkRunToEnd();
|
|
688
|
+
|
|
689
|
+
expect(getUpdatedLabelIds(0)).toEqual([
|
|
690
|
+
LABEL_BETA_ID,
|
|
691
|
+
LABEL_GAMMA_ID,
|
|
692
|
+
LABEL_ALPHA_ID,
|
|
693
|
+
]);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
test("de-dupes a picked label the item already carries", async () => {
|
|
697
|
+
getListMock.mockResolvedValue({
|
|
698
|
+
data: [
|
|
699
|
+
makeLabel(LABEL_ALPHA_ID, "Alpha"),
|
|
700
|
+
makeLabel(LABEL_BETA_ID, "Beta"),
|
|
701
|
+
],
|
|
702
|
+
count: 2,
|
|
703
|
+
});
|
|
704
|
+
getItemMock.mockResolvedValue(
|
|
705
|
+
makeItemWithLabels(ITEM_ONE_ID, [
|
|
706
|
+
makeLabel(LABEL_ALPHA_ID, "Alpha"),
|
|
707
|
+
makeLabel(LABEL_GAMMA_ID, "Gamma"),
|
|
708
|
+
]),
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
712
|
+
|
|
713
|
+
await waitForMountedLabelFetch();
|
|
714
|
+
|
|
715
|
+
clickTrigger("Add Labels");
|
|
716
|
+
await waitForModalTitle("Add Labels");
|
|
717
|
+
await selectLabel("Alpha");
|
|
718
|
+
await selectLabel("Beta");
|
|
719
|
+
submitModal();
|
|
720
|
+
|
|
721
|
+
await waitForBulkRunToEnd();
|
|
722
|
+
|
|
723
|
+
// Alpha was on the item and was picked again; it must land once.
|
|
724
|
+
expect(getUpdatedLabelIds(0)).toEqual([
|
|
725
|
+
LABEL_ALPHA_ID,
|
|
726
|
+
LABEL_GAMMA_ID,
|
|
727
|
+
LABEL_BETA_ID,
|
|
728
|
+
]);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("reads the item's current label ids before merging", async () => {
|
|
732
|
+
getListMock.mockResolvedValue({
|
|
733
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
734
|
+
count: 1,
|
|
735
|
+
});
|
|
736
|
+
getItemMock.mockResolvedValue(makeItemWithLabels(ITEM_ONE_ID, []));
|
|
737
|
+
|
|
738
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
739
|
+
|
|
740
|
+
await waitForMountedLabelFetch();
|
|
741
|
+
|
|
742
|
+
clickTrigger("Add Labels");
|
|
743
|
+
await waitForModalTitle("Add Labels");
|
|
744
|
+
await selectLabel("Alpha");
|
|
745
|
+
submitModal();
|
|
746
|
+
|
|
747
|
+
await waitForBulkRunToEnd();
|
|
748
|
+
|
|
749
|
+
const call: any = (getItemMock.mock.calls[0] as Array<any>)[0];
|
|
750
|
+
|
|
751
|
+
expect(call.modelType).toBe(Monitor);
|
|
752
|
+
expect(call.id.toString()).toBe(ITEM_ONE_ID);
|
|
753
|
+
/*
|
|
754
|
+
* `_id` is not cosmetic here: it is the only field that separates a real
|
|
755
|
+
* read of an unlabelled item from the empty body a deleted or filtered row
|
|
756
|
+
* answers with, and the guard below leans on it entirely.
|
|
757
|
+
*/
|
|
758
|
+
expect(call.select).toEqual({ _id: true, labels: { _id: true } });
|
|
759
|
+
expect(getUpdatedLabelIds(0)).toEqual([LABEL_ALPHA_ID]);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test("drops a dropdown option that came back without an id before merging", async () => {
|
|
763
|
+
const orphan: Label = new Label();
|
|
764
|
+
orphan.name = "Orphan";
|
|
765
|
+
|
|
766
|
+
getListMock.mockResolvedValue({
|
|
767
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha"), orphan],
|
|
768
|
+
count: 2,
|
|
769
|
+
});
|
|
770
|
+
getItemMock.mockResolvedValue(
|
|
771
|
+
makeItemWithLabels(ITEM_ONE_ID, [makeLabel(LABEL_GAMMA_ID, "Gamma")]),
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
775
|
+
|
|
776
|
+
await waitForMountedLabelFetch();
|
|
777
|
+
|
|
778
|
+
clickTrigger("Add Labels");
|
|
779
|
+
await waitForModalTitle("Add Labels");
|
|
780
|
+
await selectLabel("Orphan");
|
|
781
|
+
await selectLabel("Alpha");
|
|
782
|
+
submitModal();
|
|
783
|
+
|
|
784
|
+
await waitForBulkRunToEnd();
|
|
785
|
+
|
|
786
|
+
/*
|
|
787
|
+
* An id-less label renders as an option with an empty value; sending ""
|
|
788
|
+
* inside the labels array makes the API reject the whole item, so one
|
|
789
|
+
* unusable option would fail the entire bulk run.
|
|
790
|
+
*/
|
|
791
|
+
expect(getUpdatedLabelIds(0)).toEqual([LABEL_GAMMA_ID, LABEL_ALPHA_ID]);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
test("starts nothing and reports nothing when every submitted label id is blank", async () => {
|
|
795
|
+
getListMock.mockResolvedValue({
|
|
796
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
797
|
+
count: 1,
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID), makeItem(ITEM_TWO_ID)]} />);
|
|
801
|
+
|
|
802
|
+
await waitForMountedLabelFetch();
|
|
803
|
+
|
|
804
|
+
clickTrigger("Add Labels");
|
|
805
|
+
await waitForModalTitle("Add Labels");
|
|
806
|
+
|
|
807
|
+
await actAsync(async (): Promise<void> => {
|
|
808
|
+
await getModalSubmit("Add Labels")({ labelIds: ["", ""] });
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
/*
|
|
812
|
+
* Nothing survives the blank-id filter, so there is nothing to apply. The
|
|
813
|
+
* loop used to run anyway: every item matched the no-op short-circuit and
|
|
814
|
+
* was pushed onto successItems, so the user was told a bulk edit landed on
|
|
815
|
+
* their whole selection when not one label had been asked for.
|
|
816
|
+
*/
|
|
817
|
+
expect(onBulkActionStartMock).not.toHaveBeenCalled();
|
|
818
|
+
expect(onProgressInfoMock).not.toHaveBeenCalled();
|
|
819
|
+
expect(onBulkActionEndMock).not.toHaveBeenCalled();
|
|
820
|
+
expect(getItemMock).not.toHaveBeenCalled();
|
|
821
|
+
expect(updateByIdMock).not.toHaveBeenCalled();
|
|
822
|
+
// The submit still dismisses the form rather than leaving it hanging open.
|
|
823
|
+
expect(screen.queryByTestId("modal")).toBeNull();
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
test("fails an item whose current read carries no id instead of overwriting its labels", async () => {
|
|
827
|
+
/*
|
|
828
|
+
* This is the reachable case. A row deleted or filtered out between the
|
|
829
|
+
* table load and the action still answers HTTP 200, with an empty body
|
|
830
|
+
* that BaseModel.fromJSONObject turns into a truthy model carrying neither
|
|
831
|
+
* _id nor labels - so a plain null check never fires and add mode reads it
|
|
832
|
+
* as "this item has no labels".
|
|
833
|
+
*/
|
|
834
|
+
await addAlphaOverCurrentRead(BaseModel.fromJSONObject({}, Monitor));
|
|
835
|
+
|
|
836
|
+
/*
|
|
837
|
+
* Any update at all is the bug: the only thing the hook could have sent is
|
|
838
|
+
* `labels: [LABEL_ALPHA_ID]`, which is the item's entire label set replaced
|
|
839
|
+
* by the one label just picked.
|
|
840
|
+
*/
|
|
841
|
+
expect(updateByIdMock).not.toHaveBeenCalled();
|
|
842
|
+
expect(progressSnapshots[0]!.failedIds).toEqual([ITEM_ONE_ID]);
|
|
843
|
+
expect(progressSnapshots[0]!.failedMessages[0]).toBe(
|
|
844
|
+
"Could not read the current labels for this item.",
|
|
845
|
+
);
|
|
846
|
+
expect(progressSnapshots[0]!.successIds).toEqual([]);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test("fails an item whose current read comes back as null instead of overwriting its labels", async () => {
|
|
850
|
+
await addAlphaOverCurrentRead(null);
|
|
851
|
+
|
|
852
|
+
// ModelAPI.getItem is typed to allow null, so the guard has to cover it too.
|
|
853
|
+
expect(updateByIdMock).not.toHaveBeenCalled();
|
|
854
|
+
expect(progressSnapshots[0]!.failedIds).toEqual([ITEM_ONE_ID]);
|
|
855
|
+
expect(progressSnapshots[0]!.failedMessages[0]).toBe(
|
|
856
|
+
"Could not read the current labels for this item.",
|
|
857
|
+
);
|
|
858
|
+
expect(progressSnapshots[0]!.successIds).toEqual([]);
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
test("removes only the picked labels and keeps the rest", async () => {
|
|
862
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
863
|
+
if (data.modelType === Label) {
|
|
864
|
+
return { data: [], count: 0 };
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return {
|
|
868
|
+
data: [
|
|
869
|
+
makeItemWithLabels(ITEM_ONE_ID, [
|
|
870
|
+
makeLabel(LABEL_ALPHA_ID, "Alpha"),
|
|
871
|
+
makeLabel(LABEL_BETA_ID, "Beta"),
|
|
872
|
+
makeLabel(LABEL_GAMMA_ID, "Gamma"),
|
|
873
|
+
]),
|
|
874
|
+
],
|
|
875
|
+
count: 1,
|
|
876
|
+
};
|
|
877
|
+
});
|
|
878
|
+
getItemMock.mockResolvedValue(
|
|
879
|
+
makeItemWithLabels(ITEM_ONE_ID, [
|
|
880
|
+
makeLabel(LABEL_ALPHA_ID, "Alpha"),
|
|
881
|
+
makeLabel(LABEL_BETA_ID, "Beta"),
|
|
882
|
+
makeLabel(LABEL_GAMMA_ID, "Gamma"),
|
|
883
|
+
]),
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
887
|
+
|
|
888
|
+
clickTrigger("Remove Labels");
|
|
889
|
+
await waitForModalTitle("Remove Labels");
|
|
890
|
+
await selectLabel("Beta");
|
|
891
|
+
submitModal();
|
|
892
|
+
|
|
893
|
+
await waitForBulkRunToEnd();
|
|
894
|
+
|
|
895
|
+
expect(getUpdatedLabelIds(0)).toEqual([LABEL_ALPHA_ID, LABEL_GAMMA_ID]);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("skips the update but still counts the item as processed when nothing changes", async () => {
|
|
899
|
+
getListMock.mockResolvedValue({
|
|
900
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
901
|
+
count: 1,
|
|
902
|
+
});
|
|
903
|
+
getItemMock.mockResolvedValue(
|
|
904
|
+
makeItemWithLabels(ITEM_ONE_ID, [makeLabel(LABEL_ALPHA_ID, "Alpha")]),
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
908
|
+
|
|
909
|
+
await waitForMountedLabelFetch();
|
|
910
|
+
|
|
911
|
+
clickTrigger("Add Labels");
|
|
912
|
+
await waitForModalTitle("Add Labels");
|
|
913
|
+
await selectLabel("Alpha");
|
|
914
|
+
submitModal();
|
|
915
|
+
|
|
916
|
+
await waitForBulkRunToEnd();
|
|
917
|
+
|
|
918
|
+
/*
|
|
919
|
+
* A skipped write still has to advance the progress bar, otherwise a
|
|
920
|
+
* selection of already-labelled items looks like a stalled run.
|
|
921
|
+
*/
|
|
922
|
+
expect(updateByIdMock).not.toHaveBeenCalled();
|
|
923
|
+
expect(progressSnapshots[0]!.successIds).toEqual([ITEM_ONE_ID]);
|
|
924
|
+
expect(progressSnapshots[0]!.failedIds).toEqual([]);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
test("keeps going after one item fails and reports only that item as failed", async () => {
|
|
928
|
+
getListMock.mockResolvedValue({
|
|
929
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
930
|
+
count: 1,
|
|
931
|
+
});
|
|
932
|
+
getItemMock.mockImplementation(async (data: any): Promise<any> => {
|
|
933
|
+
if (data.id.toString() === ITEM_TWO_ID) {
|
|
934
|
+
throw new Error("item two is gone");
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return makeItemWithLabels(data.id.toString(), []);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
render(
|
|
941
|
+
<Harness
|
|
942
|
+
items={[
|
|
943
|
+
makeItem(ITEM_ONE_ID),
|
|
944
|
+
makeItem(ITEM_TWO_ID),
|
|
945
|
+
makeItem(ITEM_THREE_ID),
|
|
946
|
+
]}
|
|
947
|
+
/>,
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
await waitForMountedLabelFetch();
|
|
951
|
+
|
|
952
|
+
clickTrigger("Add Labels");
|
|
953
|
+
await waitForModalTitle("Add Labels");
|
|
954
|
+
await selectLabel("Alpha");
|
|
955
|
+
submitModal();
|
|
956
|
+
|
|
957
|
+
await waitForBulkRunToEnd();
|
|
958
|
+
|
|
959
|
+
expect(getItemMock).toHaveBeenCalledTimes(3);
|
|
960
|
+
expect(updateByIdMock).toHaveBeenCalledTimes(2);
|
|
961
|
+
|
|
962
|
+
const finalSnapshot: ProgressSnapshot = progressSnapshots[2]!;
|
|
963
|
+
|
|
964
|
+
expect(finalSnapshot.successIds).toEqual([ITEM_ONE_ID, ITEM_THREE_ID]);
|
|
965
|
+
expect(finalSnapshot.failedIds).toEqual([ITEM_TWO_ID]);
|
|
966
|
+
expect(finalSnapshot.failedMessages).toEqual(["item two is gone"]);
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
test("takes the failure message from API.getFriendlyMessage", async () => {
|
|
970
|
+
const friendlyMessage: MockFunction = jest.spyOn(
|
|
971
|
+
API,
|
|
972
|
+
"getFriendlyMessage",
|
|
973
|
+
) as unknown as MockFunction;
|
|
974
|
+
friendlyMessage.mockReturnValue("Something went wrong. Try again.");
|
|
975
|
+
|
|
976
|
+
const sentinel: Error = new Error("raw upstream text");
|
|
977
|
+
|
|
978
|
+
getListMock.mockResolvedValue({
|
|
979
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
980
|
+
count: 1,
|
|
981
|
+
});
|
|
982
|
+
/*
|
|
983
|
+
* mockRejectedValue hands zone.js a rejected promise it sees before the
|
|
984
|
+
* hook attaches its catch, and it prints the whole stack as a console
|
|
985
|
+
* error on every run. Throwing from inside the call keeps CI readable.
|
|
986
|
+
*/
|
|
987
|
+
getItemMock.mockImplementation(async (): Promise<never> => {
|
|
988
|
+
throw sentinel;
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
992
|
+
|
|
993
|
+
await waitForMountedLabelFetch();
|
|
994
|
+
|
|
995
|
+
clickTrigger("Add Labels");
|
|
996
|
+
await waitForModalTitle("Add Labels");
|
|
997
|
+
await selectLabel("Alpha");
|
|
998
|
+
submitModal();
|
|
999
|
+
|
|
1000
|
+
await waitForBulkRunToEnd();
|
|
1001
|
+
|
|
1002
|
+
// The raw error must go through the formatter, not into the UI verbatim.
|
|
1003
|
+
expect(friendlyMessage).toHaveBeenCalledWith(sentinel);
|
|
1004
|
+
expect(progressSnapshots[0]!.failedMessages).toEqual([
|
|
1005
|
+
"Something went wrong. Try again.",
|
|
1006
|
+
]);
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
test("fails an item that has no id without calling the API for it", async () => {
|
|
1010
|
+
getListMock.mockResolvedValue({
|
|
1011
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
1012
|
+
count: 1,
|
|
1013
|
+
});
|
|
1014
|
+
getItemMock.mockResolvedValue(makeItemWithLabels(ITEM_ONE_ID, []));
|
|
1015
|
+
|
|
1016
|
+
render(<Harness items={[new Monitor(), makeItem(ITEM_ONE_ID)]} />);
|
|
1017
|
+
|
|
1018
|
+
await waitForMountedLabelFetch();
|
|
1019
|
+
|
|
1020
|
+
clickTrigger("Add Labels");
|
|
1021
|
+
await waitForModalTitle("Add Labels");
|
|
1022
|
+
await selectLabel("Alpha");
|
|
1023
|
+
submitModal();
|
|
1024
|
+
|
|
1025
|
+
await waitForBulkRunToEnd();
|
|
1026
|
+
|
|
1027
|
+
expect(progressSnapshots[0]!.failedMessages).toEqual(["Item ID not found"]);
|
|
1028
|
+
// Only the second item is addressable, so only it may reach the API.
|
|
1029
|
+
expect(getItemMock).toHaveBeenCalledTimes(1);
|
|
1030
|
+
expect(updateByIdMock).toHaveBeenCalledTimes(1);
|
|
1031
|
+
expect(progressSnapshots[1]!.successIds).toEqual([ITEM_ONE_ID]);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
test("brackets the run with one start and one end around a progress call per item", async () => {
|
|
1035
|
+
getListMock.mockResolvedValue({
|
|
1036
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
1037
|
+
count: 1,
|
|
1038
|
+
});
|
|
1039
|
+
getItemMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1040
|
+
return makeItemWithLabels(data.id.toString(), []);
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID), makeItem(ITEM_TWO_ID)]} />);
|
|
1044
|
+
|
|
1045
|
+
await waitForMountedLabelFetch();
|
|
1046
|
+
|
|
1047
|
+
clickTrigger("Add Labels");
|
|
1048
|
+
await waitForModalTitle("Add Labels");
|
|
1049
|
+
await selectLabel("Alpha");
|
|
1050
|
+
submitModal();
|
|
1051
|
+
|
|
1052
|
+
await waitForBulkRunToEnd();
|
|
1053
|
+
|
|
1054
|
+
expect(callOrder).toEqual(["start", "progress", "progress", "end"]);
|
|
1055
|
+
expect(onBulkActionStartMock).toHaveBeenCalledTimes(1);
|
|
1056
|
+
expect(onProgressInfoMock).toHaveBeenCalledTimes(2);
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
test("reports the running success and failure split after each item", async () => {
|
|
1060
|
+
getListMock.mockResolvedValue({
|
|
1061
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
1062
|
+
count: 1,
|
|
1063
|
+
});
|
|
1064
|
+
getItemMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1065
|
+
if (data.id.toString() === ITEM_TWO_ID) {
|
|
1066
|
+
throw new Error("boom");
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
return makeItemWithLabels(data.id.toString(), []);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
render(
|
|
1073
|
+
<Harness
|
|
1074
|
+
items={[
|
|
1075
|
+
makeItem(ITEM_ONE_ID),
|
|
1076
|
+
makeItem(ITEM_TWO_ID),
|
|
1077
|
+
makeItem(ITEM_THREE_ID),
|
|
1078
|
+
]}
|
|
1079
|
+
/>,
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
await waitForMountedLabelFetch();
|
|
1083
|
+
|
|
1084
|
+
clickTrigger("Add Labels");
|
|
1085
|
+
await waitForModalTitle("Add Labels");
|
|
1086
|
+
await selectLabel("Alpha");
|
|
1087
|
+
submitModal();
|
|
1088
|
+
|
|
1089
|
+
await waitForBulkRunToEnd();
|
|
1090
|
+
|
|
1091
|
+
expect(progressSnapshots).toHaveLength(3);
|
|
1092
|
+
|
|
1093
|
+
/*
|
|
1094
|
+
* The item being worked on is spliced out of inProgressItems before its own
|
|
1095
|
+
* callback fires, so the remaining count is the work still queued.
|
|
1096
|
+
*/
|
|
1097
|
+
expect(progressSnapshots[0]).toEqual({
|
|
1098
|
+
totalIds: [ITEM_ONE_ID, ITEM_TWO_ID, ITEM_THREE_ID],
|
|
1099
|
+
inProgressIds: [ITEM_TWO_ID, ITEM_THREE_ID],
|
|
1100
|
+
successIds: [ITEM_ONE_ID],
|
|
1101
|
+
failedIds: [],
|
|
1102
|
+
failedMessages: [],
|
|
1103
|
+
});
|
|
1104
|
+
expect(progressSnapshots[1]).toEqual({
|
|
1105
|
+
totalIds: [ITEM_ONE_ID, ITEM_TWO_ID, ITEM_THREE_ID],
|
|
1106
|
+
inProgressIds: [ITEM_THREE_ID],
|
|
1107
|
+
successIds: [ITEM_ONE_ID],
|
|
1108
|
+
failedIds: [ITEM_TWO_ID],
|
|
1109
|
+
failedMessages: ["boom"],
|
|
1110
|
+
});
|
|
1111
|
+
expect(progressSnapshots[2]).toEqual({
|
|
1112
|
+
totalIds: [ITEM_ONE_ID, ITEM_TWO_ID, ITEM_THREE_ID],
|
|
1113
|
+
inProgressIds: [],
|
|
1114
|
+
successIds: [ITEM_ONE_ID, ITEM_THREE_ID],
|
|
1115
|
+
failedIds: [ITEM_TWO_ID],
|
|
1116
|
+
failedMessages: ["boom"],
|
|
1117
|
+
});
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
test("offers the de-duplicated union of the selection's labels, sorted by name", async () => {
|
|
1121
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1122
|
+
if (data.modelType === Label) {
|
|
1123
|
+
return { data: [], count: 0 };
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return {
|
|
1127
|
+
data: [
|
|
1128
|
+
makeItemWithLabels(ITEM_ONE_ID, [
|
|
1129
|
+
makeLabel(LABEL_ALPHA_ID, "Zulu"),
|
|
1130
|
+
makeLabel(LABEL_BETA_ID, "apple"),
|
|
1131
|
+
]),
|
|
1132
|
+
makeItemWithLabels(ITEM_TWO_ID, [
|
|
1133
|
+
makeLabel(LABEL_BETA_ID, "apple"),
|
|
1134
|
+
makeLabel(LABEL_GAMMA_ID, "Mike"),
|
|
1135
|
+
]),
|
|
1136
|
+
],
|
|
1137
|
+
count: 2,
|
|
1138
|
+
};
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID), makeItem(ITEM_TWO_ID)]} />);
|
|
1142
|
+
|
|
1143
|
+
clickTrigger("Remove Labels");
|
|
1144
|
+
await waitForModalTitle("Remove Labels");
|
|
1145
|
+
|
|
1146
|
+
const options: Array<HTMLElement> = await openMenu();
|
|
1147
|
+
|
|
1148
|
+
expect(options).toHaveLength(3);
|
|
1149
|
+
|
|
1150
|
+
/*
|
|
1151
|
+
* localeCompare, not codepoint order - a lowercase name would otherwise
|
|
1152
|
+
* sort below every capitalised one and look like a broken list.
|
|
1153
|
+
*/
|
|
1154
|
+
expect(optionTexts(options)).toEqual(["apple", "Mike", "Zulu"]);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
test("scopes the option fetch to the selected ids", async () => {
|
|
1158
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1159
|
+
if (data.modelType === Label) {
|
|
1160
|
+
return { data: [], count: 0 };
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
return {
|
|
1164
|
+
data: [
|
|
1165
|
+
makeItemWithLabels(ITEM_ONE_ID, [makeLabel(LABEL_ALPHA_ID, "Alpha")]),
|
|
1166
|
+
],
|
|
1167
|
+
count: 1,
|
|
1168
|
+
};
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID), makeItem(ITEM_TWO_ID)]} />);
|
|
1172
|
+
|
|
1173
|
+
clickTrigger("Remove Labels");
|
|
1174
|
+
|
|
1175
|
+
await waitFor(() => {
|
|
1176
|
+
expect(getListMock).toHaveBeenCalledTimes(2);
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
const call: any = getListCall(1);
|
|
1180
|
+
|
|
1181
|
+
expect(call.modelType).toBe(Monitor);
|
|
1182
|
+
expect(call.query._id).toBeInstanceOf(Includes);
|
|
1183
|
+
expect(call.query._id.values).toEqual([ITEM_ONE_ID, ITEM_TWO_ID]);
|
|
1184
|
+
expect(call.select).toEqual({
|
|
1185
|
+
labels: { _id: true, name: true, color: true },
|
|
1186
|
+
});
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
test("carries a label's colour into its remove option", async () => {
|
|
1190
|
+
const coloured: Label = makeLabel(LABEL_ALPHA_ID, "Alpha");
|
|
1191
|
+
coloured.color = Red;
|
|
1192
|
+
|
|
1193
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1194
|
+
if (data.modelType === Label) {
|
|
1195
|
+
return { data: [], count: 0 };
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
return {
|
|
1199
|
+
data: [
|
|
1200
|
+
makeItemWithLabels(ITEM_ONE_ID, [
|
|
1201
|
+
coloured,
|
|
1202
|
+
makeLabel(LABEL_BETA_ID, "Beta"),
|
|
1203
|
+
]),
|
|
1204
|
+
],
|
|
1205
|
+
count: 1,
|
|
1206
|
+
};
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
1210
|
+
|
|
1211
|
+
clickTrigger("Remove Labels");
|
|
1212
|
+
await waitForModalTitle("Remove Labels");
|
|
1213
|
+
|
|
1214
|
+
const options: Array<HTMLElement> = await openMenu();
|
|
1215
|
+
const alphaSwatch: Element | null =
|
|
1216
|
+
options[0]!.querySelector("span[title]");
|
|
1217
|
+
|
|
1218
|
+
expect(alphaSwatch).not.toBeNull();
|
|
1219
|
+
expect(alphaSwatch!.getAttribute("title")).toBe(Red.toString());
|
|
1220
|
+
// A colourless label must not inherit a swatch from anywhere.
|
|
1221
|
+
expect(options[1]!.querySelector("span[title]")).toBeNull();
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
test("offers nothing to remove for an empty selection", async () => {
|
|
1225
|
+
render(<Harness items={[]} />);
|
|
1226
|
+
|
|
1227
|
+
await waitForMountedLabelFetch();
|
|
1228
|
+
|
|
1229
|
+
clickTrigger("Remove Labels");
|
|
1230
|
+
await waitForModalTitle("No Labels to Remove");
|
|
1231
|
+
|
|
1232
|
+
// No addressable ids means there is nothing to ask the server about.
|
|
1233
|
+
expect(getListMock).toHaveBeenCalledTimes(1);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
test("offers nothing to remove when the option fetch fails, rather than the whole project's labels", async () => {
|
|
1237
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1238
|
+
if (data.modelType === Label) {
|
|
1239
|
+
return {
|
|
1240
|
+
data: [
|
|
1241
|
+
makeLabel(LABEL_ALPHA_ID, "Alpha"),
|
|
1242
|
+
makeLabel(LABEL_BETA_ID, "Beta"),
|
|
1243
|
+
],
|
|
1244
|
+
count: 2,
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
throw new Error("selection fetch failed");
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
1252
|
+
|
|
1253
|
+
clickTrigger("Remove Labels");
|
|
1254
|
+
await waitForModalTitle("No Labels to Remove");
|
|
1255
|
+
|
|
1256
|
+
/*
|
|
1257
|
+
* Falling back to the project label list here would offer labels the items
|
|
1258
|
+
* do not have, and removing one would look like it silently did nothing.
|
|
1259
|
+
*/
|
|
1260
|
+
expect(screen.queryByRole("combobox")).toBeNull();
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
test("the No Labels to Remove modal is a dead end with only a way out", async () => {
|
|
1264
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1265
|
+
if (data.modelType === Label) {
|
|
1266
|
+
return { data: [], count: 0 };
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
return { data: [makeItemWithLabels(ITEM_ONE_ID, [])], count: 1 };
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
1273
|
+
|
|
1274
|
+
clickTrigger("Remove Labels");
|
|
1275
|
+
await waitForModalTitle("No Labels to Remove");
|
|
1276
|
+
|
|
1277
|
+
expect(screen.getByTestId("modal-footer-close-button")).toHaveTextContent(
|
|
1278
|
+
"Close",
|
|
1279
|
+
);
|
|
1280
|
+
// There is nothing to submit, so offering a submit button would be a trap.
|
|
1281
|
+
expect(screen.queryByTestId("modal-footer-submit-button")).toBeNull();
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
test("opening Remove Labels closes an open Add Labels modal", async () => {
|
|
1285
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1286
|
+
if (data.modelType === Label) {
|
|
1287
|
+
return { data: [makeLabel(LABEL_ALPHA_ID, "Alpha")], count: 1 };
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
return {
|
|
1291
|
+
data: [
|
|
1292
|
+
makeItemWithLabels(ITEM_ONE_ID, [makeLabel(LABEL_BETA_ID, "Beta")]),
|
|
1293
|
+
],
|
|
1294
|
+
count: 1,
|
|
1295
|
+
};
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
1299
|
+
|
|
1300
|
+
clickTrigger("Add Labels");
|
|
1301
|
+
await waitForModalTitle("Add Labels");
|
|
1302
|
+
|
|
1303
|
+
clickTrigger("Remove Labels");
|
|
1304
|
+
await waitForModalTitle("Remove Labels");
|
|
1305
|
+
|
|
1306
|
+
/*
|
|
1307
|
+
* The two modals are independent siblings sharing one set of action props;
|
|
1308
|
+
* stacked, whichever submits first leaves the other silently inert.
|
|
1309
|
+
*/
|
|
1310
|
+
expect(screen.getAllByTestId("modal-title")).toHaveLength(1);
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
test("opening Add Labels closes an open Remove Labels modal", async () => {
|
|
1314
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1315
|
+
if (data.modelType === Label) {
|
|
1316
|
+
return { data: [makeLabel(LABEL_ALPHA_ID, "Alpha")], count: 1 };
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
return {
|
|
1320
|
+
data: [
|
|
1321
|
+
makeItemWithLabels(ITEM_ONE_ID, [makeLabel(LABEL_BETA_ID, "Beta")]),
|
|
1322
|
+
],
|
|
1323
|
+
count: 1,
|
|
1324
|
+
};
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
1328
|
+
|
|
1329
|
+
clickTrigger("Remove Labels");
|
|
1330
|
+
await waitForModalTitle("Remove Labels");
|
|
1331
|
+
|
|
1332
|
+
clickTrigger("Add Labels");
|
|
1333
|
+
await waitForModalTitle("Add Labels");
|
|
1334
|
+
|
|
1335
|
+
expect(screen.getAllByTestId("modal-title")).toHaveLength(1);
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
test("re-opening the remove modal re-reads the new selection instead of reusing the old options", async () => {
|
|
1339
|
+
getListMock.mockImplementation(async (data: any): Promise<any> => {
|
|
1340
|
+
if (data.modelType === Label) {
|
|
1341
|
+
return { data: [], count: 0 };
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const ids: Array<string> = data.query._id.values as Array<string>;
|
|
1345
|
+
|
|
1346
|
+
if (ids.includes(ITEM_ONE_ID)) {
|
|
1347
|
+
return {
|
|
1348
|
+
data: [
|
|
1349
|
+
makeItemWithLabels(ITEM_ONE_ID, [
|
|
1350
|
+
makeLabel(LABEL_ALPHA_ID, "Alpha"),
|
|
1351
|
+
]),
|
|
1352
|
+
],
|
|
1353
|
+
count: 1,
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
return {
|
|
1358
|
+
data: [
|
|
1359
|
+
makeItemWithLabels(ITEM_TWO_ID, [makeLabel(LABEL_BETA_ID, "Beta")]),
|
|
1360
|
+
],
|
|
1361
|
+
count: 1,
|
|
1362
|
+
};
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
const { rerender } = render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
1366
|
+
|
|
1367
|
+
clickTrigger("Remove Labels");
|
|
1368
|
+
await waitForModalTitle("Remove Labels");
|
|
1369
|
+
|
|
1370
|
+
const firstOptions: Array<HTMLElement> = await openMenu();
|
|
1371
|
+
|
|
1372
|
+
expect(firstOptions[0]!).toHaveTextContent("Alpha");
|
|
1373
|
+
|
|
1374
|
+
fireEvent.click(screen.getByTestId("modal-footer-close-button"));
|
|
1375
|
+
|
|
1376
|
+
rerender(<Harness items={[makeItem(ITEM_TWO_ID)]} />);
|
|
1377
|
+
|
|
1378
|
+
clickTrigger("Remove Labels");
|
|
1379
|
+
await waitForModalTitle("Remove Labels");
|
|
1380
|
+
|
|
1381
|
+
await waitFor(() => {
|
|
1382
|
+
expect(getListMock).toHaveBeenCalledTimes(3);
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
const secondOptions: Array<HTMLElement> = await openMenu();
|
|
1386
|
+
|
|
1387
|
+
/*
|
|
1388
|
+
* The options are keyed to the selection, so a cached list from the last
|
|
1389
|
+
* open would offer labels the newly selected item does not have.
|
|
1390
|
+
*/
|
|
1391
|
+
expect(secondOptions).toHaveLength(1);
|
|
1392
|
+
expect(secondOptions[0]!).toHaveTextContent("Beta");
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
test("keeps the newest selection's remove options when an older fetch settles last", async () => {
|
|
1396
|
+
const firstSelectionFetch: DeferredListResult = createDeferredListResult();
|
|
1397
|
+
const secondSelectionFetch: DeferredListResult = createDeferredListResult();
|
|
1398
|
+
|
|
1399
|
+
getListMock.mockImplementation((data: any): Promise<any> => {
|
|
1400
|
+
if (data.modelType === Label) {
|
|
1401
|
+
return Promise.resolve({ data: [], count: 0 });
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const ids: Array<string> = data.query._id.values as Array<string>;
|
|
1405
|
+
|
|
1406
|
+
return ids.includes(ITEM_ONE_ID)
|
|
1407
|
+
? firstSelectionFetch.promise
|
|
1408
|
+
: secondSelectionFetch.promise;
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
render(<Harness items={[]} />);
|
|
1412
|
+
|
|
1413
|
+
await waitForMountedLabelFetch();
|
|
1414
|
+
|
|
1415
|
+
/*
|
|
1416
|
+
* Driven through onClick rather than the trigger button because the two
|
|
1417
|
+
* opens have to carry different selections while both fetches are still
|
|
1418
|
+
* in flight, which one mounted Harness cannot express.
|
|
1419
|
+
*/
|
|
1420
|
+
const runs: Array<Promise<void>> = [];
|
|
1421
|
+
|
|
1422
|
+
await actAsync(async (): Promise<void> => {
|
|
1423
|
+
runs.push(
|
|
1424
|
+
capturedActions[1]!.onClick(makeActionProps([makeItem(ITEM_ONE_ID)])),
|
|
1425
|
+
);
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
await actAsync(async (): Promise<void> => {
|
|
1429
|
+
runs.push(
|
|
1430
|
+
capturedActions[1]!.onClick(makeActionProps([makeItem(ITEM_TWO_ID)])),
|
|
1431
|
+
);
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
await waitFor(() => {
|
|
1435
|
+
expect(getListMock).toHaveBeenCalledTimes(3);
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
// The newest selection answers first...
|
|
1439
|
+
await actAsync(async (): Promise<void> => {
|
|
1440
|
+
secondSelectionFetch.resolve({
|
|
1441
|
+
data: [
|
|
1442
|
+
makeItemWithLabels(ITEM_TWO_ID, [makeLabel(LABEL_BETA_ID, "Beta")]),
|
|
1443
|
+
],
|
|
1444
|
+
count: 1,
|
|
1445
|
+
});
|
|
1446
|
+
await runs[1]!;
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
// ...and the abandoned first selection lands afterwards.
|
|
1450
|
+
await actAsync(async (): Promise<void> => {
|
|
1451
|
+
firstSelectionFetch.resolve({
|
|
1452
|
+
data: [
|
|
1453
|
+
makeItemWithLabels(ITEM_ONE_ID, [makeLabel(LABEL_ALPHA_ID, "Alpha")]),
|
|
1454
|
+
],
|
|
1455
|
+
count: 1,
|
|
1456
|
+
});
|
|
1457
|
+
await runs[0]!;
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
await waitForModalTitle("Remove Labels");
|
|
1461
|
+
|
|
1462
|
+
const options: Array<HTMLElement> = await openMenu();
|
|
1463
|
+
|
|
1464
|
+
/*
|
|
1465
|
+
* Last writer wins without the request stamp, and the slow first fetch is
|
|
1466
|
+
* the last writer here: the user would be offered Alpha, a label the item
|
|
1467
|
+
* they actually selected does not carry, and removing it would appear to
|
|
1468
|
+
* do nothing.
|
|
1469
|
+
*/
|
|
1470
|
+
expect(optionTexts(options)).toEqual(["Beta"]);
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
test("blocks a submit with nothing picked and touches no API", async () => {
|
|
1474
|
+
getListMock.mockResolvedValue({
|
|
1475
|
+
data: [makeLabel(LABEL_ALPHA_ID, "Alpha")],
|
|
1476
|
+
count: 1,
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
render(<Harness items={[makeItem(ITEM_ONE_ID)]} />);
|
|
1480
|
+
|
|
1481
|
+
await waitForMountedLabelFetch();
|
|
1482
|
+
|
|
1483
|
+
clickTrigger("Add Labels");
|
|
1484
|
+
await waitForModalTitle("Add Labels");
|
|
1485
|
+
|
|
1486
|
+
submitModal();
|
|
1487
|
+
|
|
1488
|
+
expect(
|
|
1489
|
+
await screen.findByText("Select Labels is required.", undefined, {
|
|
1490
|
+
timeout: WAIT_TIMEOUT,
|
|
1491
|
+
}),
|
|
1492
|
+
).toBeInTheDocument();
|
|
1493
|
+
|
|
1494
|
+
/*
|
|
1495
|
+
* An empty submit would otherwise start a bulk run that reads and rewrites
|
|
1496
|
+
* every selected item to no effect.
|
|
1497
|
+
*/
|
|
1498
|
+
expect(onBulkActionStartMock).not.toHaveBeenCalled();
|
|
1499
|
+
expect(getItemMock).not.toHaveBeenCalled();
|
|
1500
|
+
expect(updateByIdMock).not.toHaveBeenCalled();
|
|
1501
|
+
});
|
|
1502
|
+
});
|