@oneuptime/common 14.0.1 → 14.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/AlertEpisodeMember.ts +8 -1
- package/Models/DatabaseModels/AutoRemediationSuggestion.ts +63 -0
- package/Models/DatabaseModels/IncidentEpisodeMember.ts +8 -1
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +9 -2
- package/Models/DatabaseModels/IncidentPublicNote.ts +9 -2
- package/Models/DatabaseModels/KubernetesCluster.ts +429 -0
- package/Models/DatabaseModels/Monitor.ts +48 -0
- package/Models/DatabaseModels/Runner.ts +5 -4
- package/Models/DatabaseModels/RunnerJob.ts +66 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +10 -2
- package/Models/DatabaseModels/WorkflowVariable.ts +479 -2
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +79 -25
- package/Server/API/AIInvestigationAPI.ts +341 -0
- package/Server/API/AutoRemediationAPI.ts +184 -2
- package/Server/API/CommonAPI.ts +70 -0
- package/Server/API/KubernetesClusterAiAccessAPI.ts +793 -0
- package/Server/API/MicrosoftTeamsAPI.ts +770 -315
- package/Server/API/MonitorAPI.ts +140 -0
- package/Server/API/OnCallScheduleTimelineAPI.ts +5 -21
- package/Server/API/SlackAPI.ts +447 -99
- package/Server/API/StatusPageAPI.ts +146 -26
- package/Server/API/TelemetryAPI.ts +216 -5
- package/Server/API/UserTwoFactorBackupCodeAPI.ts +1 -0
- package/Server/API/WorkflowVariableAPI.ts +156 -0
- package/Server/Infrastructure/GlobalCache.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.ts +97 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.ts +129 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.ts +20 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.ts +38 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +28 -0
- package/Server/Middleware/TelemetryIngest.ts +47 -2
- package/Server/Middleware/UserAuthorization.ts +56 -2
- package/Server/Services/AIAgentService.ts +1 -0
- package/Server/Services/AccessTokenService.ts +121 -48
- package/Server/Services/AlertEpisodeMemberService.ts +9 -0
- package/Server/Services/AlertEpisodeService.ts +14 -1
- package/Server/Services/AlertService.ts +134 -22
- package/Server/Services/AutoRemediationRuleEngineService.ts +968 -39
- package/Server/Services/AutoRemediationRuleService.ts +118 -0
- package/Server/Services/BillingService.ts +1 -0
- package/Server/Services/InboundEmail/Providers/SendGridInboundProvider.ts +16 -22
- package/Server/Services/IncidentEpisodeMemberService.ts +9 -0
- package/Server/Services/IncidentEpisodePublicNoteService.ts +66 -0
- package/Server/Services/IncidentEpisodeRoleMemberService.ts +27 -0
- package/Server/Services/IncidentEpisodeService.ts +14 -1
- package/Server/Services/IncidentGroupingEngineService.ts +11 -0
- package/Server/Services/IncidentMemberService.ts +21 -0
- package/Server/Services/IncidentPublicNoteService.ts +63 -0
- package/Server/Services/IncidentService.ts +120 -20
- package/Server/Services/IncidentStateTimelineService.ts +15 -0
- package/Server/Services/IncidentTemplateService.ts +83 -3
- package/Server/Services/KubernetesClusterAiAccessService.ts +1943 -0
- package/Server/Services/KubernetesClusterService.ts +1570 -1
- package/Server/Services/LogAggregationService.ts +14 -6
- package/Server/Services/MonitorService.ts +651 -52
- package/Server/Services/MonitorStatusTimelineService.ts +639 -2
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +3 -0
- package/Server/Services/OnCallNotificationAlertingService.ts +36 -12
- package/Server/Services/OnCallSetupReminderService.ts +1 -0
- package/Server/Services/ProbeService.ts +7 -1
- package/Server/Services/ProjectService.ts +11 -1
- package/Server/Services/RunbookCredentialService.ts +26 -1
- package/Server/Services/RunbookSecretService.ts +36 -0
- package/Server/Services/RunnerJobService.ts +941 -23
- package/Server/Services/RunnerService.ts +439 -6
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +66 -0
- package/Server/Services/ScheduledMaintenanceService.ts +124 -19
- package/Server/Services/ScheduledMaintenanceTemplateService.ts +82 -3
- package/Server/Services/ServiceLevelObjectiveService.ts +14 -1
- package/Server/Services/StatusPagePrivateUserService.ts +1 -0
- package/Server/Services/StatusPageService.ts +301 -74
- package/Server/Services/StatusPageSubscriberService.ts +4 -0
- package/Server/Services/TeamMemberService.ts +309 -7
- package/Server/Services/TraceAggregationService.ts +10 -6
- package/Server/Services/UserEmailService.ts +1 -0
- package/Server/Services/UserNotificationMethodAdminService.ts +1 -0
- package/Server/Services/UserNotificationRuleAdminService.ts +1 -0
- package/Server/Services/UserNotificationRuleService.ts +4 -0
- package/Server/Services/UserService.ts +122 -1
- package/Server/Services/WorkflowVariableService.ts +593 -8
- package/Server/Services/WorkspaceNotificationRuleService.ts +588 -8
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +170 -1
- package/Server/Types/Database/QueryHelper.ts +22 -0
- package/Server/Types/Markdown.ts +502 -27
- package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +182 -0
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +525 -0
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +854 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentAuth.ts +22 -2
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1916 -307
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1601 -74
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +139 -7
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +58 -1
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +54 -1
- package/Server/Utils/AI/Toolbox/AIActionTools.ts +14 -6
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +4 -2
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +1341 -150
- package/Server/Utils/AutoRemediation/RemediationVerifier.ts +375 -4
- package/Server/Utils/Database/AffectedResourceRelations.ts +127 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +240 -1
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +1 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +83 -9
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +5 -1
- package/Server/Utils/Monitor/MonitorIncident.ts +104 -9
- package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +124 -7
- package/Server/Utils/Monitor/ProbeMonitorsNotification.ts +407 -0
- package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +1 -0
- package/Server/Utils/PushNotificationUtil.ts +45 -0
- package/Server/Utils/Rules/OwnerRuleAssignment.ts +40 -3
- package/Server/Utils/Runbook/RunbookExecutePermission.ts +56 -6
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +223 -33
- package/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.ts +559 -0
- package/Server/Utils/StartServer.ts +4 -3
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +528 -0
- package/Server/Utils/TwoFactorBackupCodeNotification.ts +1 -0
- package/Server/Utils/UserPermission/UserPermission.ts +16 -0
- package/Server/Utils/Workflow/OAuth2TokenClient.ts +704 -0
- package/Server/Utils/Workflow/WorkflowVariableOAuthToken.ts +318 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +47 -7
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +57 -16
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +193 -214
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +57 -13
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.ts +54 -17
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +179 -161
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +281 -167
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.ts +114 -0
- package/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.ts +895 -0
- package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +7 -9
- package/Server/Utils/Workspace/Slack/Actions/Alert.ts +96 -227
- package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +93 -214
- package/Server/Utils/Workspace/Slack/Actions/Auth.ts +30 -3
- package/Server/Utils/Workspace/Slack/Actions/Authorization.ts +99 -0
- package/Server/Utils/Workspace/Slack/Actions/Incident.ts +117 -283
- package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +101 -196
- package/Server/Utils/Workspace/Slack/Actions/ReactionNote.ts +329 -0
- package/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.ts +111 -287
- package/Server/Utils/Workspace/Slack/Slack.ts +86 -17
- package/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
- package/Server/Utils/Workspace/Workspace.ts +171 -54
- package/Server/Utils/Workspace/WorkspaceActionAuthorization.ts +252 -0
- package/Server/Utils/Workspace/WorkspaceBase.ts +39 -0
- package/Server/Utils/Workspace/WorkspaceOAuthState.ts +280 -0
- package/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.ts +96 -0
- package/Server/Utils/Workspace/WorkspaceReactionNote.ts +620 -0
- package/Tests/App/Dashboard/AttributesJSONDashboardSurfaces.test.tsx +496 -0
- package/Tests/App/Dashboard/AutoRemediationRulesTableCommandRunners.test.tsx +152 -0
- package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +32 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +794 -0
- package/Tests/App/Dashboard/CreateOAuthWorkflowVariableModal.test.tsx +840 -0
- package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +653 -0
- package/Tests/App/Dashboard/DatabaseMonitorStepForm.test.tsx +197 -0
- package/Tests/App/Dashboard/DatabaseMonitorView.test.tsx +376 -0
- package/Tests/App/Dashboard/DisabledWarningRobustness.test.tsx +262 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +2080 -0
- package/Tests/App/Dashboard/EventNotesPages.test.tsx +486 -0
- package/Tests/App/Dashboard/EventNotesUtil.test.ts +933 -0
- package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +218 -0
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +665 -0
- package/Tests/App/Dashboard/IncidentEpisodeFeedPublicNoteNotifyDefault.test.tsx +494 -0
- package/Tests/App/Dashboard/IncidentEpisodeOverviewNotifyDefault.test.tsx +706 -0
- package/Tests/App/Dashboard/IncidentFeedPublicNoteNotifyDefault.test.tsx +472 -0
- package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +858 -0
- package/Tests/App/Dashboard/IncomingEmailAddressSettings.test.tsx +567 -0
- package/Tests/App/Dashboard/InvestigationClusterToolFormat.test.ts +241 -0
- package/Tests/App/Dashboard/InvestigationEvidenceKubectl.test.tsx +322 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +12 -0
- package/Tests/App/Dashboard/InvestigationPanelClusterAccess.test.tsx +476 -0
- package/Tests/App/Dashboard/InvestigationRunDetailsKubectl.test.tsx +383 -0
- package/Tests/App/Dashboard/KubectlActivitySummary.test.tsx +366 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +2886 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +2688 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +915 -0
- package/Tests/App/Dashboard/LayerDateTimeFieldTimezoneName.test.tsx +66 -0
- package/Tests/App/Dashboard/LogMonitorPreview.test.tsx +174 -0
- package/Tests/App/Dashboard/MonitorFeedRefresh.test.tsx +279 -0
- package/Tests/App/Dashboard/MonitorMetricQueryConfig.test.ts +244 -0
- package/Tests/App/Dashboard/MonitorOpenWorkCard.test.tsx +381 -0
- package/Tests/App/Dashboard/MonitorOpenWorkHook.test.tsx +784 -0
- package/Tests/App/Dashboard/MonitorOverviewData.test.tsx +2199 -0
- package/Tests/App/Dashboard/MonitorOverviewHero.test.tsx +1169 -0
- package/Tests/App/Dashboard/MonitorOverviewInput.test.ts +1127 -0
- package/Tests/App/Dashboard/MonitorOverviewPage.test.tsx +2136 -0
- package/Tests/App/Dashboard/MonitorOverviewSelectReadable.test.ts +368 -0
- package/Tests/App/Dashboard/MonitorOverviewStatBar.test.tsx +526 -0
- package/Tests/App/Dashboard/MonitorOverviewSupportingCards.test.tsx +542 -0
- package/Tests/App/Dashboard/MonitorOwnersCombine.test.ts +299 -0
- package/Tests/App/Dashboard/MonitorProbesCard.test.tsx +602 -0
- package/Tests/App/Dashboard/MonitorResponseTimeCard.test.tsx +386 -0
- package/Tests/App/Dashboard/MonitorSetupAndConnectionCards.test.tsx +612 -0
- package/Tests/App/Dashboard/MonitorStatusChangesCard.test.tsx +257 -0
- package/Tests/App/Dashboard/MonitorSummaryTestMonitorButton.test.tsx +360 -1
- package/Tests/App/Dashboard/MonitorUptimeHistoryCard.test.tsx +835 -0
- package/Tests/App/Dashboard/MonitorUptimeSummaryHook.test.tsx +971 -0
- package/Tests/App/Dashboard/MonitorViewLayout.test.tsx +580 -0
- package/Tests/App/Dashboard/OnCallLayerRestrictionTimesPreserved.test.tsx +441 -0
- package/Tests/App/Dashboard/OnCallNavbar.test.tsx +214 -0
- package/Tests/App/Dashboard/OnCallSchedulePreviewCalendarDisplay.test.tsx +1389 -0
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +205 -1
- package/Tests/App/Dashboard/OnCallScheduleTimeInputsSettingsZone.test.tsx +198 -0
- package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +524 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardApproval.test.tsx +702 -0
- package/Tests/App/Dashboard/RemoveUserFromProject.test.tsx +427 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +4 -0
- package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +3 -1
- package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +166 -0
- package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +622 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceChangeStateNotifyDefault.test.tsx +1491 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedPublicNoteNotifyDefault.test.tsx +489 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewNotifyDefault.test.tsx +909 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +14 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceWindowStatsTranslation.test.tsx +307 -0
- package/Tests/App/Dashboard/SecurityEventAttributeColumnPicker.test.tsx +542 -0
- package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +174 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsEmptyState.test.tsx +973 -212
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +491 -52
- package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +301 -0
- package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +158 -1
- package/Tests/App/Dashboard/SecurityEventProviderTile.test.tsx +869 -0
- package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +959 -94
- package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +2 -0
- package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +274 -1
- package/Tests/App/Dashboard/TeamMembershipRemoval.test.ts +900 -0
- package/Tests/App/Dashboard/TeamMembershipRemovalPages.test.tsx +460 -0
- package/Tests/App/Dashboard/TimezoneElements.test.tsx +321 -0
- package/Tests/App/Dashboard/TimezoneSelectButton.test.tsx +634 -0
- package/Tests/App/Dashboard/TraceExplorer.test.tsx +260 -0
- package/Tests/App/Dashboard/TraceMonitorPreview.test.tsx +182 -0
- package/Tests/App/Dashboard/UserTimezoneInit.test.tsx +544 -0
- package/Tests/App/Dashboard/WorkflowVariableModals.test.tsx +1630 -0
- package/Tests/App/Dashboard/WorkflowVariableRoutes.test.tsx +603 -0
- package/Tests/App/Dashboard/WorkflowVariableTokenStatus.test.tsx +589 -0
- package/Tests/App/Dashboard/WorkflowVariableUtil.test.ts +1543 -0
- package/Tests/App/Dashboard/WorkflowVariableView.test.tsx +3227 -0
- package/Tests/App/Dashboard/WorkflowVariablesTable.test.tsx +1277 -395
- package/Tests/App/StatusPage/StatusPageOverviewMonitorGroupUptime.test.tsx +714 -0
- package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.NewYork.test.tsx +1009 -0
- package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.test.tsx +1092 -0
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +5 -0
- package/Tests/Models/DatabaseModels/KubernetesClusterAiAccessColumns.test.ts +520 -0
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +66 -2
- package/Tests/Models/DatabaseModels/WorkspaceProjectAuthTokenAccessControl.test.ts +236 -0
- package/Tests/Models/IncidentEpisodePublicNoteNotifyColumnContract.test.ts +103 -0
- package/Tests/Models/IncidentPublicNoteNotifyColumnContract.test.ts +96 -0
- package/Tests/Models/ScheduledMaintenancePublicNoteNotifyColumnContract.test.ts +111 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +976 -0
- package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +836 -0
- package/Tests/Server/API/CommonAPIAssertCanCreateTable.test.ts +565 -0
- package/Tests/Server/API/CommonAPIAssertCanReadTable.test.ts +282 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +1744 -0
- package/Tests/Server/API/LogHistogramAPI.test.ts +371 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +125 -30
- package/Tests/Server/API/MicrosoftTeamsOAuthState.test.ts +1121 -0
- package/Tests/Server/API/MonitorUptimeSummaryAPI.test.ts +685 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +602 -1
- package/Tests/Server/API/SlackChannelCacheAPI.test.ts +358 -0
- package/Tests/Server/API/SlackOAuthState.test.ts +688 -0
- package/Tests/Server/API/StatusPageOverviewMonitorGroupUptime.test.ts +527 -0
- package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +2 -0
- package/Tests/Server/API/StatusPageUptimeAPI.test.ts +427 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +4 -0
- package/Tests/Server/API/WorkflowVariableAPI.test.ts +404 -0
- package/Tests/Server/API/WorkspaceOAuthTestHelpers.ts +289 -0
- package/Tests/Server/API/WorkspaceSendTestNotificationRoutes.test.ts +2064 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +86 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +589 -0
- package/Tests/Server/Infrastructure/Postgres/AddOAuth2WorkflowVariablesMigration.test.ts +254 -0
- package/Tests/Server/Middleware/BlockedUserMiddleware.test.ts +403 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +13 -0
- package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +2 -0
- package/Tests/Server/Middleware/RemovedProjectMemberAuthorization.test.ts +469 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +77 -1
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +211 -0
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +358 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +97 -0
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +2 -0
- package/Tests/Server/Services/AccessTokenProjectUserGrant.test.ts +14 -2
- package/Tests/Server/Services/AccessTokenRemovedMemberAccess.test.ts +1044 -0
- package/Tests/Server/Services/AddOwnersIdempotency.test.ts +5 -0
- package/Tests/Server/Services/AlertEpisodeMemberCreatePermission.test.ts +139 -0
- package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +1361 -0
- package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +375 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +2384 -0
- package/Tests/Server/Services/FindOwnersProjectMembership.test.ts +267 -0
- package/Tests/Server/Services/InboundEmail/SendGridInboundProviderAddress.test.ts +90 -0
- package/Tests/Server/Services/IncidentEpisodeMemberCreatePermission.test.ts +143 -0
- package/Tests/Server/Services/IncidentEpisodePublicNoteNotifyDefault.test.ts +805 -0
- package/Tests/Server/Services/IncidentPublicNoteNotifyDefault.test.ts +724 -0
- package/Tests/Server/Services/IncidentRoleProjectMembership.test.ts +233 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessAgentRunnerAssignments.test.ts +372 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessBindingGuard.test.ts +1876 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +620 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +421 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +3662 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessSubjectClusters.test.ts +405 -0
- package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +975 -0
- package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +497 -0
- package/Tests/Server/Services/KubernetesClusterServiceDeleteSettlesRemediation.test.ts +328 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +99 -0
- package/Tests/Server/Services/MonitorServiceIncomingEmailCustomAddress.test.ts +447 -0
- package/Tests/Server/Services/MonitorServiceProbeFlagSync.test.ts +1334 -0
- package/Tests/Server/Services/MonitorServiceProbeStatusGrouping.test.ts +1640 -0
- package/Tests/Server/Services/MonitorServiceRefreshCurrentStatus.test.ts +225 -0
- package/Tests/Server/Services/MonitorStatusBridgeProbePath.test.ts +8 -8
- package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +264 -1
- package/Tests/Server/Services/MonitorStatusTimelineMergedDowntime.test.ts +284 -0
- package/Tests/Server/Services/MonitorUptimeSummaryService.test.ts +1043 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +5 -0
- package/Tests/Server/Services/OnCallUndeliverablePageOwnerMembership.test.ts +359 -0
- package/Tests/Server/Services/ProbeServiceDashboardLink.test.ts +191 -0
- package/Tests/Server/Services/ProjectOwnersProjectMembership.test.ts +221 -0
- package/Tests/Server/Services/RuleRun/IncidentAlertMaintenanceRuleEnginesRun.test.ts +5 -0
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupARun.test.ts +28 -0
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +9 -1
- package/Tests/Server/Services/RunnerJobEnqueueAiCommandAgentRunner.test.ts +230 -0
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +978 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +866 -0
- package/Tests/Server/Services/RunnerJobSubmitResultRedaction.test.ts +322 -0
- package/Tests/Server/Services/RunnerJobSubmitResultUnstorableText.test.ts +357 -0
- package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +227 -0
- package/Tests/Server/Services/RunnerServiceAgentName.test.ts +581 -0
- package/Tests/Server/Services/ScheduledMaintenancePublicNoteNotifyDefault.test.ts +802 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +53 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +5 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +12 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +1176 -1
- package/Tests/Server/Services/TeamMemberBillingSeatSync.test.ts +9 -0
- package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +18 -0
- package/Tests/Server/Services/TeamMemberProjectMembership.test.ts +255 -0
- package/Tests/Server/Services/TeamMemberRemovalRevokesProjectAccess.test.ts +600 -0
- package/Tests/Server/Services/TeamMemberResourceCleanup.test.ts +731 -0
- package/Tests/Server/Services/TeamMemberWorkspaceAccountCleanup.test.ts +164 -0
- package/Tests/Server/Services/TraceAggregationService.test.ts +64 -0
- package/Tests/Server/Services/UserServiceBlockedUser.test.ts +293 -0
- package/Tests/Server/Services/WorkflowVariableOAuthHooks.test.ts +1016 -0
- package/Tests/Server/Services/WorkflowVariableOAuthPostgres.test.ts +543 -0
- package/Tests/Server/Services/WorkflowVariableRenameUniqueness.test.ts +16 -5
- package/Tests/Server/Services/WorkspaceNotificationRuleNoteDelivery.test.ts +473 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleSendTestNotification.test.ts +3690 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenMiscDataSecrets.test.ts +374 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenSlackChannelCache.test.ts +143 -0
- package/Tests/Server/Types/Database/Permissions/WorkflowVariableOAuthColumnPermission.test.ts +250 -0
- package/Tests/Server/Types/Database/QueryHelperJsonContains.test.ts +96 -0
- package/Tests/Server/Types/Markdown.test.ts +1033 -0
- package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +285 -0
- package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAuth.test.ts +159 -1
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +752 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +1840 -0
- package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +1067 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +355 -0
- package/Tests/Server/Utils/AI/PostIncidentStatusUpdateTool.test.ts +496 -0
- package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +305 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +1365 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +639 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +1585 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +1065 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +2138 -1
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +1035 -0
- package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +517 -0
- package/Tests/Server/Utils/AI/ResourceTools.test.ts +27 -1
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +1371 -24
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorRollbackRace.test.ts +962 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +518 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +939 -2
- package/Tests/Server/Utils/Database/ProjectScopedRelationReferences.test.ts +371 -0
- package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +64 -0
- package/Tests/Server/Utils/Monitor/DatabaseMetricObservationRemediation.test.ts +102 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +362 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +55 -0
- package/Tests/Server/Utils/Monitor/MonitorEmailAddressRedaction.test.ts +255 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +347 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +203 -0
- package/Tests/Server/Utils/Monitor/ProbeMonitorsNotification.test.ts +1147 -0
- package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +79 -0
- package/Tests/Server/Utils/ResourceFeed/ResourceFeedUtil.test.ts +21 -1
- package/Tests/Server/Utils/Rules/OwnerRuleAssignment.test.ts +155 -1
- package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +288 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.test.ts +304 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +40 -3
- package/Tests/Server/Utils/Workflow/OAuth2TokenClient.test.ts +702 -0
- package/Tests/Server/Utils/Workflow/OAuth2TokenClientHttpIntegration.test.ts +507 -0
- package/Tests/Server/Utils/Workflow/WorkflowVariableOAuthToken.test.ts +831 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +63 -1
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsAppAccessToken.test.ts +256 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +60 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChatActionAuthorization.test.ts +442 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +44 -2
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +55 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsReactionNoteSync.test.ts +1522 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsServiceUrl.test.ts +130 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +4 -4
- package/Tests/Server/Utils/Workspace/Slack/SlackActionAuthorization.test.ts +691 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackMessageByTimestamp.test.ts +253 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackReactionNote.test.ts +574 -0
- package/Tests/Server/Utils/Workspace/WorkspaceActionAuthorization.test.ts +488 -0
- package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +861 -0
- package/Tests/Server/Utils/Workspace/WorkspaceMessageAuthor.test.ts +635 -0
- package/Tests/Server/Utils/Workspace/WorkspaceOAuthState.test.ts +548 -0
- package/Tests/Server/Utils/Workspace/WorkspaceReactionNote.test.ts +839 -0
- package/Tests/Server/Utils/Workspace/WorkspaceSendErrorMessage.test.ts +725 -0
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +283 -9
- package/Tests/Types/DateBrowserLocalWallClock.test.ts +280 -0
- package/Tests/Types/DateLocalShortMonthNameFallback.test.ts +389 -0
- package/Tests/Types/DateUserTimezone.test.ts +177 -4
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +306 -0
- package/Tests/Types/Kubernetes/KubernetesClusterAiAccess.test.ts +283 -0
- package/Tests/Types/Kubernetes/KubernetesRunnerPostureWriteScope.test.ts +68 -0
- package/Tests/Types/Monitor/DatabaseMonitor/SqlServerPlatform.test.ts +144 -0
- package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +160 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +53 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +91 -16
- package/Tests/Types/Runner/RunnerLiveStatus.test.ts +14 -3
- package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +15 -0
- package/Tests/Types/StatusPage/PublicNoteSubscriberNotificationDefault.test.ts +1868 -0
- package/Tests/Types/Timezone.test.ts +5 -0
- package/Tests/Types/TimezoneAlias.test.ts +1034 -0
- package/Tests/Types/Workflow/JiraTemplateFixtures.ts +833 -0
- package/Tests/Types/Workflow/JiraTemplateScripts.test.ts +5414 -0
- package/Tests/Types/Workflow/JiraTemplateSimulation.test.ts +4803 -0
- package/Tests/Types/Workflow/JiraTemplates.test.ts +5429 -0
- package/Tests/Types/Workflow/Templates.test.ts +79 -4
- package/Tests/Types/Workflow/WorkflowVariableOAuth.test.ts +520 -0
- package/Tests/Types/Workspace/WorkspaceNoteReaction.test.ts +177 -0
- package/Tests/UI/Components/AttributesJSON/AttributesJSONView.test.tsx +390 -0
- package/Tests/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.test.tsx +566 -0
- package/Tests/UI/Components/AttributesJSON/JSONHighlight.test.ts +131 -0
- package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +276 -1
- package/Tests/UI/Components/Calendar/Calendar.test.tsx +1611 -0
- package/Tests/UI/Components/Calendar/CalendarDayLayout.test.ts +1486 -0
- package/Tests/UI/Components/Calendar/CalendarDayLayoutDst.test.ts +632 -0
- package/Tests/UI/Components/Calendar/CalendarDst.test.tsx +388 -0
- package/Tests/UI/Components/Charts/ChartGroupPlotFloor.test.tsx +170 -0
- package/Tests/UI/Components/Charts/HistogramSelection.test.ts +282 -0
- package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +6 -0
- package/Tests/UI/Components/CustomTimeRangeModalTimezoneTranslation.test.tsx +285 -0
- package/Tests/UI/Components/Dropdown.test.tsx +999 -2
- package/Tests/UI/Components/Forms/FormFieldCheckboxTranslation.test.tsx +216 -0
- package/Tests/UI/Components/Forms/FormFieldTimezoneDescriptionTranslation.test.tsx +461 -0
- package/Tests/UI/Components/Forms/IncidentEpisodePublicNoteNotifyPayload.test.tsx +641 -0
- package/Tests/UI/Components/Forms/IncidentPublicNoteNotifyPayload.test.tsx +346 -0
- package/Tests/UI/Components/Forms/ScheduledMaintenancePublicNoteNotifyPayload.test.tsx +493 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +911 -2
- package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.NewYork.test.tsx +730 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.Tokyo.test.tsx +506 -0
- package/Tests/UI/Components/HistogramClickToZoom.test.tsx +619 -0
- package/Tests/UI/Components/LogDetailsPanelAttributesJSON.test.tsx +248 -0
- package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +47 -1
- package/Tests/UI/Components/LogsViewerSmallScreens.test.tsx +620 -0
- package/Tests/UI/Components/ModelPageModelChange.test.tsx +242 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableDeleteConfirmation.test.tsx +521 -0
- package/Tests/UI/Components/MonitorGraphs/Uptime.test.tsx +406 -2
- package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +169 -2
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +266 -0
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerSmallScreens.test.tsx +439 -0
- package/Tests/UI/Components/TimezoneDropdownLegacyValues.test.tsx +704 -0
- package/Tests/UI/Components/UseHistogramZoom.test.tsx +89 -1
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +175 -0
- package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +5 -5
- package/Tests/UI/Telemetry/TelemetryDetailPanel.test.tsx +28 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +51 -0
- package/Tests/UI/Utils/Timezone.test.ts +747 -33
- package/Tests/UI/Workspace/SendTestNotificationButton.test.tsx +1854 -0
- package/Tests/UI/Workspace/WorkspaceCardsSendTestNotification.test.tsx +1864 -0
- package/Tests/Utils/AI/InvestigationReport.test.ts +435 -7
- package/Tests/Utils/AiRemediation/KubectlAllowlistValidatorParity.test.ts +1084 -0
- package/Tests/Utils/AiRemediation/KubectlOutputRedactor.test.ts +2531 -0
- package/Tests/Utils/AiRemediation/KubectlPolicy.test.ts +1895 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyAllowlist.test.ts +428 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +828 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyCustomResourceScope.test.ts +573 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyDenials.test.ts +1525 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyExposeOverrides.test.ts +275 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyNodePatch.test.ts +428 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyObjectWords.test.ts +195 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyPatchParents.test.ts +637 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyPodSecurityBaseline.test.ts +322 -0
- package/Tests/Utils/AiRemediation/KubectlPolicySafeWriteScope.test.ts +1026 -0
- package/Tests/Utils/AiRemediation/KubectlWaitBudget.test.ts +267 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScope.test.ts +390 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +777 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeClusterScoped.test.ts +613 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeEntryPoint.test.ts +576 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeKubectlSpellings.test.ts +752 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeObjectWords.test.ts +358 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeOverrides.test.ts +293 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeParityCases.ts +598 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopePolicyParity.test.ts +403 -0
- package/Tests/Utils/Dashboard/DashboardClockComponent.test.ts +154 -5
- package/Tests/Utils/Monitor/IncomingEmailMonitorAddress.test.ts +478 -0
- package/Tests/Utils/Monitor/MonitorCheckScheduleUtil.test.ts +646 -0
- package/Tests/Utils/Monitor/MonitorOverviewCriteriaUtil.test.ts +292 -0
- package/Tests/Utils/Monitor/MonitorOverviewFamily.test.ts +274 -0
- package/Tests/Utils/Monitor/MonitorOverviewPresentationUtil.test.ts +3100 -0
- package/Tests/Utils/Monitor/MonitorOverviewProbeUtil.test.ts +1412 -0
- package/Tests/Utils/Monitor/MonitorOverviewTargetUtil.test.ts +374 -0
- package/Tests/Utils/Monitor/MonitorStatusHistoryUtil.test.ts +332 -0
- package/Tests/Utils/Monitor/MonitorUptimeSummaryUtil.test.ts +946 -0
- package/Tests/Utils/Rum/SessionIdentity.test.ts +94 -0
- package/Tests/Utils/Rum/SessionReplayWafCompatibility.test.ts +840 -0
- package/Tests/Utils/Rum/SessionReplayWireEncoding.test.ts +208 -0
- package/Tests/Utils/Rum/UserFlow.test.ts +1010 -0
- package/Tests/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.test.ts +217 -0
- package/Tests/Utils/StatusPage/ResourceUptime.test.ts +1272 -1
- package/Tests/Utils/StatusPage/UptimeDailyAggregateTotals.test.ts +298 -0
- package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +649 -1
- package/Tests/Utils/StatusPage/UptimeDailyAggregateWindow.test.ts +314 -0
- package/Tests/Utils/Telemetry/AttributesJSON.test.ts +454 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +418 -2
- package/Tests/Utils/Uptime/DayUptimeGraphUtilMidnightDst.test.ts +160 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Auckland.test.ts +45 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Kolkata.test.ts +40 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.London.test.ts +147 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.NewYork.test.ts +48 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Tokyo.test.ts +39 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezoneCases.ts +1015 -0
- package/Tests/Utils/Uptime/UptimeUtil.test.ts +75 -0
- package/Tests/Utils/Uptime/UptimeUtilChronologicalOrder.test.ts +1279 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +203 -5
- package/Types/Date.ts +121 -7
- package/Types/Email/EmailMessage.ts +8 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Exception/ExceptionMessages.ts +1 -0
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +488 -0
- package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +54 -0
- package/Types/Kubernetes/KubernetesClusterAiAccessToolNames.ts +17 -0
- package/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.ts +9 -0
- package/Types/Monitor/DatabaseMonitor/SqlServerPlatform.ts +117 -0
- package/Types/Monitor/KubernetesAlertTemplates.ts +6 -5
- package/Types/Monitor/MonitorStepExceptionMonitor.ts +56 -0
- package/Types/Monitor/MonitorUptimeSummary.ts +113 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +11 -0
- package/Types/Rum/SessionReplay.ts +55 -2
- package/Types/Rum/SessionReplayApi.ts +44 -1
- package/Types/Rum/UserFlow.ts +187 -0
- package/Types/Runbook/RunbookStepType.ts +10 -0
- package/Types/Runbook/RunnerJobOrigin.ts +15 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +6 -2
- package/Types/StatusPage/MergedDowntimeTotals.ts +17 -0
- package/Types/StatusPage/PublicNoteSubscriberNotificationDefault.ts +148 -0
- package/Types/StatusPage/UptimeDailyAggregate.ts +15 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +9 -0
- package/Types/Timezone.ts +5 -0
- package/Types/TimezoneAlias.ts +385 -0
- package/Types/Workflow/Templates.ts +3041 -2
- package/Types/Workflow/WorkflowVariableOAuth.ts +476 -0
- package/Types/Workspace/WorkspaceNoteReaction.ts +132 -0
- package/Typings/Index.d.ts +24 -0
- package/UI/Components/AttributesJSON/AttributesJSONPreferences.ts +175 -0
- package/UI/Components/AttributesJSON/AttributesJSONView.tsx +138 -0
- package/UI/Components/AttributesJSON/AttributesViewToggle.tsx +79 -0
- package/UI/Components/AttributesJSON/BracesGlyph.tsx +30 -0
- package/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.tsx +491 -0
- package/UI/Components/AttributesJSON/JSONHighlight.ts +77 -0
- package/UI/Components/Calendar/Calendar.css +83 -0
- package/UI/Components/Calendar/Calendar.tsx +136 -7
- package/UI/Components/Calendar/CalendarDayLayout.ts +236 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -1
- package/UI/Components/Charts/Utils/HistogramSelection.ts +117 -0
- package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +269 -0
- package/UI/Components/Charts/Utils/useHistogramZoom.ts +26 -2
- package/UI/Components/Date/CustomTimeRangeModal.tsx +26 -3
- package/UI/Components/Dropdown/Dropdown.tsx +58 -9
- package/UI/Components/Forms/Fields/FormField.tsx +49 -14
- package/UI/Components/Graphs/DayUptimeGraph.tsx +166 -25
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +10 -2
- package/UI/Components/Graphs/UptimeDaySummary.tsx +8 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +52 -5
- package/UI/Components/LogsViewer/components/ColumnSelector.tsx +13 -2
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +13 -1
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +91 -76
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +15 -1
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +28 -106
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +54 -6
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +8 -2
- package/UI/Components/LogsViewer/useLogsHistogram.ts +39 -6
- package/UI/Components/ModelTable/BaseModelTable.tsx +66 -14
- package/UI/Components/MonitorGraphs/Uptime.tsx +12 -0
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +10 -2
- package/UI/Components/Page/ModelPage.tsx +59 -3
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +69 -5
- package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +12 -1
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +15 -1
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +36 -104
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +9 -1
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +19 -1
- package/UI/Utils/Timezone.ts +69 -28
- package/Utils/AI/InvestigationReport.ts +195 -25
- package/Utils/AiRemediation/KubectlOutputRedactor.ts +1724 -0
- package/Utils/AiRemediation/KubectlPolicy.ts +4136 -0
- package/Utils/AiRemediation/KubectlWaitBudget.ts +156 -0
- package/Utils/AiRemediation/KubectlWriteScope.ts +1964 -0
- package/Utils/Dashboard/Components/DashboardClockComponent.ts +7 -6
- package/Utils/Monitor/IncomingEmailMonitorAddress.ts +292 -0
- package/Utils/Monitor/MonitorCheckScheduleUtil.ts +654 -0
- package/Utils/Monitor/MonitorOverviewCriteriaUtil.ts +188 -0
- package/Utils/Monitor/MonitorOverviewFamily.ts +263 -0
- package/Utils/Monitor/MonitorOverviewPresentationUtil.ts +1958 -0
- package/Utils/Monitor/MonitorOverviewProbeUtil.ts +979 -0
- package/Utils/Monitor/MonitorOverviewTargetUtil.ts +299 -0
- package/Utils/Monitor/MonitorStatusHistoryUtil.ts +201 -0
- package/Utils/Monitor/MonitorUptimeSummaryUtil.ts +638 -0
- package/Utils/Rum/SessionIdentity.ts +44 -0
- package/Utils/Rum/SessionReplayWireEncoding.ts +137 -0
- package/Utils/Rum/UserFlow.ts +1527 -0
- package/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.ts +107 -0
- package/Utils/StatusPage/ResourceUptime.ts +99 -0
- package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +197 -0
- package/Utils/Telemetry/AttributesJSON.ts +367 -0
- package/Utils/Uptime/DayUptimeGraphUtil.ts +165 -0
- package/Utils/Uptime/UptimeUtil.ts +122 -38
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +8 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +8 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +10 -3
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +10 -3
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +434 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +49 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Runner.js +4 -4
- package/build/dist/Models/DatabaseModels/Runner.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +66 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +11 -3
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +487 -3
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js +64 -16
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +205 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +115 -2
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +49 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +564 -0
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -0
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +465 -176
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/MonitorAPI.js +107 -1
- package/build/dist/Server/API/MonitorAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +5 -16
- package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +322 -78
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +123 -17
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +144 -5
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js +1 -0
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js.map +1 -1
- package/build/dist/Server/API/WorkflowVariableAPI.js +109 -0
- package/build/dist/Server/API/WorkflowVariableAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js +54 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +21 -0
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +32 -2
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +39 -6
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AIAgentService.js +1 -0
- package/build/dist/Server/Services/AIAgentService.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +87 -42
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeMemberService.js +4 -0
- package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeService.js +12 -1
- package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +110 -23
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +716 -30
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js +100 -0
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +1 -0
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js +11 -16
- package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js +4 -0
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +48 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js +21 -0
- package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +12 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentGroupingEngineService.js +8 -0
- package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentMemberService.js +14 -0
- package/build/dist/Server/Services/IncidentMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +48 -0
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +99 -21
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +68 -4
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +1470 -0
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterService.js +1097 -0
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +14 -6
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +487 -48
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +540 -3
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +3 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js +29 -7
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -1
- package/build/dist/Server/Services/OnCallSetupReminderService.js +1 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +7 -1
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +11 -1
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RunbookCredentialService.js +24 -0
- package/build/dist/Server/Services/RunbookCredentialService.js.map +1 -1
- package/build/dist/Server/Services/RunbookSecretService.js +44 -0
- package/build/dist/Server/Services/RunbookSecretService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +641 -21
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +344 -5
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +48 -0
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +102 -20
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +67 -4
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +12 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -0
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +230 -32
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +4 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +270 -11
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +10 -6
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +1 -0
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +1 -0
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +4 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +107 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/WorkflowVariableService.js +396 -7
- package/build/dist/Server/Services/WorkflowVariableService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +463 -7
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +144 -2
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +20 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +394 -27
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +123 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +344 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +587 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js +18 -2
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1380 -239
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +1099 -68
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +88 -6
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +45 -11
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +41 -11
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +11 -4
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +4 -2
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +997 -141
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +299 -4
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +120 -0
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -0
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +160 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +59 -12
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +5 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +84 -22
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +70 -5
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js +249 -0
- package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -1
- package/build/dist/Server/Utils/PushNotificationUtil.js +27 -0
- package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
- package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +26 -3
- package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -1
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js +38 -3
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +143 -23
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js +406 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +4 -3
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +382 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -0
- package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js +1 -0
- package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js.map +1 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js +10 -0
- package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +447 -0
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -0
- package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js +202 -0
- package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +39 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +44 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +150 -191
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +44 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js +39 -17
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +151 -145
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +221 -127
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js +91 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js +673 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +3 -9
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +65 -166
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +63 -157
- package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +23 -3
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js +92 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +81 -210
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +68 -138
- package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js +240 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js +85 -212
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +60 -17
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
- package/build/dist/Server/Utils/Workspace/Workspace.js +134 -41
- package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js +197 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceBase.js +32 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js +199 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js +91 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js +450 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +103 -5
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/Date.js +97 -7
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Exception/ExceptionMessages.js +1 -0
- package/build/dist/Types/Exception/ExceptionMessages.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +245 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +49 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js +16 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js.map +1 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js +104 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js.map +1 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +6 -5
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js +32 -0
- package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorUptimeSummary.js +52 -0
- package/build/dist/Types/Monitor/MonitorUptimeSummary.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +9 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +52 -2
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayApi.js +11 -0
- package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
- package/build/dist/Types/Rum/UserFlow.js +115 -0
- package/build/dist/Types/Rum/UserFlow.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +10 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/Types/Runbook/RunnerJobOrigin.js +14 -0
- package/build/dist/Types/Runbook/RunnerJobOrigin.js.map +1 -1
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +2 -2
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -1
- package/build/dist/Types/StatusPage/MergedDowntimeTotals.js +2 -0
- package/build/dist/Types/StatusPage/MergedDowntimeTotals.js.map +1 -0
- package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js +65 -0
- package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +8 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/Types/Timezone.js +5 -0
- package/build/dist/Types/Timezone.js.map +1 -1
- package/build/dist/Types/TimezoneAlias.js +340 -0
- package/build/dist/Types/TimezoneAlias.js.map +1 -0
- package/build/dist/Types/Workflow/Templates.js +2694 -2
- package/build/dist/Types/Workflow/Templates.js.map +1 -1
- package/build/dist/Types/Workflow/WorkflowVariableOAuth.js +314 -0
- package/build/dist/Types/Workflow/WorkflowVariableOAuth.js.map +1 -0
- package/build/dist/Types/Workspace/WorkspaceNoteReaction.js +98 -0
- package/build/dist/Types/Workspace/WorkspaceNoteReaction.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js +95 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js +44 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js +27 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js +9 -0
- package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js +260 -0
- package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js +56 -0
- package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js.map +1 -0
- package/build/dist/UI/Components/Calendar/Calendar.js +94 -7
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Calendar/CalendarDayLayout.js +136 -0
- package/build/dist/UI/Components/Calendar/CalendarDayLayout.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js +72 -0
- package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +178 -0
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js +13 -1
- package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js.map +1 -1
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +20 -4
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +33 -9
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +36 -13
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +117 -11
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +36 -8
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js +10 -2
- package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +9 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +16 -15
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +14 -74
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +22 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +9 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +16 -5
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +23 -8
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +7 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +5 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/Page/ModelPage.js +37 -3
- package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +27 -6
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +7 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +17 -77
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +10 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +15 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Timezone.js +52 -17
- package/build/dist/UI/Utils/Timezone.js.map +1 -1
- package/build/dist/Utils/AI/InvestigationReport.js +116 -21
- package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
- package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js +1209 -0
- package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js.map +1 -0
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js +2973 -0
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js +99 -0
- package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js.map +1 -0
- package/build/dist/Utils/AiRemediation/KubectlWriteScope.js +1429 -0
- package/build/dist/Utils/AiRemediation/KubectlWriteScope.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js +7 -6
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js.map +1 -1
- package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js +214 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js +471 -0
- package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js +107 -0
- package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewFamily.js +191 -0
- package/build/dist/Utils/Monitor/MonitorOverviewFamily.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js +1259 -0
- package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js +593 -0
- package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js +195 -0
- package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js +127 -0
- package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js +464 -0
- package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js.map +1 -0
- package/build/dist/Utils/Rum/SessionIdentity.js +31 -1
- package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayWireEncoding.js +128 -0
- package/build/dist/Utils/Rum/SessionReplayWireEncoding.js.map +1 -0
- package/build/dist/Utils/Rum/UserFlow.js +941 -0
- package/build/dist/Utils/Rum/UserFlow.js.map +1 -0
- package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js +82 -0
- package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js +46 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/AttributesJSON.js +245 -0
- package/build/dist/Utils/Telemetry/AttributesJSON.js.map +1 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +103 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -1
- package/build/dist/Utils/Uptime/UptimeUtil.js +91 -27
- package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
- package/jest.config.json +2 -1
- package/package.json +2 -1
- package/tsconfig.tests.json +36 -0
- package/Tests/UI/Components/EmptyState/EmptyStateGuideLinks.test.tsx +0 -337
- package/UI/Components/EmptyState/EmptyStateGuideLinks.tsx +0 -124
- package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js +0 -47
- package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js.map +0 -1
|
@@ -0,0 +1,3227 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
act,
|
|
4
|
+
cleanup,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
waitFor,
|
|
9
|
+
within,
|
|
10
|
+
} from "@testing-library/react";
|
|
11
|
+
import React, { ReactElement, ReactNode } from "react";
|
|
12
|
+
import {
|
|
13
|
+
afterEach,
|
|
14
|
+
beforeEach,
|
|
15
|
+
describe,
|
|
16
|
+
expect,
|
|
17
|
+
jest,
|
|
18
|
+
test,
|
|
19
|
+
} from "@jest/globals";
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* A workflow variable's own page (Workflows > Global Variables > View, and
|
|
23
|
+
* Workflow > Workflow Variables > View).
|
|
24
|
+
*
|
|
25
|
+
* The variables list used to do everything in its row actions - Show ID,
|
|
26
|
+
* Update Content, Update Credentials, Edit Details, Delete - and a Token
|
|
27
|
+
* column with its own Refresh now button. The list now only lists and
|
|
28
|
+
* creates; its single View action opens this page, and everything done to one
|
|
29
|
+
* variable happens here. So what is asserted here is that every one of those
|
|
30
|
+
* doors exists on the page, for the right kind of variable, on both the global
|
|
31
|
+
* and the local page, gated the way the list gated them.
|
|
32
|
+
*
|
|
33
|
+
* The cards, the modals, the token status and the delete card are replaced by
|
|
34
|
+
* prop recorders: what is under test is what the page hands them and what it
|
|
35
|
+
* does when their callbacks fire. The real CardModelDetail and ModelDelete
|
|
36
|
+
* fetch on mount.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
let permissionsForTest: Array<unknown> = [];
|
|
40
|
+
let isMasterAdminForTest: boolean = false;
|
|
41
|
+
|
|
42
|
+
jest.mock("../../../UI/Utils/Permission", () => {
|
|
43
|
+
return {
|
|
44
|
+
__esModule: true,
|
|
45
|
+
default: {
|
|
46
|
+
getAllPermissions: (): Array<unknown> => {
|
|
47
|
+
return permissionsForTest;
|
|
48
|
+
},
|
|
49
|
+
getProjectPermissions: (): null => {
|
|
50
|
+
return null;
|
|
51
|
+
},
|
|
52
|
+
getGlobalPermissions: (): null => {
|
|
53
|
+
return null;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
jest.mock("../../../UI/Utils/User", () => {
|
|
60
|
+
return {
|
|
61
|
+
__esModule: true,
|
|
62
|
+
default: {
|
|
63
|
+
isMasterAdmin: (): boolean => {
|
|
64
|
+
return isMasterAdminForTest;
|
|
65
|
+
},
|
|
66
|
+
getUserId: (): null => {
|
|
67
|
+
return null;
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
74
|
+
return {
|
|
75
|
+
__esModule: true,
|
|
76
|
+
default: {
|
|
77
|
+
getCurrentProjectId: (): unknown => {
|
|
78
|
+
return PROJECT_ID;
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const navigate: ReturnType<typeof jest.fn<(to: unknown) => void>> =
|
|
85
|
+
jest.fn<(to: unknown) => void>();
|
|
86
|
+
const getLastParamAsObjectID: ReturnType<
|
|
87
|
+
typeof jest.fn<(index?: number) => unknown>
|
|
88
|
+
> = jest.fn<(index?: number) => unknown>();
|
|
89
|
+
|
|
90
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
91
|
+
return {
|
|
92
|
+
__esModule: true,
|
|
93
|
+
default: {
|
|
94
|
+
navigate: (to: unknown): void => {
|
|
95
|
+
navigate(to);
|
|
96
|
+
},
|
|
97
|
+
getLastParamAsObjectID: (index?: number): unknown => {
|
|
98
|
+
return getLastParamAsObjectID(index);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const getItem: ReturnType<typeof jest.fn<(data: unknown) => Promise<unknown>>> =
|
|
105
|
+
jest.fn<(data: unknown) => Promise<unknown>>();
|
|
106
|
+
const updateById: ReturnType<
|
|
107
|
+
typeof jest.fn<(data: unknown) => Promise<unknown>>
|
|
108
|
+
> = jest.fn<(data: unknown) => Promise<unknown>>();
|
|
109
|
+
|
|
110
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
111
|
+
return {
|
|
112
|
+
__esModule: true,
|
|
113
|
+
default: {
|
|
114
|
+
getItem: (data: unknown): Promise<unknown> => {
|
|
115
|
+
return getItem(data);
|
|
116
|
+
},
|
|
117
|
+
updateById: (data: unknown): Promise<unknown> => {
|
|
118
|
+
return updateById(data);
|
|
119
|
+
},
|
|
120
|
+
getCommonHeaders: (): Record<string, string> => {
|
|
121
|
+
return { tenantid: "project-header" };
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const apiPost: ReturnType<typeof jest.fn<(data: unknown) => Promise<unknown>>> =
|
|
128
|
+
jest.fn<(data: unknown) => Promise<unknown>>();
|
|
129
|
+
|
|
130
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
131
|
+
return {
|
|
132
|
+
__esModule: true,
|
|
133
|
+
default: {
|
|
134
|
+
post: (data: unknown): Promise<unknown> => {
|
|
135
|
+
return apiPost(data);
|
|
136
|
+
},
|
|
137
|
+
getFriendlyMessage: (error: unknown): string => {
|
|
138
|
+
return error instanceof Error ? error.message : "Request failed.";
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* Every render of a visible loader, so a test can prove the loader never
|
|
146
|
+
* appeared at all - a background reload that flashed it for one render and
|
|
147
|
+
* took it down again would still blank the page for that moment.
|
|
148
|
+
*/
|
|
149
|
+
let pageLoaderRenderCount: number = 0;
|
|
150
|
+
|
|
151
|
+
jest.mock("../../../UI/Components/Loader/PageLoader", () => {
|
|
152
|
+
return {
|
|
153
|
+
__esModule: true,
|
|
154
|
+
default: (props: { isVisible: boolean }): ReactElement | null => {
|
|
155
|
+
if (props.isVisible) {
|
|
156
|
+
pageLoaderRenderCount++;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return props.isVisible ? <div data-testid="page-loader" /> : null;
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
type CardButtonEntry = {
|
|
165
|
+
title: string;
|
|
166
|
+
disabled?: boolean | undefined;
|
|
167
|
+
tooltip?: string | undefined;
|
|
168
|
+
onClick: () => void;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
type CapturedCardProps = {
|
|
172
|
+
title?: string | undefined;
|
|
173
|
+
description?: string | undefined;
|
|
174
|
+
buttons?: Array<CardButtonEntry> | undefined;
|
|
175
|
+
children?: ReactNode;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
let capturedCards: Record<string, CapturedCardProps> = {};
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* The card header the mocked Card and CardModelDetail both render: a heading
|
|
182
|
+
* and the card's buttons as real buttons, so a click goes through the same
|
|
183
|
+
* disabled attribute a person would meet.
|
|
184
|
+
*/
|
|
185
|
+
function renderMockCard(
|
|
186
|
+
testId: string,
|
|
187
|
+
cardProps: CapturedCardProps,
|
|
188
|
+
): ReactElement {
|
|
189
|
+
return (
|
|
190
|
+
<section data-testid={testId}>
|
|
191
|
+
<h2>{cardProps.title}</h2>
|
|
192
|
+
{(cardProps.buttons || []).map((button: CardButtonEntry) => {
|
|
193
|
+
return (
|
|
194
|
+
<button
|
|
195
|
+
key={button.title}
|
|
196
|
+
type="button"
|
|
197
|
+
data-testid={`card-button:${button.title}`}
|
|
198
|
+
disabled={Boolean(button.disabled)}
|
|
199
|
+
title={button.tooltip}
|
|
200
|
+
onClick={button.onClick}
|
|
201
|
+
>
|
|
202
|
+
{button.title}
|
|
203
|
+
</button>
|
|
204
|
+
);
|
|
205
|
+
})}
|
|
206
|
+
{cardProps.children}
|
|
207
|
+
</section>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
jest.mock("../../../UI/Components/Card/Card", () => {
|
|
212
|
+
return {
|
|
213
|
+
__esModule: true,
|
|
214
|
+
default: (props: CapturedCardProps): ReactElement => {
|
|
215
|
+
capturedCards[props.title || ""] = props;
|
|
216
|
+
return renderMockCard(`card:${props.title}`, props);
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
type DetailField = {
|
|
222
|
+
field?: Record<string, unknown> | undefined;
|
|
223
|
+
title?: string | undefined;
|
|
224
|
+
description?: string | undefined;
|
|
225
|
+
getElement?: ((item: unknown) => ReactElement) | undefined;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
type FormFieldEntry = {
|
|
229
|
+
field?: Record<string, unknown> | undefined;
|
|
230
|
+
title?: string | undefined;
|
|
231
|
+
description?: string | undefined;
|
|
232
|
+
fieldType?: string | undefined;
|
|
233
|
+
required?: unknown;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
type CapturedDetailProps = {
|
|
237
|
+
name: string;
|
|
238
|
+
cardProps: CapturedCardProps;
|
|
239
|
+
isEditable?: boolean | undefined;
|
|
240
|
+
editButtonText?: string | undefined;
|
|
241
|
+
onSaveSuccess?: ((item: unknown) => void) | undefined;
|
|
242
|
+
formFields?: Array<FormFieldEntry> | undefined;
|
|
243
|
+
modelDetailProps: {
|
|
244
|
+
modelType: unknown;
|
|
245
|
+
modelId: { toString: () => string };
|
|
246
|
+
fields: Array<DetailField>;
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
let capturedDetails: Record<string, CapturedDetailProps> = {};
|
|
251
|
+
|
|
252
|
+
jest.mock("../../../UI/Components/ModelDetail/CardModelDetail", () => {
|
|
253
|
+
return {
|
|
254
|
+
__esModule: true,
|
|
255
|
+
default: (props: CapturedDetailProps): ReactElement => {
|
|
256
|
+
capturedDetails[props.name] = props;
|
|
257
|
+
return renderMockCard(`detail:${props.name}`, props.cardProps);
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
type CapturedDeleteProps = {
|
|
263
|
+
modelType: unknown;
|
|
264
|
+
modelId: { toString: () => string };
|
|
265
|
+
onDeleteSuccess: () => void;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
let capturedDeleteProps: CapturedDeleteProps | null = null;
|
|
269
|
+
|
|
270
|
+
jest.mock("../../../UI/Components/ModelDelete/ModelDelete", () => {
|
|
271
|
+
return {
|
|
272
|
+
__esModule: true,
|
|
273
|
+
default: (props: CapturedDeleteProps): ReactElement => {
|
|
274
|
+
capturedDeleteProps = props;
|
|
275
|
+
return <div data-testid="model-delete" />;
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
type CapturedContentModalProps = {
|
|
281
|
+
variable: { name?: string | undefined; id?: unknown };
|
|
282
|
+
onClose: () => void;
|
|
283
|
+
onSuccess: () => void;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
let capturedContentModalProps: CapturedContentModalProps | null = null;
|
|
287
|
+
|
|
288
|
+
jest.mock(
|
|
289
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Workflow/UpdateWorkflowVariableContentModal",
|
|
290
|
+
() => {
|
|
291
|
+
return {
|
|
292
|
+
__esModule: true,
|
|
293
|
+
default: (props: CapturedContentModalProps): ReactElement => {
|
|
294
|
+
capturedContentModalProps = props;
|
|
295
|
+
return <div data-testid="update-content-modal" />;
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
type CapturedCredentialsModalProps = {
|
|
302
|
+
variable: { name?: string | undefined; id?: unknown };
|
|
303
|
+
onClose: () => void;
|
|
304
|
+
onSaved: (savedWhat: string) => void;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
let capturedCredentialsModalProps: CapturedCredentialsModalProps | null = null;
|
|
308
|
+
|
|
309
|
+
jest.mock(
|
|
310
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Workflow/UpdateWorkflowVariableCredentialsModal",
|
|
311
|
+
() => {
|
|
312
|
+
return {
|
|
313
|
+
__esModule: true,
|
|
314
|
+
default: (props: CapturedCredentialsModalProps): ReactElement => {
|
|
315
|
+
capturedCredentialsModalProps = props;
|
|
316
|
+
return <div data-testid="update-credentials-modal" />;
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
type CapturedTokenRefreshModalProps = {
|
|
323
|
+
variableName: string;
|
|
324
|
+
outcome: {
|
|
325
|
+
variableName: string;
|
|
326
|
+
savedWhat?: string | undefined;
|
|
327
|
+
expiresAt?: Date | null | undefined;
|
|
328
|
+
error?: string | undefined;
|
|
329
|
+
} | null;
|
|
330
|
+
onClose: () => void;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
let capturedTokenRefreshModalProps: CapturedTokenRefreshModalProps | null =
|
|
334
|
+
null;
|
|
335
|
+
|
|
336
|
+
jest.mock(
|
|
337
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Workflow/WorkflowVariableTokenRefreshModal",
|
|
338
|
+
() => {
|
|
339
|
+
return {
|
|
340
|
+
__esModule: true,
|
|
341
|
+
default: (props: CapturedTokenRefreshModalProps): ReactElement => {
|
|
342
|
+
capturedTokenRefreshModalProps = props;
|
|
343
|
+
return (
|
|
344
|
+
<div
|
|
345
|
+
data-testid="token-refresh-modal"
|
|
346
|
+
data-pending={props.outcome ? "false" : "true"}
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
type RefreshActionEntry = {
|
|
355
|
+
onClick: () => void;
|
|
356
|
+
isLoading?: boolean | undefined;
|
|
357
|
+
disabled?: boolean | undefined;
|
|
358
|
+
tooltip?: string | undefined;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
type CapturedTokenStatusProps = {
|
|
362
|
+
variable: {
|
|
363
|
+
name?: string | undefined;
|
|
364
|
+
id?: unknown;
|
|
365
|
+
oauthLastRefreshError?: string | undefined;
|
|
366
|
+
};
|
|
367
|
+
refreshAction?: RefreshActionEntry | undefined;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
let capturedTokenStatusProps: CapturedTokenStatusProps | null = null;
|
|
371
|
+
|
|
372
|
+
jest.mock(
|
|
373
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Workflow/WorkflowVariableTokenStatus",
|
|
374
|
+
() => {
|
|
375
|
+
return {
|
|
376
|
+
__esModule: true,
|
|
377
|
+
// Disabled exactly as the real one is: while loading, or when asked to.
|
|
378
|
+
default: (props: CapturedTokenStatusProps): ReactElement => {
|
|
379
|
+
capturedTokenStatusProps = props;
|
|
380
|
+
return (
|
|
381
|
+
<div data-testid="token-status">
|
|
382
|
+
{props.refreshAction ? (
|
|
383
|
+
<button
|
|
384
|
+
type="button"
|
|
385
|
+
data-testid="refresh-now"
|
|
386
|
+
disabled={Boolean(
|
|
387
|
+
props.refreshAction.disabled || props.refreshAction.isLoading,
|
|
388
|
+
)}
|
|
389
|
+
title={props.refreshAction.tooltip}
|
|
390
|
+
onClick={props.refreshAction.onClick}
|
|
391
|
+
>
|
|
392
|
+
Refresh now
|
|
393
|
+
</button>
|
|
394
|
+
) : null}
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
},
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
import WorkflowVariableView, {
|
|
403
|
+
WORKFLOW_VARIABLE_VIEW_SELECT,
|
|
404
|
+
getVariableScopeError,
|
|
405
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Workflow/WorkflowVariableView";
|
|
406
|
+
import GlobalWorkflowVariableViewPage from "../../../../App/FeatureSet/Dashboard/src/Pages/Workflow/VariableView";
|
|
407
|
+
import LocalWorkflowVariableViewPage from "../../../../App/FeatureSet/Dashboard/src/Pages/Workflow/View/VariableView";
|
|
408
|
+
import {
|
|
409
|
+
TokenRefreshOutcome,
|
|
410
|
+
getTokenRefreshDescription,
|
|
411
|
+
getTokenRefreshTitle,
|
|
412
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Utils/Workflow/WorkflowVariableUtil";
|
|
413
|
+
import WorkflowVariable from "../../../Models/DatabaseModels/WorkflowVariable";
|
|
414
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
415
|
+
import Permission from "../../../Types/Permission";
|
|
416
|
+
import {
|
|
417
|
+
OAuth2ClientAuthenticationMethod,
|
|
418
|
+
OAuth2GrantType,
|
|
419
|
+
WorkflowVariableType,
|
|
420
|
+
} from "../../../Types/Workflow/WorkflowVariableOAuth";
|
|
421
|
+
import PermissionGate, { ModelAction } from "../../../UI/Utils/PermissionGate";
|
|
422
|
+
|
|
423
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
424
|
+
"11111111-1111-4111-8111-111111111111",
|
|
425
|
+
);
|
|
426
|
+
const WORKFLOW_ID: ObjectID = new ObjectID(
|
|
427
|
+
"22222222-2222-4222-8222-222222222222",
|
|
428
|
+
);
|
|
429
|
+
const VARIABLE_ID: ObjectID = new ObjectID(
|
|
430
|
+
"33333333-3333-4333-8333-333333333333",
|
|
431
|
+
);
|
|
432
|
+
const OTHER_WORKFLOW_ID: ObjectID = new ObjectID(
|
|
433
|
+
"44444444-4444-4444-8444-444444444444",
|
|
434
|
+
);
|
|
435
|
+
const OTHER_VARIABLE_ID: ObjectID = new ObjectID(
|
|
436
|
+
"55555555-5555-4555-8555-555555555555",
|
|
437
|
+
);
|
|
438
|
+
// What Navigation hands back for a route segment a page should not be reading.
|
|
439
|
+
const UNEXPECTED_PARAM_ID: ObjectID = new ObjectID(
|
|
440
|
+
"99999999-9999-4999-8999-999999999999",
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const DETAILS_CARD: string = "Workflow > Variable Details";
|
|
444
|
+
const OAUTH_SETTINGS_CARD: string = "Workflow > OAuth 2.0 Settings";
|
|
445
|
+
|
|
446
|
+
// Columns nobody may read back: they must never be selected, shown or edited.
|
|
447
|
+
const WRITE_ONLY_COLUMNS: Array<string> = [
|
|
448
|
+
"content",
|
|
449
|
+
"oauthClientSecret",
|
|
450
|
+
"oauthRefreshToken",
|
|
451
|
+
"oauthAccessToken",
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
const TOKEN_EXPIRES_AT: string = "2030-01-01T10:00:00.000Z";
|
|
455
|
+
|
|
456
|
+
let lastParams: Record<number, ObjectID> = {};
|
|
457
|
+
|
|
458
|
+
type Deferred<T> = {
|
|
459
|
+
promise: Promise<T>;
|
|
460
|
+
resolve: (value: T) => void;
|
|
461
|
+
reject: (error: unknown) => void;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
function deferred<T>(): Deferred<T> {
|
|
465
|
+
let resolve!: (value: T) => void;
|
|
466
|
+
let reject!: (error: unknown) => void;
|
|
467
|
+
|
|
468
|
+
const promise: Promise<T> = new Promise<T>(
|
|
469
|
+
(
|
|
470
|
+
promiseResolve: (value: T) => void,
|
|
471
|
+
promiseReject: (error: unknown) => void,
|
|
472
|
+
) => {
|
|
473
|
+
resolve = promiseResolve;
|
|
474
|
+
reject = promiseReject;
|
|
475
|
+
},
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
return { promise, resolve, reject };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function applyOverrides(
|
|
482
|
+
variable: WorkflowVariable,
|
|
483
|
+
overrides?: Partial<Record<string, unknown>>,
|
|
484
|
+
): WorkflowVariable {
|
|
485
|
+
for (const [key, value] of Object.entries(overrides || {})) {
|
|
486
|
+
(variable as unknown as Record<string, unknown>)[key] = value;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return variable;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function staticVariable(
|
|
493
|
+
overrides?: Partial<Record<string, unknown>>,
|
|
494
|
+
): WorkflowVariable {
|
|
495
|
+
const variable: WorkflowVariable = new WorkflowVariable();
|
|
496
|
+
variable._id = VARIABLE_ID.toString();
|
|
497
|
+
variable.name = "API_KEY";
|
|
498
|
+
variable.variableType = WorkflowVariableType.Static;
|
|
499
|
+
|
|
500
|
+
// The model types isSecret as a string, but the column is a boolean.
|
|
501
|
+
return applyOverrides(variable, { isSecret: false, ...(overrides || {}) });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function oauthVariable(
|
|
505
|
+
overrides?: Partial<Record<string, unknown>>,
|
|
506
|
+
): WorkflowVariable {
|
|
507
|
+
const variable: WorkflowVariable = new WorkflowVariable();
|
|
508
|
+
variable._id = VARIABLE_ID.toString();
|
|
509
|
+
variable.name = "API_TOKEN";
|
|
510
|
+
variable.variableType = WorkflowVariableType.OAuth2;
|
|
511
|
+
variable.oauthGrantType = OAuth2GrantType.ClientCredentials;
|
|
512
|
+
|
|
513
|
+
return applyOverrides(variable, { isSecret: true, ...(overrides || {}) });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// A variable local to WORKFLOW_ID.
|
|
517
|
+
function localStaticVariable(
|
|
518
|
+
overrides?: Partial<Record<string, unknown>>,
|
|
519
|
+
): WorkflowVariable {
|
|
520
|
+
return staticVariable({ workflowId: WORKFLOW_ID, ...(overrides || {}) });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function localOAuthVariable(
|
|
524
|
+
overrides?: Partial<Record<string, unknown>>,
|
|
525
|
+
): WorkflowVariable {
|
|
526
|
+
return oauthVariable({ workflowId: WORKFLOW_ID, ...(overrides || {}) });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function renderView(data?: {
|
|
530
|
+
workflowId?: ObjectID | undefined;
|
|
531
|
+
variableId?: ObjectID | undefined;
|
|
532
|
+
}): ReturnType<typeof render> {
|
|
533
|
+
return render(
|
|
534
|
+
<WorkflowVariableView
|
|
535
|
+
variableId={data?.variableId || VARIABLE_ID}
|
|
536
|
+
workflowId={data?.workflowId}
|
|
537
|
+
/>,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function waitForPage(): Promise<void> {
|
|
542
|
+
await screen.findByTestId("model-delete");
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Loads `variable` and renders the global page (or a workflow's, with workflowId).
|
|
546
|
+
async function renderLoaded(
|
|
547
|
+
variable: WorkflowVariable,
|
|
548
|
+
workflowId?: ObjectID | undefined,
|
|
549
|
+
): Promise<ReturnType<typeof render>> {
|
|
550
|
+
getItem.mockResolvedValue(variable);
|
|
551
|
+
|
|
552
|
+
const result: ReturnType<typeof render> = renderView({ workflowId });
|
|
553
|
+
|
|
554
|
+
await waitForPage();
|
|
555
|
+
|
|
556
|
+
// From here on, any loader render is one a reload caused.
|
|
557
|
+
pageLoaderRenderCount = 0;
|
|
558
|
+
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function expectNoLoaderSinceLoad(): void {
|
|
563
|
+
expect(pageLoaderRenderCount).toBe(0);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function detail(name: string): CapturedDetailProps {
|
|
567
|
+
const props: CapturedDetailProps | undefined = capturedDetails[name];
|
|
568
|
+
|
|
569
|
+
if (!props) {
|
|
570
|
+
throw new Error(`The page rendered no "${name}" card`);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return props;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function card(title: string): CapturedCardProps {
|
|
577
|
+
const props: CapturedCardProps | undefined = capturedCards[title];
|
|
578
|
+
|
|
579
|
+
if (!props) {
|
|
580
|
+
throw new Error(`The page rendered no "${title}" card`);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return props;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function cardButton(
|
|
587
|
+
props: CapturedCardProps,
|
|
588
|
+
title: string,
|
|
589
|
+
): CardButtonEntry | undefined {
|
|
590
|
+
return (props.buttons || []).find((button: CardButtonEntry) => {
|
|
591
|
+
return button.title === title;
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
type NamedField = { field?: Record<string, unknown> | undefined };
|
|
596
|
+
|
|
597
|
+
function fieldNames(fields: Array<NamedField>): Array<string> {
|
|
598
|
+
return fields.map((entry: NamedField) => {
|
|
599
|
+
return Object.keys(entry.field || {})[0] || "";
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function detailFormFieldNames(name: string): Array<string> {
|
|
604
|
+
return fieldNames(detail(name).formFields || []);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function detailViewField(name: string, title: string): DetailField {
|
|
608
|
+
const field: DetailField | undefined = detail(
|
|
609
|
+
name,
|
|
610
|
+
).modelDetailProps.fields.find((entry: DetailField) => {
|
|
611
|
+
return entry.title === title;
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
if (!field) {
|
|
615
|
+
throw new Error(`The "${name}" card shows no "${title}" field`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return field;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function renderDetailElement(
|
|
622
|
+
name: string,
|
|
623
|
+
title: string,
|
|
624
|
+
item: WorkflowVariable,
|
|
625
|
+
): HTMLElement {
|
|
626
|
+
const element: ReactElement = detailViewField(name, title).getElement!(item);
|
|
627
|
+
|
|
628
|
+
return render(element).container;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function getItemCall(index: number = 0): {
|
|
632
|
+
modelType: unknown;
|
|
633
|
+
id: ObjectID;
|
|
634
|
+
select: Record<string, unknown>;
|
|
635
|
+
} {
|
|
636
|
+
return getItem.mock.calls[index]![0] as {
|
|
637
|
+
modelType: unknown;
|
|
638
|
+
id: ObjectID;
|
|
639
|
+
select: Record<string, unknown>;
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function refreshCall(index: number = 0): {
|
|
644
|
+
url: unknown;
|
|
645
|
+
headers: Record<string, string>;
|
|
646
|
+
data: unknown;
|
|
647
|
+
} {
|
|
648
|
+
return apiPost.mock.calls[index]![0] as {
|
|
649
|
+
url: unknown;
|
|
650
|
+
headers: Record<string, string>;
|
|
651
|
+
data: unknown;
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function tokenModal(): CapturedTokenRefreshModalProps {
|
|
656
|
+
if (!capturedTokenRefreshModalProps) {
|
|
657
|
+
throw new Error("No token refresh modal is open");
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return capturedTokenRefreshModalProps;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function tokenStatus(): CapturedTokenStatusProps {
|
|
664
|
+
if (!capturedTokenStatusProps) {
|
|
665
|
+
throw new Error("The page rendered no token status");
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return capturedTokenStatusProps;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function missingUpdatePermissionMessage(): string {
|
|
672
|
+
return PermissionGate.getMissingPermissionMessage(
|
|
673
|
+
new WorkflowVariable(),
|
|
674
|
+
ModelAction.Update,
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function clickRefreshNow(): Promise<void> {
|
|
679
|
+
await act(async () => {
|
|
680
|
+
fireEvent.click(screen.getByTestId("refresh-now"));
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
async function waitForOutcome(): Promise<void> {
|
|
685
|
+
await waitFor(() => {
|
|
686
|
+
expect(capturedTokenRefreshModalProps?.outcome).toBeTruthy();
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const NOT_FOUND_MESSAGE: RegExp = /This variable could not be found/;
|
|
691
|
+
|
|
692
|
+
/*
|
|
693
|
+
* What the API really answers for a missing, deleted or out-of-tenant id: an
|
|
694
|
+
* empty object, which ModelAPI turns into a WorkflowVariable with nothing set.
|
|
695
|
+
*/
|
|
696
|
+
function missingVariable(): WorkflowVariable {
|
|
697
|
+
return new WorkflowVariable();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/*
|
|
701
|
+
* Makes the next read (a background reload after a save or a token refresh)
|
|
702
|
+
* wait until the test settles it.
|
|
703
|
+
*/
|
|
704
|
+
function holdNextRead(): Deferred<unknown> {
|
|
705
|
+
const pending: Deferred<unknown> = deferred<unknown>();
|
|
706
|
+
getItem.mockReturnValueOnce(pending.promise);
|
|
707
|
+
return pending;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function waitForReads(count: number): Promise<void> {
|
|
711
|
+
await waitFor(() => {
|
|
712
|
+
expect(getItem).toHaveBeenCalledTimes(count);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// The variable's page, whole: its cards and its delete card, and no error.
|
|
717
|
+
function expectOAuthPageShown(): void {
|
|
718
|
+
expect(screen.getByTestId(`detail:${DETAILS_CARD}`)).toBeInTheDocument();
|
|
719
|
+
expect(screen.getByTestId("card:Access Token")).toBeInTheDocument();
|
|
720
|
+
expect(
|
|
721
|
+
screen.getByTestId(`detail:${OAUTH_SETTINGS_CARD}`),
|
|
722
|
+
).toBeInTheDocument();
|
|
723
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
724
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
725
|
+
expect(screen.queryByTestId("refresh-button")).not.toBeInTheDocument();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
beforeEach(() => {
|
|
729
|
+
capturedCards = {};
|
|
730
|
+
capturedDetails = {};
|
|
731
|
+
capturedDeleteProps = null;
|
|
732
|
+
capturedContentModalProps = null;
|
|
733
|
+
capturedCredentialsModalProps = null;
|
|
734
|
+
capturedTokenRefreshModalProps = null;
|
|
735
|
+
capturedTokenStatusProps = null;
|
|
736
|
+
pageLoaderRenderCount = 0;
|
|
737
|
+
|
|
738
|
+
getItem.mockReset();
|
|
739
|
+
getItem.mockResolvedValue(staticVariable());
|
|
740
|
+
updateById.mockReset();
|
|
741
|
+
updateById.mockResolvedValue(undefined);
|
|
742
|
+
apiPost.mockReset();
|
|
743
|
+
apiPost.mockResolvedValue({
|
|
744
|
+
data: { oauthAccessTokenExpiresAt: TOKEN_EXPIRES_AT },
|
|
745
|
+
});
|
|
746
|
+
navigate.mockReset();
|
|
747
|
+
|
|
748
|
+
lastParams = {};
|
|
749
|
+
getLastParamAsObjectID.mockReset();
|
|
750
|
+
getLastParamAsObjectID.mockImplementation((index?: number): unknown => {
|
|
751
|
+
return lastParams[index ?? 0] || UNEXPECTED_PARAM_ID;
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
permissionsForTest = [Permission.ProjectAdmin];
|
|
755
|
+
isMasterAdminForTest = false;
|
|
756
|
+
PermissionGate.clearPermissionPropsCache();
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
afterEach(() => {
|
|
760
|
+
cleanup();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
describe("getVariableScopeError", () => {
|
|
764
|
+
test("a global variable on the global page is fine", () => {
|
|
765
|
+
expect(getVariableScopeError({ variable: staticVariable() })).toBeNull();
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
test("a local variable on the global page is refused, pointing at its workflow", () => {
|
|
769
|
+
const error: string | null = getVariableScopeError({
|
|
770
|
+
variable: localStaticVariable(),
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
expect(error).toContain("belongs to a workflow");
|
|
774
|
+
expect(error).toContain("Workflow Variables page");
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
test("a variable of this workflow on this workflow's page is fine", () => {
|
|
778
|
+
expect(
|
|
779
|
+
getVariableScopeError({
|
|
780
|
+
variable: localStaticVariable(),
|
|
781
|
+
workflowId: WORKFLOW_ID,
|
|
782
|
+
}),
|
|
783
|
+
).toBeNull();
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
test("compares workflow ids by value, not by object", () => {
|
|
787
|
+
expect(
|
|
788
|
+
getVariableScopeError({
|
|
789
|
+
variable: localStaticVariable(),
|
|
790
|
+
workflowId: new ObjectID(WORKFLOW_ID.toString()),
|
|
791
|
+
}),
|
|
792
|
+
).toBeNull();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("another workflow's variable on this workflow's page is refused", () => {
|
|
796
|
+
expect(
|
|
797
|
+
getVariableScopeError({
|
|
798
|
+
variable: staticVariable({ workflowId: OTHER_WORKFLOW_ID }),
|
|
799
|
+
workflowId: WORKFLOW_ID,
|
|
800
|
+
}),
|
|
801
|
+
).toBe("This variable does not belong to this workflow.");
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("a global variable on a workflow's page is refused", () => {
|
|
805
|
+
expect(
|
|
806
|
+
getVariableScopeError({
|
|
807
|
+
variable: staticVariable(),
|
|
808
|
+
workflowId: WORKFLOW_ID,
|
|
809
|
+
}),
|
|
810
|
+
).toBe("This variable does not belong to this workflow.");
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
describe("loading the variable", () => {
|
|
815
|
+
test("reads the variable by its id", async () => {
|
|
816
|
+
await renderLoaded(staticVariable());
|
|
817
|
+
|
|
818
|
+
expect(getItem).toHaveBeenCalledTimes(1);
|
|
819
|
+
expect(getItemCall().modelType).toBe(WorkflowVariable);
|
|
820
|
+
expect(getItemCall().id.toString()).toBe(VARIABLE_ID.toString());
|
|
821
|
+
expect(getItemCall().select).toBe(WORKFLOW_VARIABLE_VIEW_SELECT);
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
test("selects what the layout and the token status need", async () => {
|
|
825
|
+
await renderLoaded(oauthVariable());
|
|
826
|
+
|
|
827
|
+
expect(Object.keys(getItemCall().select)).toEqual(
|
|
828
|
+
expect.arrayContaining([
|
|
829
|
+
"_id",
|
|
830
|
+
"name",
|
|
831
|
+
"variableType",
|
|
832
|
+
"workflowId",
|
|
833
|
+
"isSecret",
|
|
834
|
+
"oauthGrantType",
|
|
835
|
+
"oauthAccessTokenExpiresAt",
|
|
836
|
+
"oauthLastRefreshedAt",
|
|
837
|
+
"oauthLastRefreshError",
|
|
838
|
+
"oauthLastRefreshErrorAt",
|
|
839
|
+
]),
|
|
840
|
+
);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
/*
|
|
844
|
+
* These columns have an empty read list. Selecting any one of them makes the
|
|
845
|
+
* server refuse the whole request, so the page would never load at all - and
|
|
846
|
+
* it would be the wrong place to show a secret if it did.
|
|
847
|
+
*/
|
|
848
|
+
test("never selects content, a credential or a token", async () => {
|
|
849
|
+
await renderLoaded(oauthVariable());
|
|
850
|
+
|
|
851
|
+
for (const column of WRITE_ONLY_COLUMNS) {
|
|
852
|
+
expect(getItemCall().select).not.toHaveProperty(column);
|
|
853
|
+
expect(WORKFLOW_VARIABLE_VIEW_SELECT).not.toHaveProperty(column);
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
test("shows a loader until the variable arrives, and nothing else", async () => {
|
|
858
|
+
const pending: Deferred<unknown> = deferred<unknown>();
|
|
859
|
+
getItem.mockReturnValue(pending.promise);
|
|
860
|
+
|
|
861
|
+
renderView();
|
|
862
|
+
|
|
863
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
864
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
865
|
+
expect(capturedDetails[DETAILS_CARD]).toBeUndefined();
|
|
866
|
+
|
|
867
|
+
await act(async () => {
|
|
868
|
+
pending.resolve(staticVariable());
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
await waitForPage();
|
|
872
|
+
|
|
873
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
874
|
+
expect(screen.getByTestId(`detail:${DETAILS_CARD}`)).toBeInTheDocument();
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
/*
|
|
878
|
+
* The API answers a missing id with an empty object, not null. Read as a
|
|
879
|
+
* variable, that has no type - so without the id check it would be drawn
|
|
880
|
+
* as an empty static variable with an Update Content button.
|
|
881
|
+
*/
|
|
882
|
+
test("says so when the variable is not found", async () => {
|
|
883
|
+
getItem.mockResolvedValue(missingVariable());
|
|
884
|
+
|
|
885
|
+
renderView();
|
|
886
|
+
|
|
887
|
+
expect(await screen.findByText(NOT_FOUND_MESSAGE)).toBeInTheDocument();
|
|
888
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
889
|
+
// Nothing to edit or delete.
|
|
890
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
891
|
+
expect(capturedDetails[DETAILS_CARD]).toBeUndefined();
|
|
892
|
+
expect(screen.queryByTestId("card:Content")).not.toBeInTheDocument();
|
|
893
|
+
expect(
|
|
894
|
+
screen.queryByTestId("card-button:Update Content"),
|
|
895
|
+
).not.toBeInTheDocument();
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("says so when the read answers with nothing at all", async () => {
|
|
899
|
+
getItem.mockResolvedValue(null);
|
|
900
|
+
|
|
901
|
+
renderView();
|
|
902
|
+
|
|
903
|
+
expect(await screen.findByText(NOT_FOUND_MESSAGE)).toBeInTheDocument();
|
|
904
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
905
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
906
|
+
expect(capturedDetails[DETAILS_CARD]).toBeUndefined();
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
/*
|
|
910
|
+
* An empty variable has no workflowId either, so a scope check run first
|
|
911
|
+
* would call a missing variable "another workflow's".
|
|
912
|
+
*/
|
|
913
|
+
test("a workflow's page says a missing variable is missing, not another workflow's", async () => {
|
|
914
|
+
getItem.mockResolvedValue(missingVariable());
|
|
915
|
+
|
|
916
|
+
renderView({ workflowId: WORKFLOW_ID });
|
|
917
|
+
|
|
918
|
+
expect(await screen.findByText(NOT_FOUND_MESSAGE)).toBeInTheDocument();
|
|
919
|
+
expect(screen.queryByText(/does not belong/)).not.toBeInTheDocument();
|
|
920
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
921
|
+
expect(capturedDetails[DETAILS_CARD]).toBeUndefined();
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
test("a workflow's page says so for a null read too", async () => {
|
|
925
|
+
getItem.mockResolvedValue(null);
|
|
926
|
+
|
|
927
|
+
renderView({ workflowId: WORKFLOW_ID });
|
|
928
|
+
|
|
929
|
+
expect(await screen.findByText(NOT_FOUND_MESSAGE)).toBeInTheDocument();
|
|
930
|
+
expect(screen.queryByText(/does not belong/)).not.toBeInTheDocument();
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
// A variable with values but no id is still not one the page can act on.
|
|
934
|
+
test("a read with fields but no id is not found either", async () => {
|
|
935
|
+
const variable: WorkflowVariable = new WorkflowVariable();
|
|
936
|
+
variable.name = "API_KEY";
|
|
937
|
+
variable.variableType = WorkflowVariableType.Static;
|
|
938
|
+
|
|
939
|
+
getItem.mockResolvedValue(variable);
|
|
940
|
+
|
|
941
|
+
renderView();
|
|
942
|
+
|
|
943
|
+
expect(await screen.findByText(NOT_FOUND_MESSAGE)).toBeInTheDocument();
|
|
944
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
test("shows the friendly message when the request fails", async () => {
|
|
948
|
+
getItem.mockRejectedValue(new Error("The API is unreachable."));
|
|
949
|
+
|
|
950
|
+
renderView();
|
|
951
|
+
|
|
952
|
+
expect(
|
|
953
|
+
await screen.findByText("The API is unreachable."),
|
|
954
|
+
).toBeInTheDocument();
|
|
955
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
956
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
test("Refresh? on the error reads the variable again", async () => {
|
|
960
|
+
getItem.mockRejectedValueOnce(new Error("The API is unreachable."));
|
|
961
|
+
getItem.mockResolvedValueOnce(staticVariable());
|
|
962
|
+
|
|
963
|
+
renderView();
|
|
964
|
+
|
|
965
|
+
await screen.findByText("The API is unreachable.");
|
|
966
|
+
|
|
967
|
+
await act(async () => {
|
|
968
|
+
fireEvent.click(screen.getByTestId("refresh-button"));
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
await waitForPage();
|
|
972
|
+
|
|
973
|
+
expect(getItem).toHaveBeenCalledTimes(2);
|
|
974
|
+
expect(
|
|
975
|
+
screen.queryByText("The API is unreachable."),
|
|
976
|
+
).not.toBeInTheDocument();
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
// The error owns the page, so its retry is a foreground load: loader, then page.
|
|
980
|
+
test("Refresh? on the error retries in the foreground and shows the variable", async () => {
|
|
981
|
+
const retry: Deferred<unknown> = deferred<unknown>();
|
|
982
|
+
getItem.mockRejectedValueOnce(new Error("The API is unreachable."));
|
|
983
|
+
getItem.mockReturnValueOnce(retry.promise);
|
|
984
|
+
|
|
985
|
+
renderView();
|
|
986
|
+
|
|
987
|
+
await screen.findByText("The API is unreachable.");
|
|
988
|
+
|
|
989
|
+
await act(async () => {
|
|
990
|
+
fireEvent.click(screen.getByTestId("refresh-button"));
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
994
|
+
expect(
|
|
995
|
+
screen.queryByText("The API is unreachable."),
|
|
996
|
+
).not.toBeInTheDocument();
|
|
997
|
+
|
|
998
|
+
await act(async () => {
|
|
999
|
+
retry.resolve(oauthVariable());
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
await waitForPage();
|
|
1003
|
+
|
|
1004
|
+
expectOAuthPageShown();
|
|
1005
|
+
expect(tokenStatus().variable.name).toBe("API_TOKEN");
|
|
1006
|
+
expect(getItemCall(1).id.toString()).toBe(VARIABLE_ID.toString());
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
test("Refresh? after a not-found shows the variable once it is there", async () => {
|
|
1010
|
+
getItem.mockResolvedValueOnce(missingVariable());
|
|
1011
|
+
getItem.mockResolvedValueOnce(staticVariable());
|
|
1012
|
+
|
|
1013
|
+
renderView();
|
|
1014
|
+
|
|
1015
|
+
await screen.findByText(NOT_FOUND_MESSAGE);
|
|
1016
|
+
|
|
1017
|
+
await act(async () => {
|
|
1018
|
+
fireEvent.click(screen.getByTestId("refresh-button"));
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
await waitForPage();
|
|
1022
|
+
|
|
1023
|
+
expect(screen.queryByText(NOT_FOUND_MESSAGE)).not.toBeInTheDocument();
|
|
1024
|
+
expect(screen.getByTestId("card:Content")).toBeInTheDocument();
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
test("Refresh? that fails again shows the new error", async () => {
|
|
1028
|
+
getItem.mockRejectedValueOnce(new Error("The API is unreachable."));
|
|
1029
|
+
getItem.mockRejectedValueOnce(new Error("Still unreachable."));
|
|
1030
|
+
|
|
1031
|
+
renderView();
|
|
1032
|
+
|
|
1033
|
+
await screen.findByText("The API is unreachable.");
|
|
1034
|
+
|
|
1035
|
+
await act(async () => {
|
|
1036
|
+
fireEvent.click(screen.getByTestId("refresh-button"));
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
expect(await screen.findByText("Still unreachable.")).toBeInTheDocument();
|
|
1040
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
1041
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
test("reads the new variable when the page moves to another one", async () => {
|
|
1045
|
+
const result: ReturnType<typeof render> =
|
|
1046
|
+
await renderLoaded(staticVariable());
|
|
1047
|
+
|
|
1048
|
+
getItem.mockResolvedValue(
|
|
1049
|
+
staticVariable({ _id: OTHER_VARIABLE_ID.toString(), name: "OTHER" }),
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
result.rerender(<WorkflowVariableView variableId={OTHER_VARIABLE_ID} />);
|
|
1053
|
+
|
|
1054
|
+
await waitFor(() => {
|
|
1055
|
+
expect(getItem).toHaveBeenCalledTimes(2);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
expect(getItemCall(1).id.toString()).toBe(OTHER_VARIABLE_ID.toString());
|
|
1059
|
+
|
|
1060
|
+
await waitFor(() => {
|
|
1061
|
+
expect(detail(DETAILS_CARD).modelDetailProps.modelId.toString()).toBe(
|
|
1062
|
+
OTHER_VARIABLE_ID.toString(),
|
|
1063
|
+
);
|
|
1064
|
+
});
|
|
1065
|
+
expect(capturedDeleteProps?.modelId.toString()).toBe(
|
|
1066
|
+
OTHER_VARIABLE_ID.toString(),
|
|
1067
|
+
);
|
|
1068
|
+
});
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
/*
|
|
1072
|
+
* Moving from one variable's page to another's reuses the component, so the
|
|
1073
|
+
* first variable's read can still be in flight when the second one starts.
|
|
1074
|
+
* Whichever answers last, the page must show the variable it is now on.
|
|
1075
|
+
*
|
|
1076
|
+
* The two variables differ in type, so the layout alone says which one
|
|
1077
|
+
* landed: the cards' modelId comes from the prop, not from the read.
|
|
1078
|
+
*/
|
|
1079
|
+
describe("a slow read never overwrites a newer one", () => {
|
|
1080
|
+
function otherStaticVariable(): WorkflowVariable {
|
|
1081
|
+
return staticVariable({ _id: OTHER_VARIABLE_ID.toString(), name: "OTHER" });
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function expectOtherVariableShown(): void {
|
|
1085
|
+
expect(screen.getByTestId("card:Content")).toBeInTheDocument();
|
|
1086
|
+
expect(screen.queryByTestId("card:Access Token")).not.toBeInTheDocument();
|
|
1087
|
+
expect(capturedDetails[OAUTH_SETTINGS_CARD]).toBeUndefined();
|
|
1088
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
1089
|
+
|
|
1090
|
+
// The variable the page acts on is the one it read for OTHER.
|
|
1091
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
1092
|
+
expect(capturedContentModalProps?.variable.name).toBe("OTHER");
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
test("the previous variable's read answering last is ignored", async () => {
|
|
1096
|
+
const first: Deferred<unknown> = deferred<unknown>();
|
|
1097
|
+
const second: Deferred<unknown> = deferred<unknown>();
|
|
1098
|
+
getItem.mockReturnValueOnce(first.promise);
|
|
1099
|
+
getItem.mockReturnValueOnce(second.promise);
|
|
1100
|
+
|
|
1101
|
+
const result: ReturnType<typeof render> = renderView();
|
|
1102
|
+
|
|
1103
|
+
result.rerender(<WorkflowVariableView variableId={OTHER_VARIABLE_ID} />);
|
|
1104
|
+
|
|
1105
|
+
await waitForReads(2);
|
|
1106
|
+
expect(getItemCall(0).id.toString()).toBe(VARIABLE_ID.toString());
|
|
1107
|
+
expect(getItemCall(1).id.toString()).toBe(OTHER_VARIABLE_ID.toString());
|
|
1108
|
+
|
|
1109
|
+
await act(async () => {
|
|
1110
|
+
second.resolve(otherStaticVariable());
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
await waitForPage();
|
|
1114
|
+
|
|
1115
|
+
await act(async () => {
|
|
1116
|
+
first.resolve(oauthVariable());
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
expectOtherVariableShown();
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
test("the previous variable's read answering first does not land either", async () => {
|
|
1123
|
+
const first: Deferred<unknown> = deferred<unknown>();
|
|
1124
|
+
const second: Deferred<unknown> = deferred<unknown>();
|
|
1125
|
+
getItem.mockReturnValueOnce(first.promise);
|
|
1126
|
+
getItem.mockReturnValueOnce(second.promise);
|
|
1127
|
+
|
|
1128
|
+
const result: ReturnType<typeof render> = renderView();
|
|
1129
|
+
|
|
1130
|
+
result.rerender(<WorkflowVariableView variableId={OTHER_VARIABLE_ID} />);
|
|
1131
|
+
|
|
1132
|
+
await waitForReads(2);
|
|
1133
|
+
|
|
1134
|
+
await act(async () => {
|
|
1135
|
+
first.resolve(oauthVariable());
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
// Still waiting for the variable the page is on.
|
|
1139
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
1140
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
1141
|
+
|
|
1142
|
+
await act(async () => {
|
|
1143
|
+
second.resolve(otherStaticVariable());
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
await waitForPage();
|
|
1147
|
+
|
|
1148
|
+
expectOtherVariableShown();
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
test("the previous variable's late failure does not replace the page with an error", async () => {
|
|
1152
|
+
const first: Deferred<unknown> = deferred<unknown>();
|
|
1153
|
+
const second: Deferred<unknown> = deferred<unknown>();
|
|
1154
|
+
getItem.mockReturnValueOnce(first.promise);
|
|
1155
|
+
getItem.mockReturnValueOnce(second.promise);
|
|
1156
|
+
|
|
1157
|
+
const result: ReturnType<typeof render> = renderView();
|
|
1158
|
+
|
|
1159
|
+
result.rerender(<WorkflowVariableView variableId={OTHER_VARIABLE_ID} />);
|
|
1160
|
+
|
|
1161
|
+
await waitForReads(2);
|
|
1162
|
+
|
|
1163
|
+
await act(async () => {
|
|
1164
|
+
second.resolve(otherStaticVariable());
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
await waitForPage();
|
|
1168
|
+
|
|
1169
|
+
await act(async () => {
|
|
1170
|
+
first.reject(new Error("The API is unreachable."));
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
expect(
|
|
1174
|
+
screen.queryByText("The API is unreachable."),
|
|
1175
|
+
).not.toBeInTheDocument();
|
|
1176
|
+
expectOtherVariableShown();
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
// Two saves in a row: the first save's reload must not undo the second's.
|
|
1180
|
+
test("an older background reload answering last is ignored", async () => {
|
|
1181
|
+
await renderLoaded(staticVariable({ isSecret: false }));
|
|
1182
|
+
|
|
1183
|
+
const olderReload: Deferred<unknown> = holdNextRead();
|
|
1184
|
+
const newerReload: Deferred<unknown> = holdNextRead();
|
|
1185
|
+
|
|
1186
|
+
await act(async () => {
|
|
1187
|
+
detail(DETAILS_CARD).onSaveSuccess?.(staticVariable());
|
|
1188
|
+
});
|
|
1189
|
+
await act(async () => {
|
|
1190
|
+
detail(DETAILS_CARD).onSaveSuccess?.(staticVariable({ isSecret: true }));
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
await waitForReads(3);
|
|
1194
|
+
|
|
1195
|
+
await act(async () => {
|
|
1196
|
+
newerReload.resolve(staticVariable({ isSecret: true }));
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
await waitFor(() => {
|
|
1200
|
+
expect(detailFormFieldNames(DETAILS_CARD)).toEqual([
|
|
1201
|
+
"name",
|
|
1202
|
+
"description",
|
|
1203
|
+
]);
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
await act(async () => {
|
|
1207
|
+
olderReload.resolve(staticVariable({ isSecret: false }));
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
// The secret toggle would come back if the stale read landed.
|
|
1211
|
+
expect(detailFormFieldNames(DETAILS_CARD)).toEqual(["name", "description"]);
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
test("a read that answers after the page is gone does nothing", async () => {
|
|
1215
|
+
const pending: Deferred<unknown> = deferred<unknown>();
|
|
1216
|
+
getItem.mockReturnValueOnce(pending.promise);
|
|
1217
|
+
|
|
1218
|
+
const errorSpy: ReturnType<typeof jest.spyOn> = jest
|
|
1219
|
+
.spyOn(console, "error")
|
|
1220
|
+
.mockImplementation((): void => {});
|
|
1221
|
+
|
|
1222
|
+
try {
|
|
1223
|
+
const result: ReturnType<typeof render> = renderView();
|
|
1224
|
+
|
|
1225
|
+
result.unmount();
|
|
1226
|
+
|
|
1227
|
+
await act(async () => {
|
|
1228
|
+
pending.resolve(oauthVariable());
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
1232
|
+
expect(capturedDetails[DETAILS_CARD]).toBeUndefined();
|
|
1233
|
+
expect(capturedTokenStatusProps).toBeNull();
|
|
1234
|
+
} finally {
|
|
1235
|
+
errorSpy.mockRestore();
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
test("a read that fails after the page is gone does nothing", async () => {
|
|
1240
|
+
const pending: Deferred<unknown> = deferred<unknown>();
|
|
1241
|
+
getItem.mockReturnValueOnce(pending.promise);
|
|
1242
|
+
|
|
1243
|
+
const errorSpy: ReturnType<typeof jest.spyOn> = jest
|
|
1244
|
+
.spyOn(console, "error")
|
|
1245
|
+
.mockImplementation((): void => {});
|
|
1246
|
+
|
|
1247
|
+
try {
|
|
1248
|
+
const result: ReturnType<typeof render> = renderView();
|
|
1249
|
+
|
|
1250
|
+
result.unmount();
|
|
1251
|
+
|
|
1252
|
+
await act(async () => {
|
|
1253
|
+
pending.reject(new Error("The API is unreachable."));
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
1257
|
+
} finally {
|
|
1258
|
+
errorSpy.mockRestore();
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
/*
|
|
1264
|
+
* After a save or a token refresh the page reads the variable again to pick
|
|
1265
|
+
* up what was written. The page already shows a variable that exists, and the
|
|
1266
|
+
* token refresh result on top of it is the one thing somebody needs to read
|
|
1267
|
+
* right then - so a reload never shows the loader, and a failed one leaves
|
|
1268
|
+
* everything where it was.
|
|
1269
|
+
*/
|
|
1270
|
+
describe("reloads after a save or a token refresh", () => {
|
|
1271
|
+
test("a failed reload after Refresh now leaves the page and the outcome on screen", async () => {
|
|
1272
|
+
await renderLoaded(oauthVariable());
|
|
1273
|
+
|
|
1274
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1275
|
+
|
|
1276
|
+
await clickRefreshNow();
|
|
1277
|
+
await waitForOutcome();
|
|
1278
|
+
await waitForReads(2);
|
|
1279
|
+
|
|
1280
|
+
await act(async () => {
|
|
1281
|
+
reload.reject(new Error("The API is unreachable."));
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
expectOAuthPageShown();
|
|
1285
|
+
expect(
|
|
1286
|
+
screen.queryByText("The API is unreachable."),
|
|
1287
|
+
).not.toBeInTheDocument();
|
|
1288
|
+
expect(screen.getByTestId("token-refresh-modal")).toHaveAttribute(
|
|
1289
|
+
"data-pending",
|
|
1290
|
+
"false",
|
|
1291
|
+
);
|
|
1292
|
+
expect(tokenModal().outcome?.expiresAt?.toISOString()).toBe(
|
|
1293
|
+
TOKEN_EXPIRES_AT,
|
|
1294
|
+
);
|
|
1295
|
+
// The token status still shows the variable it had.
|
|
1296
|
+
expect(tokenStatus().variable.name).toBe("API_TOKEN");
|
|
1297
|
+
expectNoLoaderSinceLoad();
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
test("a failed reload after a refused Refresh now keeps the refusal on screen", async () => {
|
|
1301
|
+
apiPost.mockRejectedValue(new Error("invalid_client"));
|
|
1302
|
+
|
|
1303
|
+
await renderLoaded(oauthVariable());
|
|
1304
|
+
|
|
1305
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1306
|
+
|
|
1307
|
+
await clickRefreshNow();
|
|
1308
|
+
await waitForOutcome();
|
|
1309
|
+
await waitForReads(2);
|
|
1310
|
+
|
|
1311
|
+
await act(async () => {
|
|
1312
|
+
reload.reject(new Error("The API is unreachable."));
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
expectOAuthPageShown();
|
|
1316
|
+
expect(screen.getByTestId("token-refresh-modal")).toBeInTheDocument();
|
|
1317
|
+
expect(tokenModal().outcome?.error).toBe("invalid_client");
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
test("a failed reload after a credentials save leaves the page and the outcome on screen", async () => {
|
|
1321
|
+
await renderLoaded(oauthVariable());
|
|
1322
|
+
|
|
1323
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1324
|
+
|
|
1325
|
+
fireEvent.click(screen.getByTestId("card-button:Update Credentials"));
|
|
1326
|
+
|
|
1327
|
+
await act(async () => {
|
|
1328
|
+
capturedCredentialsModalProps?.onSaved("Client secret");
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
await waitForOutcome();
|
|
1332
|
+
await waitForReads(2);
|
|
1333
|
+
|
|
1334
|
+
await act(async () => {
|
|
1335
|
+
reload.reject(new Error("The API is unreachable."));
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
expectOAuthPageShown();
|
|
1339
|
+
expect(
|
|
1340
|
+
screen.queryByText("The API is unreachable."),
|
|
1341
|
+
).not.toBeInTheDocument();
|
|
1342
|
+
expect(screen.getByTestId("token-refresh-modal")).toHaveAttribute(
|
|
1343
|
+
"data-pending",
|
|
1344
|
+
"false",
|
|
1345
|
+
);
|
|
1346
|
+
expect(tokenModal().outcome?.savedWhat).toBe("Client secret");
|
|
1347
|
+
expect(tokenModal().variableName).toBe("API_TOKEN");
|
|
1348
|
+
expectNoLoaderSinceLoad();
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
test("a failed reload after saving the details leaves the page", async () => {
|
|
1352
|
+
await renderLoaded(oauthVariable());
|
|
1353
|
+
|
|
1354
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1355
|
+
|
|
1356
|
+
await act(async () => {
|
|
1357
|
+
detail(DETAILS_CARD).onSaveSuccess?.(oauthVariable());
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
await waitForReads(2);
|
|
1361
|
+
|
|
1362
|
+
await act(async () => {
|
|
1363
|
+
reload.reject(new Error("The API is unreachable."));
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
expectOAuthPageShown();
|
|
1367
|
+
expect(
|
|
1368
|
+
screen.queryByText("The API is unreachable."),
|
|
1369
|
+
).not.toBeInTheDocument();
|
|
1370
|
+
expect(tokenStatus().variable.name).toBe("API_TOKEN");
|
|
1371
|
+
expectNoLoaderSinceLoad();
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
test("a failed reload after saving the OAuth 2.0 settings leaves the page", async () => {
|
|
1375
|
+
await renderLoaded(oauthVariable());
|
|
1376
|
+
|
|
1377
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1378
|
+
|
|
1379
|
+
await act(async () => {
|
|
1380
|
+
detail(OAUTH_SETTINGS_CARD).onSaveSuccess?.(oauthVariable());
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
await waitForReads(2);
|
|
1384
|
+
|
|
1385
|
+
await act(async () => {
|
|
1386
|
+
reload.reject(new Error("The API is unreachable."));
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
expectOAuthPageShown();
|
|
1390
|
+
expect(
|
|
1391
|
+
screen.queryByText("The API is unreachable."),
|
|
1392
|
+
).not.toBeInTheDocument();
|
|
1393
|
+
expectNoLoaderSinceLoad();
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
test("a failed reload after saving a static variable's details leaves the page", async () => {
|
|
1397
|
+
await renderLoaded(staticVariable());
|
|
1398
|
+
|
|
1399
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1400
|
+
|
|
1401
|
+
await act(async () => {
|
|
1402
|
+
detail(DETAILS_CARD).onSaveSuccess?.(staticVariable());
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
await waitForReads(2);
|
|
1406
|
+
|
|
1407
|
+
await act(async () => {
|
|
1408
|
+
reload.reject(new Error("The API is unreachable."));
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
expect(screen.getByTestId(`detail:${DETAILS_CARD}`)).toBeInTheDocument();
|
|
1412
|
+
expect(screen.getByTestId("card:Content")).toBeInTheDocument();
|
|
1413
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
1414
|
+
expect(
|
|
1415
|
+
screen.queryByText("The API is unreachable."),
|
|
1416
|
+
).not.toBeInTheDocument();
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
// A reload that reads back nothing is a failed reload too, not a not-found page.
|
|
1420
|
+
test("a reload that finds no variable leaves the page and the outcome on screen", async () => {
|
|
1421
|
+
await renderLoaded(oauthVariable());
|
|
1422
|
+
|
|
1423
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1424
|
+
|
|
1425
|
+
await clickRefreshNow();
|
|
1426
|
+
await waitForOutcome();
|
|
1427
|
+
await waitForReads(2);
|
|
1428
|
+
|
|
1429
|
+
await act(async () => {
|
|
1430
|
+
reload.resolve(missingVariable());
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
expectOAuthPageShown();
|
|
1434
|
+
expect(screen.queryByText(NOT_FOUND_MESSAGE)).not.toBeInTheDocument();
|
|
1435
|
+
expect(screen.getByTestId("token-refresh-modal")).toBeInTheDocument();
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
test("a successful reload after Refresh now updates the token status without the loader", async () => {
|
|
1439
|
+
await renderLoaded(oauthVariable());
|
|
1440
|
+
|
|
1441
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1442
|
+
|
|
1443
|
+
await clickRefreshNow();
|
|
1444
|
+
await waitForOutcome();
|
|
1445
|
+
await waitForReads(2);
|
|
1446
|
+
|
|
1447
|
+
// The reload is out, and the page is still the page.
|
|
1448
|
+
expectOAuthPageShown();
|
|
1449
|
+
expect(tokenStatus().variable.oauthLastRefreshError).toBeUndefined();
|
|
1450
|
+
|
|
1451
|
+
await act(async () => {
|
|
1452
|
+
reload.resolve(
|
|
1453
|
+
oauthVariable({ oauthLastRefreshError: "invalid_client" }),
|
|
1454
|
+
);
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
await waitFor(() => {
|
|
1458
|
+
expect(tokenStatus().variable.oauthLastRefreshError).toBe(
|
|
1459
|
+
"invalid_client",
|
|
1460
|
+
);
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
expectOAuthPageShown();
|
|
1464
|
+
expect(screen.getByTestId("token-refresh-modal")).toBeInTheDocument();
|
|
1465
|
+
// Only the first load, before the page was up, ever showed it.
|
|
1466
|
+
expectNoLoaderSinceLoad();
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
test("a successful reload after a settings save updates the page without the loader", async () => {
|
|
1470
|
+
await renderLoaded(oauthVariable());
|
|
1471
|
+
|
|
1472
|
+
const reload: Deferred<unknown> = holdNextRead();
|
|
1473
|
+
|
|
1474
|
+
await act(async () => {
|
|
1475
|
+
detail(OAUTH_SETTINGS_CARD).onSaveSuccess?.(oauthVariable());
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
await waitForReads(2);
|
|
1479
|
+
|
|
1480
|
+
expectOAuthPageShown();
|
|
1481
|
+
|
|
1482
|
+
await act(async () => {
|
|
1483
|
+
reload.resolve(oauthVariable({ oauthLastRefreshError: "invalid_scope" }));
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
await waitFor(() => {
|
|
1487
|
+
expect(tokenStatus().variable.oauthLastRefreshError).toBe(
|
|
1488
|
+
"invalid_scope",
|
|
1489
|
+
);
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1492
|
+
expectOAuthPageShown();
|
|
1493
|
+
expectNoLoaderSinceLoad();
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
test("a failed reload does not stop the next one from landing", async () => {
|
|
1497
|
+
await renderLoaded(oauthVariable());
|
|
1498
|
+
|
|
1499
|
+
getItem.mockRejectedValueOnce(new Error("The API is unreachable."));
|
|
1500
|
+
|
|
1501
|
+
await act(async () => {
|
|
1502
|
+
detail(DETAILS_CARD).onSaveSuccess?.(oauthVariable());
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
await waitForReads(2);
|
|
1506
|
+
|
|
1507
|
+
getItem.mockResolvedValueOnce(
|
|
1508
|
+
oauthVariable({ oauthLastRefreshError: "invalid_client" }),
|
|
1509
|
+
);
|
|
1510
|
+
|
|
1511
|
+
await clickRefreshNow();
|
|
1512
|
+
await waitForOutcome();
|
|
1513
|
+
|
|
1514
|
+
await waitFor(() => {
|
|
1515
|
+
expect(tokenStatus().variable.oauthLastRefreshError).toBe(
|
|
1516
|
+
"invalid_client",
|
|
1517
|
+
);
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
expectOAuthPageShown();
|
|
1521
|
+
expectNoLoaderSinceLoad();
|
|
1522
|
+
});
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
describe("the variable's scope", () => {
|
|
1526
|
+
test("the global page refuses a variable that belongs to a workflow", async () => {
|
|
1527
|
+
getItem.mockResolvedValue(localStaticVariable());
|
|
1528
|
+
|
|
1529
|
+
renderView();
|
|
1530
|
+
|
|
1531
|
+
expect(
|
|
1532
|
+
await screen.findByText(/This variable belongs to a workflow/),
|
|
1533
|
+
).toBeInTheDocument();
|
|
1534
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
1535
|
+
expect(capturedDetails[DETAILS_CARD]).toBeUndefined();
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
test("a workflow's page refuses another workflow's variable", async () => {
|
|
1539
|
+
getItem.mockResolvedValue(
|
|
1540
|
+
staticVariable({ workflowId: OTHER_WORKFLOW_ID }),
|
|
1541
|
+
);
|
|
1542
|
+
|
|
1543
|
+
renderView({ workflowId: WORKFLOW_ID });
|
|
1544
|
+
|
|
1545
|
+
expect(
|
|
1546
|
+
await screen.findByText(
|
|
1547
|
+
"This variable does not belong to this workflow.",
|
|
1548
|
+
),
|
|
1549
|
+
).toBeInTheDocument();
|
|
1550
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
1551
|
+
expect(capturedDetails[DETAILS_CARD]).toBeUndefined();
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
test("a workflow's page refuses a global variable", async () => {
|
|
1555
|
+
getItem.mockResolvedValue(staticVariable());
|
|
1556
|
+
|
|
1557
|
+
renderView({ workflowId: WORKFLOW_ID });
|
|
1558
|
+
|
|
1559
|
+
expect(
|
|
1560
|
+
await screen.findByText(
|
|
1561
|
+
"This variable does not belong to this workflow.",
|
|
1562
|
+
),
|
|
1563
|
+
).toBeInTheDocument();
|
|
1564
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
test("the global page shows a global variable", async () => {
|
|
1568
|
+
await renderLoaded(staticVariable());
|
|
1569
|
+
|
|
1570
|
+
expect(screen.queryByText(/does not belong/)).not.toBeInTheDocument();
|
|
1571
|
+
expect(screen.queryByText(/belongs to a workflow/)).not.toBeInTheDocument();
|
|
1572
|
+
expect(screen.getByTestId(`detail:${DETAILS_CARD}`)).toBeInTheDocument();
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
test("a workflow's page shows that workflow's variable", async () => {
|
|
1576
|
+
await renderLoaded(localStaticVariable(), WORKFLOW_ID);
|
|
1577
|
+
|
|
1578
|
+
expect(screen.queryByText(/does not belong/)).not.toBeInTheDocument();
|
|
1579
|
+
expect(screen.getByTestId(`detail:${DETAILS_CARD}`)).toBeInTheDocument();
|
|
1580
|
+
});
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
describe("a static variable", () => {
|
|
1584
|
+
test("has an editable Variable Details card for this variable", async () => {
|
|
1585
|
+
await renderLoaded(staticVariable());
|
|
1586
|
+
|
|
1587
|
+
const details: CapturedDetailProps = detail(DETAILS_CARD);
|
|
1588
|
+
|
|
1589
|
+
expect(details.cardProps.title).toBe("Variable Details");
|
|
1590
|
+
expect(details.isEditable).toBe(true);
|
|
1591
|
+
expect(details.editButtonText).toBe("Edit Variable");
|
|
1592
|
+
expect(details.modelDetailProps.modelType).toBe(WorkflowVariable);
|
|
1593
|
+
expect(details.modelDetailProps.modelId.toString()).toBe(
|
|
1594
|
+
VARIABLE_ID.toString(),
|
|
1595
|
+
);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
test("its edit form offers the name, the description and the secret toggle", async () => {
|
|
1599
|
+
await renderLoaded(staticVariable({ isSecret: false }));
|
|
1600
|
+
|
|
1601
|
+
expect(detailFormFieldNames(DETAILS_CARD)).toEqual([
|
|
1602
|
+
"name",
|
|
1603
|
+
"description",
|
|
1604
|
+
"isSecret",
|
|
1605
|
+
]);
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
/*
|
|
1609
|
+
* The server refuses to turn the flag off again, so a toggle that can only
|
|
1610
|
+
* fail is not offered once the variable is secret.
|
|
1611
|
+
*/
|
|
1612
|
+
test("its edit form has no secret toggle once the variable is secret", async () => {
|
|
1613
|
+
await renderLoaded(staticVariable({ isSecret: true }));
|
|
1614
|
+
|
|
1615
|
+
expect(detailFormFieldNames(DETAILS_CARD)).toEqual(["name", "description"]);
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
test("never shows or edits the content on the details card", async () => {
|
|
1619
|
+
await renderLoaded(staticVariable());
|
|
1620
|
+
|
|
1621
|
+
const details: CapturedDetailProps = detail(DETAILS_CARD);
|
|
1622
|
+
|
|
1623
|
+
for (const column of WRITE_ONLY_COLUMNS) {
|
|
1624
|
+
expect(detailFormFieldNames(DETAILS_CARD)).not.toContain(column);
|
|
1625
|
+
expect(fieldNames(details.modelDetailProps.fields)).not.toContain(column);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// The type is fixed once saved.
|
|
1629
|
+
expect(detailFormFieldNames(DETAILS_CARD)).not.toContain("variableType");
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
test("saving the details reads the variable again, and drops the toggle once it is secret", async () => {
|
|
1633
|
+
await renderLoaded(staticVariable({ isSecret: false }));
|
|
1634
|
+
|
|
1635
|
+
getItem.mockResolvedValue(staticVariable({ isSecret: true }));
|
|
1636
|
+
|
|
1637
|
+
await act(async () => {
|
|
1638
|
+
detail(DETAILS_CARD).onSaveSuccess?.(staticVariable({ isSecret: true }));
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
await waitFor(() => {
|
|
1642
|
+
expect(detailFormFieldNames(DETAILS_CARD)).toEqual([
|
|
1643
|
+
"name",
|
|
1644
|
+
"description",
|
|
1645
|
+
]);
|
|
1646
|
+
});
|
|
1647
|
+
expect(getItem).toHaveBeenCalledTimes(2);
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
test("a reload after a save keeps the page on screen instead of the loader", async () => {
|
|
1651
|
+
await renderLoaded(staticVariable());
|
|
1652
|
+
|
|
1653
|
+
const pending: Deferred<unknown> = deferred<unknown>();
|
|
1654
|
+
getItem.mockReturnValue(pending.promise);
|
|
1655
|
+
|
|
1656
|
+
await act(async () => {
|
|
1657
|
+
detail(DETAILS_CARD).onSaveSuccess?.(staticVariable());
|
|
1658
|
+
});
|
|
1659
|
+
|
|
1660
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
1661
|
+
expect(screen.getByTestId(`detail:${DETAILS_CARD}`)).toBeInTheDocument();
|
|
1662
|
+
|
|
1663
|
+
await act(async () => {
|
|
1664
|
+
pending.resolve(staticVariable());
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
expectNoLoaderSinceLoad();
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
test("shows its type as Static", async () => {
|
|
1671
|
+
await renderLoaded(staticVariable());
|
|
1672
|
+
|
|
1673
|
+
const element: HTMLElement = renderDetailElement(
|
|
1674
|
+
DETAILS_CARD,
|
|
1675
|
+
"Type",
|
|
1676
|
+
staticVariable(),
|
|
1677
|
+
);
|
|
1678
|
+
|
|
1679
|
+
expect(
|
|
1680
|
+
within(element).getByTestId("workflow-variable-type"),
|
|
1681
|
+
).toHaveTextContent("Static");
|
|
1682
|
+
});
|
|
1683
|
+
|
|
1684
|
+
test("shows whether it is secret", async () => {
|
|
1685
|
+
await renderLoaded(staticVariable());
|
|
1686
|
+
|
|
1687
|
+
expect(
|
|
1688
|
+
renderDetailElement(
|
|
1689
|
+
DETAILS_CARD,
|
|
1690
|
+
"Secret",
|
|
1691
|
+
staticVariable({ isSecret: true }),
|
|
1692
|
+
),
|
|
1693
|
+
).toHaveTextContent("Secret");
|
|
1694
|
+
expect(
|
|
1695
|
+
renderDetailElement(
|
|
1696
|
+
DETAILS_CARD,
|
|
1697
|
+
"Secret",
|
|
1698
|
+
staticVariable({ isSecret: false }),
|
|
1699
|
+
),
|
|
1700
|
+
).toHaveTextContent("Not secret");
|
|
1701
|
+
expect(detailViewField(DETAILS_CARD, "Secret").description).toContain(
|
|
1702
|
+
"[REDACTED]",
|
|
1703
|
+
);
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
test("has a Content card with an Update Content button", async () => {
|
|
1707
|
+
await renderLoaded(staticVariable());
|
|
1708
|
+
|
|
1709
|
+
expect(screen.getByTestId("card:Content")).toBeInTheDocument();
|
|
1710
|
+
expect(
|
|
1711
|
+
screen.getByTestId("card-button:Update Content"),
|
|
1712
|
+
).toBeInTheDocument();
|
|
1713
|
+
expect(
|
|
1714
|
+
screen.getByTestId("workflow-variable-content-note"),
|
|
1715
|
+
).toHaveTextContent("Hidden. The content cannot be viewed once saved.");
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
test("never renders the content, even if the API were to return it", async () => {
|
|
1719
|
+
await renderLoaded(staticVariable({ content: "super-secret-value" }));
|
|
1720
|
+
|
|
1721
|
+
expect(screen.queryByText(/super-secret-value/)).not.toBeInTheDocument();
|
|
1722
|
+
});
|
|
1723
|
+
|
|
1724
|
+
test("Update Content opens the content modal for this variable", async () => {
|
|
1725
|
+
await renderLoaded(staticVariable());
|
|
1726
|
+
|
|
1727
|
+
expect(
|
|
1728
|
+
screen.queryByTestId("update-content-modal"),
|
|
1729
|
+
).not.toBeInTheDocument();
|
|
1730
|
+
|
|
1731
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
1732
|
+
|
|
1733
|
+
expect(screen.getByTestId("update-content-modal")).toBeInTheDocument();
|
|
1734
|
+
expect(capturedContentModalProps?.variable.name).toBe("API_KEY");
|
|
1735
|
+
expect(String(capturedContentModalProps?.variable.id)).toBe(
|
|
1736
|
+
VARIABLE_ID.toString(),
|
|
1737
|
+
);
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
test("a saved content closes the modal and says the content was updated", async () => {
|
|
1741
|
+
await renderLoaded(staticVariable());
|
|
1742
|
+
|
|
1743
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
1744
|
+
|
|
1745
|
+
act(() => {
|
|
1746
|
+
capturedContentModalProps?.onSuccess();
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
expect(
|
|
1750
|
+
screen.queryByTestId("update-content-modal"),
|
|
1751
|
+
).not.toBeInTheDocument();
|
|
1752
|
+
expect(
|
|
1753
|
+
screen.getByTestId("workflow-variable-content-note"),
|
|
1754
|
+
).toHaveTextContent("Content updated.");
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
test("a closed content modal changes nothing", async () => {
|
|
1758
|
+
await renderLoaded(staticVariable());
|
|
1759
|
+
|
|
1760
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
1761
|
+
|
|
1762
|
+
act(() => {
|
|
1763
|
+
capturedContentModalProps?.onClose();
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
expect(
|
|
1767
|
+
screen.queryByTestId("update-content-modal"),
|
|
1768
|
+
).not.toBeInTheDocument();
|
|
1769
|
+
expect(
|
|
1770
|
+
screen.getByTestId("workflow-variable-content-note"),
|
|
1771
|
+
).toHaveTextContent("Hidden.");
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
test("opening the content modal again clears the updated note", async () => {
|
|
1775
|
+
await renderLoaded(staticVariable());
|
|
1776
|
+
|
|
1777
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
1778
|
+
|
|
1779
|
+
act(() => {
|
|
1780
|
+
capturedContentModalProps?.onSuccess();
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
1784
|
+
|
|
1785
|
+
expect(
|
|
1786
|
+
screen.getByTestId("workflow-variable-content-note"),
|
|
1787
|
+
).toHaveTextContent("Hidden.");
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
test("has no Access Token card, no OAuth 2.0 Settings and no Update Credentials", async () => {
|
|
1791
|
+
await renderLoaded(staticVariable());
|
|
1792
|
+
|
|
1793
|
+
expect(screen.queryByTestId("card:Access Token")).not.toBeInTheDocument();
|
|
1794
|
+
expect(screen.queryByTestId("token-status")).not.toBeInTheDocument();
|
|
1795
|
+
expect(
|
|
1796
|
+
screen.queryByTestId(`detail:${OAUTH_SETTINGS_CARD}`),
|
|
1797
|
+
).not.toBeInTheDocument();
|
|
1798
|
+
expect(capturedDetails[OAUTH_SETTINGS_CARD]).toBeUndefined();
|
|
1799
|
+
expect(
|
|
1800
|
+
screen.queryByTestId("card-button:Update Credentials"),
|
|
1801
|
+
).not.toBeInTheDocument();
|
|
1802
|
+
});
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
describe("an OAuth 2.0 variable", () => {
|
|
1806
|
+
test("has an Access Token card showing the token's status with Refresh now", async () => {
|
|
1807
|
+
await renderLoaded(oauthVariable());
|
|
1808
|
+
|
|
1809
|
+
expect(screen.getByTestId("card:Access Token")).toBeInTheDocument();
|
|
1810
|
+
expect(
|
|
1811
|
+
within(screen.getByTestId("card:Access Token")).getByTestId(
|
|
1812
|
+
"token-status",
|
|
1813
|
+
),
|
|
1814
|
+
).toBeInTheDocument();
|
|
1815
|
+
expect(tokenStatus().variable.name).toBe("API_TOKEN");
|
|
1816
|
+
expect(String(tokenStatus().variable.id)).toBe(VARIABLE_ID.toString());
|
|
1817
|
+
expect(tokenStatus().refreshAction).toBeDefined();
|
|
1818
|
+
expect(screen.getByTestId("refresh-now")).toBeEnabled();
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1821
|
+
test("has an editable OAuth 2.0 Settings card for this variable", async () => {
|
|
1822
|
+
await renderLoaded(oauthVariable());
|
|
1823
|
+
|
|
1824
|
+
const settings: CapturedDetailProps = detail(OAUTH_SETTINGS_CARD);
|
|
1825
|
+
|
|
1826
|
+
expect(settings.cardProps.title).toBe("OAuth 2.0 Settings");
|
|
1827
|
+
expect(settings.isEditable).toBe(true);
|
|
1828
|
+
expect(settings.editButtonText).toBe("Edit Settings");
|
|
1829
|
+
expect(settings.modelDetailProps.modelType).toBe(WorkflowVariable);
|
|
1830
|
+
expect(settings.modelDetailProps.modelId.toString()).toBe(
|
|
1831
|
+
VARIABLE_ID.toString(),
|
|
1832
|
+
);
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1835
|
+
test("its settings form is exactly the readable settings", async () => {
|
|
1836
|
+
await renderLoaded(oauthVariable());
|
|
1837
|
+
|
|
1838
|
+
expect(detailFormFieldNames(OAUTH_SETTINGS_CARD)).toEqual([
|
|
1839
|
+
"oauthTokenUrl",
|
|
1840
|
+
"oauthClientId",
|
|
1841
|
+
"oauthScope",
|
|
1842
|
+
"oauthAdditionalParameters",
|
|
1843
|
+
"oauthClientAuthenticationMethod",
|
|
1844
|
+
]);
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
/*
|
|
1848
|
+
* The secret and refresh token are write-only (their own modal replaces
|
|
1849
|
+
* them), and the grant and the type are fixed once saved.
|
|
1850
|
+
*/
|
|
1851
|
+
test("its settings form never carries a credential, the grant or the type", async () => {
|
|
1852
|
+
await renderLoaded(oauthVariable());
|
|
1853
|
+
|
|
1854
|
+
for (const column of [
|
|
1855
|
+
"oauthClientSecret",
|
|
1856
|
+
"oauthRefreshToken",
|
|
1857
|
+
"oauthAccessToken",
|
|
1858
|
+
"oauthGrantType",
|
|
1859
|
+
"variableType",
|
|
1860
|
+
"content",
|
|
1861
|
+
]) {
|
|
1862
|
+
expect(detailFormFieldNames(OAUTH_SETTINGS_CARD)).not.toContain(column);
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1866
|
+
test("its settings card shows the grant but never a credential or a token", async () => {
|
|
1867
|
+
await renderLoaded(oauthVariable());
|
|
1868
|
+
|
|
1869
|
+
const shown: Array<string> = fieldNames(
|
|
1870
|
+
detail(OAUTH_SETTINGS_CARD).modelDetailProps.fields,
|
|
1871
|
+
);
|
|
1872
|
+
|
|
1873
|
+
expect(shown).toEqual(
|
|
1874
|
+
expect.arrayContaining([
|
|
1875
|
+
"oauthGrantType",
|
|
1876
|
+
"oauthTokenUrl",
|
|
1877
|
+
"oauthClientId",
|
|
1878
|
+
"oauthScope",
|
|
1879
|
+
"oauthClientAuthenticationMethod",
|
|
1880
|
+
"oauthAdditionalParameters",
|
|
1881
|
+
]),
|
|
1882
|
+
);
|
|
1883
|
+
|
|
1884
|
+
for (const column of WRITE_ONLY_COLUMNS) {
|
|
1885
|
+
expect(shown).not.toContain(column);
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1889
|
+
test("shows the client authentication method in words", async () => {
|
|
1890
|
+
await renderLoaded(oauthVariable());
|
|
1891
|
+
|
|
1892
|
+
expect(
|
|
1893
|
+
renderDetailElement(
|
|
1894
|
+
OAUTH_SETTINGS_CARD,
|
|
1895
|
+
"Client Authentication",
|
|
1896
|
+
oauthVariable({
|
|
1897
|
+
oauthClientAuthenticationMethod:
|
|
1898
|
+
OAuth2ClientAuthenticationMethod.RequestBody,
|
|
1899
|
+
}),
|
|
1900
|
+
),
|
|
1901
|
+
).toHaveTextContent("Request body (client_secret_post)");
|
|
1902
|
+
|
|
1903
|
+
// Nothing saved means the default, HTTP Basic.
|
|
1904
|
+
expect(
|
|
1905
|
+
renderDetailElement(
|
|
1906
|
+
OAUTH_SETTINGS_CARD,
|
|
1907
|
+
"Client Authentication",
|
|
1908
|
+
oauthVariable(),
|
|
1909
|
+
),
|
|
1910
|
+
).toHaveTextContent("HTTP Basic header (client_secret_basic)");
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
test("its details form has no secret toggle", async () => {
|
|
1914
|
+
await renderLoaded(oauthVariable({ isSecret: true }));
|
|
1915
|
+
|
|
1916
|
+
expect(detailFormFieldNames(DETAILS_CARD)).toEqual(["name", "description"]);
|
|
1917
|
+
});
|
|
1918
|
+
|
|
1919
|
+
test("its details form has no secret toggle even if the flag reads false", async () => {
|
|
1920
|
+
await renderLoaded(oauthVariable({ isSecret: false }));
|
|
1921
|
+
|
|
1922
|
+
expect(detailFormFieldNames(DETAILS_CARD)).toEqual(["name", "description"]);
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1925
|
+
test("shows its type as OAuth 2.0, and that it is always secret", async () => {
|
|
1926
|
+
await renderLoaded(oauthVariable());
|
|
1927
|
+
|
|
1928
|
+
expect(
|
|
1929
|
+
renderDetailElement(DETAILS_CARD, "Type", oauthVariable()),
|
|
1930
|
+
).toHaveTextContent("OAuth 2.0");
|
|
1931
|
+
expect(detailViewField(DETAILS_CARD, "Secret").description).toBe(
|
|
1932
|
+
"OAuth 2.0 variables are always secret.",
|
|
1933
|
+
);
|
|
1934
|
+
});
|
|
1935
|
+
|
|
1936
|
+
test("has an Update Credentials button that opens the credentials modal for this variable", async () => {
|
|
1937
|
+
await renderLoaded(oauthVariable());
|
|
1938
|
+
|
|
1939
|
+
expect(
|
|
1940
|
+
cardButton(detail(OAUTH_SETTINGS_CARD).cardProps, "Update Credentials"),
|
|
1941
|
+
).toBeDefined();
|
|
1942
|
+
expect(
|
|
1943
|
+
screen.queryByTestId("update-credentials-modal"),
|
|
1944
|
+
).not.toBeInTheDocument();
|
|
1945
|
+
|
|
1946
|
+
fireEvent.click(screen.getByTestId("card-button:Update Credentials"));
|
|
1947
|
+
|
|
1948
|
+
expect(screen.getByTestId("update-credentials-modal")).toBeInTheDocument();
|
|
1949
|
+
expect(capturedCredentialsModalProps?.variable.name).toBe("API_TOKEN");
|
|
1950
|
+
expect(String(capturedCredentialsModalProps?.variable.id)).toBe(
|
|
1951
|
+
VARIABLE_ID.toString(),
|
|
1952
|
+
);
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
test("has no Content card and no Update Content", async () => {
|
|
1956
|
+
await renderLoaded(oauthVariable());
|
|
1957
|
+
|
|
1958
|
+
expect(screen.queryByTestId("card:Content")).not.toBeInTheDocument();
|
|
1959
|
+
expect(
|
|
1960
|
+
screen.queryByTestId("card-button:Update Content"),
|
|
1961
|
+
).not.toBeInTheDocument();
|
|
1962
|
+
expect(
|
|
1963
|
+
screen.queryByTestId("workflow-variable-content-note"),
|
|
1964
|
+
).not.toBeInTheDocument();
|
|
1965
|
+
});
|
|
1966
|
+
|
|
1967
|
+
// A settings change discards the cached token, so the status is stale.
|
|
1968
|
+
test("saving the settings reads the variable again", async () => {
|
|
1969
|
+
await renderLoaded(oauthVariable());
|
|
1970
|
+
|
|
1971
|
+
getItem.mockResolvedValue(
|
|
1972
|
+
oauthVariable({ oauthLastRefreshError: "invalid_scope" }),
|
|
1973
|
+
);
|
|
1974
|
+
|
|
1975
|
+
await act(async () => {
|
|
1976
|
+
detail(OAUTH_SETTINGS_CARD).onSaveSuccess?.(oauthVariable());
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
await waitFor(() => {
|
|
1980
|
+
expect(tokenStatus().variable.oauthLastRefreshError).toBe(
|
|
1981
|
+
"invalid_scope",
|
|
1982
|
+
);
|
|
1983
|
+
});
|
|
1984
|
+
expect(getItem).toHaveBeenCalledTimes(2);
|
|
1985
|
+
});
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
describe("how workflows refer to the variable", () => {
|
|
1989
|
+
test("the global page shows {{global.variables.NAME}}", async () => {
|
|
1990
|
+
await renderLoaded(staticVariable());
|
|
1991
|
+
|
|
1992
|
+
const element: HTMLElement = renderDetailElement(
|
|
1993
|
+
DETAILS_CARD,
|
|
1994
|
+
"Use in Workflows",
|
|
1995
|
+
staticVariable(),
|
|
1996
|
+
);
|
|
1997
|
+
|
|
1998
|
+
expect(
|
|
1999
|
+
within(element).getByTestId("workflow-variable-reference"),
|
|
2000
|
+
).toHaveTextContent("{{global.variables.API_KEY}}");
|
|
2001
|
+
});
|
|
2002
|
+
|
|
2003
|
+
test("a workflow's page shows {{local.variables.NAME}}", async () => {
|
|
2004
|
+
await renderLoaded(localStaticVariable(), WORKFLOW_ID);
|
|
2005
|
+
|
|
2006
|
+
const element: HTMLElement = renderDetailElement(
|
|
2007
|
+
DETAILS_CARD,
|
|
2008
|
+
"Use in Workflows",
|
|
2009
|
+
localStaticVariable(),
|
|
2010
|
+
);
|
|
2011
|
+
|
|
2012
|
+
expect(
|
|
2013
|
+
within(element).getByTestId("workflow-variable-reference"),
|
|
2014
|
+
).toHaveTextContent("{{local.variables.API_KEY}}");
|
|
2015
|
+
});
|
|
2016
|
+
|
|
2017
|
+
test("the reference follows the name the card renders", async () => {
|
|
2018
|
+
await renderLoaded(oauthVariable());
|
|
2019
|
+
|
|
2020
|
+
const element: HTMLElement = renderDetailElement(
|
|
2021
|
+
DETAILS_CARD,
|
|
2022
|
+
"Use in Workflows",
|
|
2023
|
+
oauthVariable({ name: "RENAMED_TOKEN" }),
|
|
2024
|
+
);
|
|
2025
|
+
|
|
2026
|
+
expect(
|
|
2027
|
+
within(element).getByTestId("workflow-variable-reference"),
|
|
2028
|
+
).toHaveTextContent("{{global.variables.RENAMED_TOKEN}}");
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
test("the rename warning on the name field quotes the right scope", async () => {
|
|
2032
|
+
await renderLoaded(staticVariable());
|
|
2033
|
+
|
|
2034
|
+
const globalName: FormFieldEntry | undefined = (detail(DETAILS_CARD)
|
|
2035
|
+
.formFields || [])[0];
|
|
2036
|
+
|
|
2037
|
+
expect(globalName?.description).toContain("{{global.variables.THIS_NAME}}");
|
|
2038
|
+
expect(globalName?.description).toContain("Renaming it does not update");
|
|
2039
|
+
|
|
2040
|
+
cleanup();
|
|
2041
|
+
capturedDetails = {};
|
|
2042
|
+
|
|
2043
|
+
await renderLoaded(localStaticVariable(), WORKFLOW_ID);
|
|
2044
|
+
|
|
2045
|
+
const localName: FormFieldEntry | undefined = (detail(DETAILS_CARD)
|
|
2046
|
+
.formFields || [])[0];
|
|
2047
|
+
|
|
2048
|
+
expect(localName?.description).toContain("{{local.variables.THIS_NAME}}");
|
|
2049
|
+
});
|
|
2050
|
+
});
|
|
2051
|
+
|
|
2052
|
+
describe("Refresh now", () => {
|
|
2053
|
+
test("posts to the variable's refresh route with the project header", async () => {
|
|
2054
|
+
await renderLoaded(oauthVariable());
|
|
2055
|
+
|
|
2056
|
+
await clickRefreshNow();
|
|
2057
|
+
|
|
2058
|
+
await waitFor(() => {
|
|
2059
|
+
expect(apiPost).toHaveBeenCalledTimes(1);
|
|
2060
|
+
});
|
|
2061
|
+
expect(String(refreshCall().url)).toContain(
|
|
2062
|
+
`/workflow-variable/${VARIABLE_ID.toString()}/refresh-oauth-token`,
|
|
2063
|
+
);
|
|
2064
|
+
expect(refreshCall().headers).toEqual({ tenantid: "project-header" });
|
|
2065
|
+
expect(refreshCall().data).toEqual({});
|
|
2066
|
+
});
|
|
2067
|
+
|
|
2068
|
+
test("shows the token that was fetched and when it expires", async () => {
|
|
2069
|
+
await renderLoaded(oauthVariable());
|
|
2070
|
+
|
|
2071
|
+
await clickRefreshNow();
|
|
2072
|
+
await waitForOutcome();
|
|
2073
|
+
|
|
2074
|
+
expect(screen.getByTestId("token-refresh-modal")).toHaveAttribute(
|
|
2075
|
+
"data-pending",
|
|
2076
|
+
"false",
|
|
2077
|
+
);
|
|
2078
|
+
expect(tokenModal().variableName).toBe("API_TOKEN");
|
|
2079
|
+
|
|
2080
|
+
const outcome: TokenRefreshOutcome = tokenModal()
|
|
2081
|
+
.outcome as TokenRefreshOutcome;
|
|
2082
|
+
|
|
2083
|
+
expect(outcome.variableName).toBe("API_TOKEN");
|
|
2084
|
+
expect(outcome.error).toBeUndefined();
|
|
2085
|
+
expect(outcome.savedWhat).toBeUndefined();
|
|
2086
|
+
expect(outcome.expiresAt?.toISOString()).toBe(TOKEN_EXPIRES_AT);
|
|
2087
|
+
expect(getTokenRefreshTitle(outcome)).toBe("Access Token Fetched");
|
|
2088
|
+
});
|
|
2089
|
+
|
|
2090
|
+
test("shows the identity provider's refusal", async () => {
|
|
2091
|
+
apiPost.mockRejectedValue(
|
|
2092
|
+
new Error(
|
|
2093
|
+
"The token endpoint refused the request (HTTP 401): invalid_client.",
|
|
2094
|
+
),
|
|
2095
|
+
);
|
|
2096
|
+
|
|
2097
|
+
await renderLoaded(oauthVariable());
|
|
2098
|
+
|
|
2099
|
+
await clickRefreshNow();
|
|
2100
|
+
await waitForOutcome();
|
|
2101
|
+
|
|
2102
|
+
const outcome: TokenRefreshOutcome = tokenModal()
|
|
2103
|
+
.outcome as TokenRefreshOutcome;
|
|
2104
|
+
|
|
2105
|
+
expect(outcome.error).toBe(
|
|
2106
|
+
"The token endpoint refused the request (HTTP 401): invalid_client.",
|
|
2107
|
+
);
|
|
2108
|
+
expect(getTokenRefreshTitle(outcome)).toBe(
|
|
2109
|
+
"Could Not Fetch an Access Token",
|
|
2110
|
+
);
|
|
2111
|
+
expect(getTokenRefreshDescription(outcome)).toContain("invalid_client");
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
// Refresh now has its own spinner, so no pending modal opens over it.
|
|
2115
|
+
test("spins the button while the request is out, without a pending modal", async () => {
|
|
2116
|
+
const pending: Deferred<unknown> = deferred<unknown>();
|
|
2117
|
+
apiPost.mockReturnValue(pending.promise);
|
|
2118
|
+
|
|
2119
|
+
await renderLoaded(oauthVariable());
|
|
2120
|
+
|
|
2121
|
+
await clickRefreshNow();
|
|
2122
|
+
|
|
2123
|
+
expect(tokenStatus().refreshAction?.isLoading).toBe(true);
|
|
2124
|
+
expect(screen.getByTestId("refresh-now")).toBeDisabled();
|
|
2125
|
+
expect(screen.queryByTestId("token-refresh-modal")).not.toBeInTheDocument();
|
|
2126
|
+
|
|
2127
|
+
await act(async () => {
|
|
2128
|
+
pending.resolve({
|
|
2129
|
+
data: { oauthAccessTokenExpiresAt: TOKEN_EXPIRES_AT },
|
|
2130
|
+
});
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2133
|
+
await waitForOutcome();
|
|
2134
|
+
|
|
2135
|
+
expect(tokenStatus().refreshAction?.isLoading).toBe(false);
|
|
2136
|
+
expect(screen.getByTestId("refresh-now")).toBeEnabled();
|
|
2137
|
+
});
|
|
2138
|
+
|
|
2139
|
+
// The refresh wrote the expiry, or the failure, to the variable.
|
|
2140
|
+
test("reads the variable again afterwards", async () => {
|
|
2141
|
+
await renderLoaded(oauthVariable());
|
|
2142
|
+
|
|
2143
|
+
getItem.mockResolvedValue(
|
|
2144
|
+
oauthVariable({ oauthLastRefreshError: "invalid_client" }),
|
|
2145
|
+
);
|
|
2146
|
+
|
|
2147
|
+
await clickRefreshNow();
|
|
2148
|
+
await waitForOutcome();
|
|
2149
|
+
|
|
2150
|
+
await waitFor(() => {
|
|
2151
|
+
expect(getItem).toHaveBeenCalledTimes(2);
|
|
2152
|
+
});
|
|
2153
|
+
expect(getItemCall(1).id.toString()).toBe(VARIABLE_ID.toString());
|
|
2154
|
+
await waitFor(() => {
|
|
2155
|
+
expect(tokenStatus().variable.oauthLastRefreshError).toBe(
|
|
2156
|
+
"invalid_client",
|
|
2157
|
+
);
|
|
2158
|
+
});
|
|
2159
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
test("reads the variable again after a failure too", async () => {
|
|
2163
|
+
apiPost.mockRejectedValue(new Error("invalid_client"));
|
|
2164
|
+
|
|
2165
|
+
await renderLoaded(oauthVariable());
|
|
2166
|
+
|
|
2167
|
+
await clickRefreshNow();
|
|
2168
|
+
await waitForOutcome();
|
|
2169
|
+
|
|
2170
|
+
await waitFor(() => {
|
|
2171
|
+
expect(getItem).toHaveBeenCalledTimes(2);
|
|
2172
|
+
});
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
test("the outcome modal closes", async () => {
|
|
2176
|
+
await renderLoaded(oauthVariable());
|
|
2177
|
+
|
|
2178
|
+
await clickRefreshNow();
|
|
2179
|
+
await waitForOutcome();
|
|
2180
|
+
|
|
2181
|
+
act(() => {
|
|
2182
|
+
tokenModal().onClose();
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
expect(screen.queryByTestId("token-refresh-modal")).not.toBeInTheDocument();
|
|
2186
|
+
});
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2189
|
+
describe("Update Credentials", () => {
|
|
2190
|
+
test("closing the modal saves nothing and fetches nothing", async () => {
|
|
2191
|
+
await renderLoaded(oauthVariable());
|
|
2192
|
+
|
|
2193
|
+
fireEvent.click(screen.getByTestId("card-button:Update Credentials"));
|
|
2194
|
+
|
|
2195
|
+
act(() => {
|
|
2196
|
+
capturedCredentialsModalProps?.onClose();
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
expect(
|
|
2200
|
+
screen.queryByTestId("update-credentials-modal"),
|
|
2201
|
+
).not.toBeInTheDocument();
|
|
2202
|
+
expect(apiPost).not.toHaveBeenCalled();
|
|
2203
|
+
expect(screen.queryByTestId("token-refresh-modal")).not.toBeInTheDocument();
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2206
|
+
/*
|
|
2207
|
+
* A token request can take a while. The credentials modal has just closed,
|
|
2208
|
+
* so a pending modal says at once that a token is on its way.
|
|
2209
|
+
*/
|
|
2210
|
+
test("a save closes the modal and shows a pending token modal at once", async () => {
|
|
2211
|
+
const pending: Deferred<unknown> = deferred<unknown>();
|
|
2212
|
+
apiPost.mockReturnValue(pending.promise);
|
|
2213
|
+
|
|
2214
|
+
await renderLoaded(oauthVariable());
|
|
2215
|
+
|
|
2216
|
+
fireEvent.click(screen.getByTestId("card-button:Update Credentials"));
|
|
2217
|
+
|
|
2218
|
+
await act(async () => {
|
|
2219
|
+
capturedCredentialsModalProps?.onSaved("Client secret");
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2222
|
+
expect(
|
|
2223
|
+
screen.queryByTestId("update-credentials-modal"),
|
|
2224
|
+
).not.toBeInTheDocument();
|
|
2225
|
+
expect(screen.getByTestId("token-refresh-modal")).toHaveAttribute(
|
|
2226
|
+
"data-pending",
|
|
2227
|
+
"true",
|
|
2228
|
+
);
|
|
2229
|
+
expect(tokenModal().outcome).toBeNull();
|
|
2230
|
+
expect(tokenModal().variableName).toBe("API_TOKEN");
|
|
2231
|
+
expect(apiPost).toHaveBeenCalledTimes(1);
|
|
2232
|
+
expect(String(refreshCall().url)).toContain(
|
|
2233
|
+
`/workflow-variable/${VARIABLE_ID.toString()}/refresh-oauth-token`,
|
|
2234
|
+
);
|
|
2235
|
+
|
|
2236
|
+
await act(async () => {
|
|
2237
|
+
pending.resolve({
|
|
2238
|
+
data: { oauthAccessTokenExpiresAt: TOKEN_EXPIRES_AT },
|
|
2239
|
+
});
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
await waitForOutcome();
|
|
2243
|
+
|
|
2244
|
+
expect(screen.getByTestId("token-refresh-modal")).toHaveAttribute(
|
|
2245
|
+
"data-pending",
|
|
2246
|
+
"false",
|
|
2247
|
+
);
|
|
2248
|
+
});
|
|
2249
|
+
|
|
2250
|
+
test("the outcome says what was saved and that a token was fetched", async () => {
|
|
2251
|
+
await renderLoaded(oauthVariable());
|
|
2252
|
+
|
|
2253
|
+
fireEvent.click(screen.getByTestId("card-button:Update Credentials"));
|
|
2254
|
+
|
|
2255
|
+
await act(async () => {
|
|
2256
|
+
capturedCredentialsModalProps?.onSaved("Client secret");
|
|
2257
|
+
});
|
|
2258
|
+
|
|
2259
|
+
await waitForOutcome();
|
|
2260
|
+
|
|
2261
|
+
const outcome: TokenRefreshOutcome = tokenModal()
|
|
2262
|
+
.outcome as TokenRefreshOutcome;
|
|
2263
|
+
|
|
2264
|
+
expect(outcome.savedWhat).toBe("Client secret");
|
|
2265
|
+
expect(outcome.variableName).toBe("API_TOKEN");
|
|
2266
|
+
expect(outcome.error).toBeUndefined();
|
|
2267
|
+
expect(outcome.expiresAt?.toISOString()).toBe(TOKEN_EXPIRES_AT);
|
|
2268
|
+
expect(getTokenRefreshDescription(outcome)).toMatch(
|
|
2269
|
+
/^Client secret saved\. OneUptime fetched a new access token/,
|
|
2270
|
+
);
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
test("a saved secret the provider then refused says both", async () => {
|
|
2274
|
+
apiPost.mockRejectedValue(new Error("invalid_client"));
|
|
2275
|
+
|
|
2276
|
+
await renderLoaded(oauthVariable());
|
|
2277
|
+
|
|
2278
|
+
fireEvent.click(screen.getByTestId("card-button:Update Credentials"));
|
|
2279
|
+
|
|
2280
|
+
await act(async () => {
|
|
2281
|
+
capturedCredentialsModalProps?.onSaved("Client secret");
|
|
2282
|
+
});
|
|
2283
|
+
|
|
2284
|
+
await waitForOutcome();
|
|
2285
|
+
|
|
2286
|
+
const outcome: TokenRefreshOutcome = tokenModal()
|
|
2287
|
+
.outcome as TokenRefreshOutcome;
|
|
2288
|
+
|
|
2289
|
+
expect(outcome.savedWhat).toBe("Client secret");
|
|
2290
|
+
expect(outcome.error).toBe("invalid_client");
|
|
2291
|
+
expect(getTokenRefreshTitle(outcome)).toBe(
|
|
2292
|
+
"Could Not Fetch an Access Token",
|
|
2293
|
+
);
|
|
2294
|
+
expect(getTokenRefreshDescription(outcome)).toMatch(
|
|
2295
|
+
/^Client secret saved\. /,
|
|
2296
|
+
);
|
|
2297
|
+
});
|
|
2298
|
+
|
|
2299
|
+
test("reads the variable again after the token request", async () => {
|
|
2300
|
+
await renderLoaded(oauthVariable());
|
|
2301
|
+
|
|
2302
|
+
fireEvent.click(screen.getByTestId("card-button:Update Credentials"));
|
|
2303
|
+
|
|
2304
|
+
await act(async () => {
|
|
2305
|
+
capturedCredentialsModalProps?.onSaved("Client secret and refresh token");
|
|
2306
|
+
});
|
|
2307
|
+
|
|
2308
|
+
await waitForOutcome();
|
|
2309
|
+
|
|
2310
|
+
await waitFor(() => {
|
|
2311
|
+
expect(getItem).toHaveBeenCalledTimes(2);
|
|
2312
|
+
});
|
|
2313
|
+
expect(tokenModal().outcome?.savedWhat).toBe(
|
|
2314
|
+
"Client secret and refresh token",
|
|
2315
|
+
);
|
|
2316
|
+
});
|
|
2317
|
+
});
|
|
2318
|
+
|
|
2319
|
+
describe("permissions", () => {
|
|
2320
|
+
describe("with permission to update variables", () => {
|
|
2321
|
+
test("Update Content is enabled and says what it does", async () => {
|
|
2322
|
+
await renderLoaded(staticVariable());
|
|
2323
|
+
|
|
2324
|
+
const button: HTMLElement = screen.getByTestId(
|
|
2325
|
+
"card-button:Update Content",
|
|
2326
|
+
);
|
|
2327
|
+
|
|
2328
|
+
expect(button).toBeEnabled();
|
|
2329
|
+
expect(button.getAttribute("title")).toContain(
|
|
2330
|
+
"Replace this variable's content",
|
|
2331
|
+
);
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
test("Update Credentials and Refresh now are enabled", async () => {
|
|
2335
|
+
await renderLoaded(oauthVariable());
|
|
2336
|
+
|
|
2337
|
+
expect(
|
|
2338
|
+
screen.getByTestId("card-button:Update Credentials"),
|
|
2339
|
+
).toBeEnabled();
|
|
2340
|
+
expect(screen.getByTestId("refresh-now")).toBeEnabled();
|
|
2341
|
+
expect(tokenStatus().refreshAction?.disabled).toBe(false);
|
|
2342
|
+
expect(tokenStatus().refreshAction?.tooltip).toContain(
|
|
2343
|
+
"Fetch a new access token",
|
|
2344
|
+
);
|
|
2345
|
+
});
|
|
2346
|
+
|
|
2347
|
+
test("the details and settings cards are editable", async () => {
|
|
2348
|
+
await renderLoaded(oauthVariable());
|
|
2349
|
+
|
|
2350
|
+
expect(detail(DETAILS_CARD).isEditable).toBe(true);
|
|
2351
|
+
expect(detail(OAUTH_SETTINGS_CARD).isEditable).toBe(true);
|
|
2352
|
+
});
|
|
2353
|
+
});
|
|
2354
|
+
|
|
2355
|
+
describe("without permission to update variables", () => {
|
|
2356
|
+
beforeEach(() => {
|
|
2357
|
+
permissionsForTest = [Permission.Viewer];
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
test("Update Content is shown disabled, naming the permission it needs", async () => {
|
|
2361
|
+
await renderLoaded(staticVariable());
|
|
2362
|
+
|
|
2363
|
+
const button: HTMLElement = screen.getByTestId(
|
|
2364
|
+
"card-button:Update Content",
|
|
2365
|
+
);
|
|
2366
|
+
|
|
2367
|
+
expect(button).toBeDisabled();
|
|
2368
|
+
expect(button).toHaveAttribute("title", missingUpdatePermissionMessage());
|
|
2369
|
+
expect(button.getAttribute("title")).toContain(
|
|
2370
|
+
"You do not have permission to update",
|
|
2371
|
+
);
|
|
2372
|
+
expect(button.getAttribute("title")).toContain("Project Admin");
|
|
2373
|
+
});
|
|
2374
|
+
|
|
2375
|
+
test("Update Content opens nothing even if its click fires", async () => {
|
|
2376
|
+
await renderLoaded(staticVariable());
|
|
2377
|
+
|
|
2378
|
+
act(() => {
|
|
2379
|
+
cardButton(card("Content"), "Update Content")!.onClick();
|
|
2380
|
+
});
|
|
2381
|
+
|
|
2382
|
+
expect(
|
|
2383
|
+
screen.queryByTestId("update-content-modal"),
|
|
2384
|
+
).not.toBeInTheDocument();
|
|
2385
|
+
});
|
|
2386
|
+
|
|
2387
|
+
test("Update Credentials is shown disabled, naming the permission it needs", async () => {
|
|
2388
|
+
await renderLoaded(oauthVariable());
|
|
2389
|
+
|
|
2390
|
+
const button: HTMLElement = screen.getByTestId(
|
|
2391
|
+
"card-button:Update Credentials",
|
|
2392
|
+
);
|
|
2393
|
+
|
|
2394
|
+
expect(button).toBeDisabled();
|
|
2395
|
+
expect(button).toHaveAttribute("title", missingUpdatePermissionMessage());
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
test("Update Credentials opens nothing even if its click fires", async () => {
|
|
2399
|
+
await renderLoaded(oauthVariable());
|
|
2400
|
+
|
|
2401
|
+
act(() => {
|
|
2402
|
+
cardButton(
|
|
2403
|
+
detail(OAUTH_SETTINGS_CARD).cardProps,
|
|
2404
|
+
"Update Credentials",
|
|
2405
|
+
)!.onClick();
|
|
2406
|
+
});
|
|
2407
|
+
|
|
2408
|
+
expect(
|
|
2409
|
+
screen.queryByTestId("update-credentials-modal"),
|
|
2410
|
+
).not.toBeInTheDocument();
|
|
2411
|
+
});
|
|
2412
|
+
|
|
2413
|
+
test("Refresh now is shown disabled, naming the permission it needs", async () => {
|
|
2414
|
+
await renderLoaded(oauthVariable());
|
|
2415
|
+
|
|
2416
|
+
expect(screen.getByTestId("refresh-now")).toBeDisabled();
|
|
2417
|
+
expect(tokenStatus().refreshAction?.disabled).toBe(true);
|
|
2418
|
+
expect(tokenStatus().refreshAction?.tooltip).toBe(
|
|
2419
|
+
missingUpdatePermissionMessage(),
|
|
2420
|
+
);
|
|
2421
|
+
});
|
|
2422
|
+
});
|
|
2423
|
+
|
|
2424
|
+
/*
|
|
2425
|
+
* The snapshot arrives on a response header, so it is empty for a moment
|
|
2426
|
+
* after login. A disabled button then would blame somebody who may well
|
|
2427
|
+
* hold the permission, so the affordances stay hidden instead.
|
|
2428
|
+
*/
|
|
2429
|
+
describe("while the permission snapshot is still empty", () => {
|
|
2430
|
+
beforeEach(() => {
|
|
2431
|
+
permissionsForTest = [];
|
|
2432
|
+
});
|
|
2433
|
+
|
|
2434
|
+
test("Update Content is hidden, but the Content card stays", async () => {
|
|
2435
|
+
await renderLoaded(staticVariable());
|
|
2436
|
+
|
|
2437
|
+
expect(screen.getByTestId("card:Content")).toBeInTheDocument();
|
|
2438
|
+
expect(
|
|
2439
|
+
screen.queryByTestId("card-button:Update Content"),
|
|
2440
|
+
).not.toBeInTheDocument();
|
|
2441
|
+
expect(card("Content").buttons).toEqual([]);
|
|
2442
|
+
});
|
|
2443
|
+
|
|
2444
|
+
test("Update Credentials and Refresh now are hidden, but the cards stay", async () => {
|
|
2445
|
+
await renderLoaded(oauthVariable());
|
|
2446
|
+
|
|
2447
|
+
expect(screen.getByTestId("card:Access Token")).toBeInTheDocument();
|
|
2448
|
+
expect(
|
|
2449
|
+
screen.getByTestId(`detail:${OAUTH_SETTINGS_CARD}`),
|
|
2450
|
+
).toBeInTheDocument();
|
|
2451
|
+
expect(
|
|
2452
|
+
screen.queryByTestId("card-button:Update Credentials"),
|
|
2453
|
+
).not.toBeInTheDocument();
|
|
2454
|
+
expect(detail(OAUTH_SETTINGS_CARD).cardProps.buttons).toEqual([]);
|
|
2455
|
+
expect(screen.queryByTestId("refresh-now")).not.toBeInTheDocument();
|
|
2456
|
+
expect(tokenStatus().refreshAction).toBeUndefined();
|
|
2457
|
+
});
|
|
2458
|
+
});
|
|
2459
|
+
|
|
2460
|
+
test("a master admin may update without any project permission", async () => {
|
|
2461
|
+
permissionsForTest = [];
|
|
2462
|
+
isMasterAdminForTest = true;
|
|
2463
|
+
|
|
2464
|
+
await renderLoaded(oauthVariable());
|
|
2465
|
+
|
|
2466
|
+
expect(screen.getByTestId("card-button:Update Credentials")).toBeEnabled();
|
|
2467
|
+
expect(screen.getByTestId("refresh-now")).toBeEnabled();
|
|
2468
|
+
});
|
|
2469
|
+
});
|
|
2470
|
+
|
|
2471
|
+
describe("deleting the variable", () => {
|
|
2472
|
+
test("the delete card deletes this workflow variable", async () => {
|
|
2473
|
+
await renderLoaded(staticVariable());
|
|
2474
|
+
|
|
2475
|
+
expect(capturedDeleteProps?.modelType).toBe(WorkflowVariable);
|
|
2476
|
+
expect(capturedDeleteProps?.modelId.toString()).toBe(
|
|
2477
|
+
VARIABLE_ID.toString(),
|
|
2478
|
+
);
|
|
2479
|
+
});
|
|
2480
|
+
|
|
2481
|
+
test("an OAuth 2.0 variable can be deleted too", async () => {
|
|
2482
|
+
await renderLoaded(oauthVariable());
|
|
2483
|
+
|
|
2484
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
2485
|
+
expect(capturedDeleteProps?.modelId.toString()).toBe(
|
|
2486
|
+
VARIABLE_ID.toString(),
|
|
2487
|
+
);
|
|
2488
|
+
});
|
|
2489
|
+
|
|
2490
|
+
test("after a delete the global page goes back to the global variables", async () => {
|
|
2491
|
+
await renderLoaded(staticVariable());
|
|
2492
|
+
|
|
2493
|
+
act(() => {
|
|
2494
|
+
capturedDeleteProps?.onDeleteSuccess();
|
|
2495
|
+
});
|
|
2496
|
+
|
|
2497
|
+
expect(navigate).toHaveBeenCalledTimes(1);
|
|
2498
|
+
expect(String(navigate.mock.calls[0]![0])).toBe(
|
|
2499
|
+
`/dashboard/${PROJECT_ID.toString()}/workflows/variables`,
|
|
2500
|
+
);
|
|
2501
|
+
});
|
|
2502
|
+
|
|
2503
|
+
test("after a delete a workflow's page goes back to that workflow's variables", async () => {
|
|
2504
|
+
await renderLoaded(localStaticVariable(), WORKFLOW_ID);
|
|
2505
|
+
|
|
2506
|
+
act(() => {
|
|
2507
|
+
capturedDeleteProps?.onDeleteSuccess();
|
|
2508
|
+
});
|
|
2509
|
+
|
|
2510
|
+
expect(navigate).toHaveBeenCalledTimes(1);
|
|
2511
|
+
expect(String(navigate.mock.calls[0]![0])).toBe(
|
|
2512
|
+
`/dashboard/${PROJECT_ID.toString()}/workflows/${WORKFLOW_ID.toString()}/variables`,
|
|
2513
|
+
);
|
|
2514
|
+
});
|
|
2515
|
+
});
|
|
2516
|
+
|
|
2517
|
+
/*
|
|
2518
|
+
* The list's row actions (Show ID, Update Content, Update Credentials, Edit
|
|
2519
|
+
* Details, Delete) and its Token column's Refresh now all moved here. If one
|
|
2520
|
+
* goes missing from the page, it is gone from the product.
|
|
2521
|
+
*/
|
|
2522
|
+
describe("everything the list's row actions used to do", () => {
|
|
2523
|
+
test("a static variable's page can show its id, edit it, replace its content and delete it", async () => {
|
|
2524
|
+
await renderLoaded(staticVariable());
|
|
2525
|
+
|
|
2526
|
+
expect(fieldNames(detail(DETAILS_CARD).modelDetailProps.fields)).toContain(
|
|
2527
|
+
"_id",
|
|
2528
|
+
);
|
|
2529
|
+
expect(detail(DETAILS_CARD).isEditable).toBe(true);
|
|
2530
|
+
expect(
|
|
2531
|
+
screen.getByTestId("card-button:Update Content"),
|
|
2532
|
+
).toBeInTheDocument();
|
|
2533
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
test("an OAuth 2.0 variable's page can show its id, edit it, replace its credentials, refresh its token and delete it", async () => {
|
|
2537
|
+
await renderLoaded(oauthVariable());
|
|
2538
|
+
|
|
2539
|
+
expect(fieldNames(detail(DETAILS_CARD).modelDetailProps.fields)).toContain(
|
|
2540
|
+
"_id",
|
|
2541
|
+
);
|
|
2542
|
+
expect(detail(DETAILS_CARD).isEditable).toBe(true);
|
|
2543
|
+
expect(detail(OAUTH_SETTINGS_CARD).isEditable).toBe(true);
|
|
2544
|
+
expect(
|
|
2545
|
+
screen.getByTestId("card-button:Update Credentials"),
|
|
2546
|
+
).toBeInTheDocument();
|
|
2547
|
+
expect(screen.getByTestId("refresh-now")).toBeInTheDocument();
|
|
2548
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
2549
|
+
});
|
|
2550
|
+
|
|
2551
|
+
test("the same doors are there on a workflow's page", async () => {
|
|
2552
|
+
await renderLoaded(localOAuthVariable(), WORKFLOW_ID);
|
|
2553
|
+
|
|
2554
|
+
expect(detail(DETAILS_CARD).isEditable).toBe(true);
|
|
2555
|
+
expect(detail(OAUTH_SETTINGS_CARD).isEditable).toBe(true);
|
|
2556
|
+
expect(
|
|
2557
|
+
screen.getByTestId("card-button:Update Credentials"),
|
|
2558
|
+
).toBeInTheDocument();
|
|
2559
|
+
expect(screen.getByTestId("refresh-now")).toBeInTheDocument();
|
|
2560
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
2561
|
+
});
|
|
2562
|
+
});
|
|
2563
|
+
|
|
2564
|
+
/*
|
|
2565
|
+
* The page wrappers key WorkflowVariableView by the ids they read from the
|
|
2566
|
+
* route. React Router keeps the page mounted when only a param changes, so
|
|
2567
|
+
* without the key, going straight from one variable's page to another's would
|
|
2568
|
+
* hand the new variable to the old instance - with the old one's modals, notes
|
|
2569
|
+
* and requests still attached to it.
|
|
2570
|
+
*/
|
|
2571
|
+
describe("the page wrappers", () => {
|
|
2572
|
+
/*
|
|
2573
|
+
* The variable a wrapper moves on to: another one, of the other kind, so
|
|
2574
|
+
* the layout alone says which variable is on screen.
|
|
2575
|
+
*/
|
|
2576
|
+
function otherStaticVariable(
|
|
2577
|
+
overrides?: Partial<Record<string, unknown>>,
|
|
2578
|
+
): WorkflowVariable {
|
|
2579
|
+
return staticVariable({
|
|
2580
|
+
_id: OTHER_VARIABLE_ID.toString(),
|
|
2581
|
+
name: "OTHER",
|
|
2582
|
+
...(overrides || {}),
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
function contentNote(): HTMLElement {
|
|
2587
|
+
return screen.getByTestId("workflow-variable-content-note");
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// Sets the page's "Content updated" note, which lives only in the page.
|
|
2591
|
+
function markContentUpdated(): void {
|
|
2592
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
2593
|
+
|
|
2594
|
+
act(() => {
|
|
2595
|
+
capturedContentModalProps?.onSuccess();
|
|
2596
|
+
});
|
|
2597
|
+
|
|
2598
|
+
expect(contentNote()).toHaveTextContent("Content updated.");
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// The other variable's page, whole, and nothing of the previous one's.
|
|
2602
|
+
function expectOtherStaticVariableShown(): void {
|
|
2603
|
+
expect(screen.queryByTestId("page-loader")).not.toBeInTheDocument();
|
|
2604
|
+
expect(screen.getByTestId("card:Content")).toBeInTheDocument();
|
|
2605
|
+
expect(screen.queryByTestId("card:Access Token")).not.toBeInTheDocument();
|
|
2606
|
+
expect(screen.queryByTestId("token-status")).not.toBeInTheDocument();
|
|
2607
|
+
expect(
|
|
2608
|
+
screen.queryByTestId(`detail:${OAUTH_SETTINGS_CARD}`),
|
|
2609
|
+
).not.toBeInTheDocument();
|
|
2610
|
+
expect(screen.queryByTestId("token-refresh-modal")).not.toBeInTheDocument();
|
|
2611
|
+
expect(
|
|
2612
|
+
screen.queryByTestId("update-content-modal"),
|
|
2613
|
+
).not.toBeInTheDocument();
|
|
2614
|
+
expect(detail(DETAILS_CARD).modelDetailProps.modelId.toString()).toBe(
|
|
2615
|
+
OTHER_VARIABLE_ID.toString(),
|
|
2616
|
+
);
|
|
2617
|
+
expect(capturedDeleteProps?.modelId.toString()).toBe(
|
|
2618
|
+
OTHER_VARIABLE_ID.toString(),
|
|
2619
|
+
);
|
|
2620
|
+
|
|
2621
|
+
// The variable the page acts on is the one it read for OTHER.
|
|
2622
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
2623
|
+
expect(capturedContentModalProps?.variable.name).toBe("OTHER");
|
|
2624
|
+
|
|
2625
|
+
// Closed again, so the check can run again later in the same test.
|
|
2626
|
+
act(() => {
|
|
2627
|
+
capturedContentModalProps?.onClose();
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
type WrapperUnderTest = {
|
|
2632
|
+
renderPage: () => ReturnType<typeof render>;
|
|
2633
|
+
moveToOtherVariable: (result: ReturnType<typeof render>) => void;
|
|
2634
|
+
// An OAuth 2.0 variable, so it has a Refresh now.
|
|
2635
|
+
previousVariable: () => WorkflowVariable;
|
|
2636
|
+
};
|
|
2637
|
+
|
|
2638
|
+
type HeldRefreshRace = {
|
|
2639
|
+
refresh: Deferred<unknown>;
|
|
2640
|
+
otherRead: Deferred<unknown>;
|
|
2641
|
+
previousFollowUpRead: Deferred<unknown>;
|
|
2642
|
+
};
|
|
2643
|
+
|
|
2644
|
+
/*
|
|
2645
|
+
* The race the key closes: Refresh now on an OAuth 2.0 variable, its request
|
|
2646
|
+
* still out, and the page moves straight to another variable. The refresh,
|
|
2647
|
+
* the other variable's read and the read the refresh makes afterwards are
|
|
2648
|
+
* all held, so each test picks the order they land in.
|
|
2649
|
+
*/
|
|
2650
|
+
async function startRefreshThenMove(
|
|
2651
|
+
wrapper: WrapperUnderTest,
|
|
2652
|
+
): Promise<HeldRefreshRace> {
|
|
2653
|
+
const refresh: Deferred<unknown> = deferred<unknown>();
|
|
2654
|
+
apiPost.mockReturnValue(refresh.promise);
|
|
2655
|
+
|
|
2656
|
+
getItem.mockResolvedValueOnce(wrapper.previousVariable());
|
|
2657
|
+
const otherRead: Deferred<unknown> = holdNextRead();
|
|
2658
|
+
const previousFollowUpRead: Deferred<unknown> = holdNextRead();
|
|
2659
|
+
|
|
2660
|
+
const result: ReturnType<typeof render> = wrapper.renderPage();
|
|
2661
|
+
|
|
2662
|
+
await waitForPage();
|
|
2663
|
+
expectOAuthPageShown();
|
|
2664
|
+
|
|
2665
|
+
await clickRefreshNow();
|
|
2666
|
+
|
|
2667
|
+
expect(apiPost).toHaveBeenCalledTimes(1);
|
|
2668
|
+
expect(screen.getByTestId("refresh-now")).toBeDisabled();
|
|
2669
|
+
|
|
2670
|
+
wrapper.moveToOtherVariable(result);
|
|
2671
|
+
|
|
2672
|
+
await waitForReads(2);
|
|
2673
|
+
expect(getItemCall(1).id.toString()).toBe(OTHER_VARIABLE_ID.toString());
|
|
2674
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
2675
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
2676
|
+
|
|
2677
|
+
return { refresh, otherRead, previousFollowUpRead };
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
/*
|
|
2681
|
+
* The previous variable's page is gone, but its refresh still finishes -
|
|
2682
|
+
* and reads its own variable again, as it always does afterwards.
|
|
2683
|
+
*/
|
|
2684
|
+
async function finishPreviousRefresh(race: HeldRefreshRace): Promise<void> {
|
|
2685
|
+
await act(async () => {
|
|
2686
|
+
race.refresh.resolve({
|
|
2687
|
+
data: { oauthAccessTokenExpiresAt: TOKEN_EXPIRES_AT },
|
|
2688
|
+
});
|
|
2689
|
+
});
|
|
2690
|
+
|
|
2691
|
+
await waitForReads(3);
|
|
2692
|
+
expect(getItemCall(2).id.toString()).toBe(VARIABLE_ID.toString());
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
describe("Workflows > Global Variables > View Variable", () => {
|
|
2696
|
+
function globalPage(): ReactElement {
|
|
2697
|
+
return (
|
|
2698
|
+
<GlobalWorkflowVariableViewPage
|
|
2699
|
+
{...({} as unknown as React.ComponentProps<
|
|
2700
|
+
typeof GlobalWorkflowVariableViewPage
|
|
2701
|
+
>)}
|
|
2702
|
+
/>
|
|
2703
|
+
);
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
function renderGlobalPage(): ReturnType<typeof render> {
|
|
2707
|
+
return render(globalPage());
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// What following a link to another variable's page does to the wrapper.
|
|
2711
|
+
function moveGlobalPageTo(
|
|
2712
|
+
result: ReturnType<typeof render>,
|
|
2713
|
+
variableId: ObjectID,
|
|
2714
|
+
): void {
|
|
2715
|
+
lastParams = { 0: variableId };
|
|
2716
|
+
result.rerender(globalPage());
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
const globalWrapper: WrapperUnderTest = {
|
|
2720
|
+
renderPage: renderGlobalPage,
|
|
2721
|
+
moveToOtherVariable: (result: ReturnType<typeof render>): void => {
|
|
2722
|
+
moveGlobalPageTo(result, OTHER_VARIABLE_ID);
|
|
2723
|
+
},
|
|
2724
|
+
previousVariable: (): WorkflowVariable => {
|
|
2725
|
+
return oauthVariable();
|
|
2726
|
+
},
|
|
2727
|
+
};
|
|
2728
|
+
|
|
2729
|
+
beforeEach(() => {
|
|
2730
|
+
// /dashboard/:projectId/workflows/variables/:id
|
|
2731
|
+
lastParams = { 0: VARIABLE_ID };
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2734
|
+
test("reads the variable named by the last route segment", async () => {
|
|
2735
|
+
getItem.mockResolvedValue(staticVariable());
|
|
2736
|
+
|
|
2737
|
+
renderGlobalPage();
|
|
2738
|
+
|
|
2739
|
+
await waitForPage();
|
|
2740
|
+
|
|
2741
|
+
expect(getLastParamAsObjectID).toHaveBeenCalledWith(0);
|
|
2742
|
+
expect(getItemCall().id.toString()).toBe(VARIABLE_ID.toString());
|
|
2743
|
+
expect(capturedDeleteProps?.modelId.toString()).toBe(
|
|
2744
|
+
VARIABLE_ID.toString(),
|
|
2745
|
+
);
|
|
2746
|
+
});
|
|
2747
|
+
|
|
2748
|
+
test("is the global page: a global reference, and back to the global list after a delete", async () => {
|
|
2749
|
+
getItem.mockResolvedValue(staticVariable());
|
|
2750
|
+
|
|
2751
|
+
renderGlobalPage();
|
|
2752
|
+
|
|
2753
|
+
await waitForPage();
|
|
2754
|
+
|
|
2755
|
+
expect(
|
|
2756
|
+
within(
|
|
2757
|
+
renderDetailElement(
|
|
2758
|
+
DETAILS_CARD,
|
|
2759
|
+
"Use in Workflows",
|
|
2760
|
+
staticVariable(),
|
|
2761
|
+
),
|
|
2762
|
+
).getByTestId("workflow-variable-reference"),
|
|
2763
|
+
).toHaveTextContent("{{global.variables.API_KEY}}");
|
|
2764
|
+
|
|
2765
|
+
act(() => {
|
|
2766
|
+
capturedDeleteProps?.onDeleteSuccess();
|
|
2767
|
+
});
|
|
2768
|
+
|
|
2769
|
+
expect(String(navigate.mock.calls[0]![0])).toBe(
|
|
2770
|
+
`/dashboard/${PROJECT_ID.toString()}/workflows/variables`,
|
|
2771
|
+
);
|
|
2772
|
+
});
|
|
2773
|
+
|
|
2774
|
+
test("refuses a variable that belongs to a workflow", async () => {
|
|
2775
|
+
getItem.mockResolvedValue(localStaticVariable());
|
|
2776
|
+
|
|
2777
|
+
renderGlobalPage();
|
|
2778
|
+
|
|
2779
|
+
expect(
|
|
2780
|
+
await screen.findByText(/This variable belongs to a workflow/),
|
|
2781
|
+
).toBeInTheDocument();
|
|
2782
|
+
});
|
|
2783
|
+
|
|
2784
|
+
describe("moving straight to another variable's page", () => {
|
|
2785
|
+
test("mounts a fresh page: the loader again, a read of the new variable, and no note from the previous one", async () => {
|
|
2786
|
+
getItem.mockResolvedValueOnce(staticVariable());
|
|
2787
|
+
const otherRead: Deferred<unknown> = holdNextRead();
|
|
2788
|
+
|
|
2789
|
+
const result: ReturnType<typeof render> = renderGlobalPage();
|
|
2790
|
+
|
|
2791
|
+
await waitForPage();
|
|
2792
|
+
|
|
2793
|
+
markContentUpdated();
|
|
2794
|
+
|
|
2795
|
+
moveGlobalPageTo(result, OTHER_VARIABLE_ID);
|
|
2796
|
+
|
|
2797
|
+
await waitForReads(2);
|
|
2798
|
+
expect(getItemCall(1).id.toString()).toBe(OTHER_VARIABLE_ID.toString());
|
|
2799
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
2800
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
2801
|
+
|
|
2802
|
+
await act(async () => {
|
|
2803
|
+
otherRead.resolve(otherStaticVariable());
|
|
2804
|
+
});
|
|
2805
|
+
|
|
2806
|
+
await waitForPage();
|
|
2807
|
+
|
|
2808
|
+
expect(contentNote()).toHaveTextContent("Hidden.");
|
|
2809
|
+
expectOtherStaticVariableShown();
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2812
|
+
// The content modal was for the previous variable's content.
|
|
2813
|
+
test("an open content modal does not follow onto the new variable's page", async () => {
|
|
2814
|
+
getItem.mockResolvedValueOnce(staticVariable());
|
|
2815
|
+
const otherRead: Deferred<unknown> = holdNextRead();
|
|
2816
|
+
|
|
2817
|
+
const result: ReturnType<typeof render> = renderGlobalPage();
|
|
2818
|
+
|
|
2819
|
+
await waitForPage();
|
|
2820
|
+
|
|
2821
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
2822
|
+
expect(screen.getByTestId("update-content-modal")).toBeInTheDocument();
|
|
2823
|
+
|
|
2824
|
+
moveGlobalPageTo(result, OTHER_VARIABLE_ID);
|
|
2825
|
+
|
|
2826
|
+
await waitForReads(2);
|
|
2827
|
+
|
|
2828
|
+
await act(async () => {
|
|
2829
|
+
otherRead.resolve(otherStaticVariable());
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2832
|
+
await waitForPage();
|
|
2833
|
+
|
|
2834
|
+
expectOtherStaticVariableShown();
|
|
2835
|
+
});
|
|
2836
|
+
|
|
2837
|
+
test("the previous variable's token outcome does not follow onto the new variable's page", async () => {
|
|
2838
|
+
getItem.mockResolvedValue(oauthVariable());
|
|
2839
|
+
|
|
2840
|
+
const result: ReturnType<typeof render> = renderGlobalPage();
|
|
2841
|
+
|
|
2842
|
+
await waitForPage();
|
|
2843
|
+
|
|
2844
|
+
await clickRefreshNow();
|
|
2845
|
+
await waitForOutcome();
|
|
2846
|
+
// The read the refresh makes afterwards.
|
|
2847
|
+
await waitForReads(2);
|
|
2848
|
+
|
|
2849
|
+
expect(screen.getByTestId("token-refresh-modal")).toBeInTheDocument();
|
|
2850
|
+
|
|
2851
|
+
const otherRead: Deferred<unknown> = holdNextRead();
|
|
2852
|
+
|
|
2853
|
+
moveGlobalPageTo(result, OTHER_VARIABLE_ID);
|
|
2854
|
+
|
|
2855
|
+
await waitForReads(3);
|
|
2856
|
+
expect(getItemCall(2).id.toString()).toBe(OTHER_VARIABLE_ID.toString());
|
|
2857
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
2858
|
+
|
|
2859
|
+
await act(async () => {
|
|
2860
|
+
otherRead.resolve(otherStaticVariable());
|
|
2861
|
+
});
|
|
2862
|
+
|
|
2863
|
+
await waitForPage();
|
|
2864
|
+
|
|
2865
|
+
expectOtherStaticVariableShown();
|
|
2866
|
+
});
|
|
2867
|
+
|
|
2868
|
+
/*
|
|
2869
|
+
* One instance for both variables would show the previous variable's
|
|
2870
|
+
* outcome modal over the new page, and its follow-up read would put the
|
|
2871
|
+
* previous variable back on screen.
|
|
2872
|
+
*/
|
|
2873
|
+
test("a token refresh still out for the previous variable never lands on the new variable's page", async () => {
|
|
2874
|
+
const race: HeldRefreshRace = await startRefreshThenMove(globalWrapper);
|
|
2875
|
+
|
|
2876
|
+
await act(async () => {
|
|
2877
|
+
race.otherRead.resolve(otherStaticVariable());
|
|
2878
|
+
});
|
|
2879
|
+
|
|
2880
|
+
await waitForPage();
|
|
2881
|
+
expectOtherStaticVariableShown();
|
|
2882
|
+
|
|
2883
|
+
await finishPreviousRefresh(race);
|
|
2884
|
+
|
|
2885
|
+
await act(async () => {
|
|
2886
|
+
race.previousFollowUpRead.resolve(oauthVariable());
|
|
2887
|
+
});
|
|
2888
|
+
|
|
2889
|
+
expectOtherStaticVariableShown();
|
|
2890
|
+
});
|
|
2891
|
+
|
|
2892
|
+
/*
|
|
2893
|
+
* One instance for both variables would let the refresh's follow-up read
|
|
2894
|
+
* outdate the new variable's read, and when that follow-up failed the
|
|
2895
|
+
* page would sit on the loader for good.
|
|
2896
|
+
*/
|
|
2897
|
+
test("a token refresh that settles while the new variable loads leaves that load alone", async () => {
|
|
2898
|
+
const race: HeldRefreshRace = await startRefreshThenMove(globalWrapper);
|
|
2899
|
+
|
|
2900
|
+
await finishPreviousRefresh(race);
|
|
2901
|
+
|
|
2902
|
+
await act(async () => {
|
|
2903
|
+
race.previousFollowUpRead.reject(
|
|
2904
|
+
new Error("The API is unreachable."),
|
|
2905
|
+
);
|
|
2906
|
+
});
|
|
2907
|
+
|
|
2908
|
+
// Still waiting for the variable the page is on.
|
|
2909
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
2910
|
+
expect(
|
|
2911
|
+
screen.queryByTestId("token-refresh-modal"),
|
|
2912
|
+
).not.toBeInTheDocument();
|
|
2913
|
+
|
|
2914
|
+
await act(async () => {
|
|
2915
|
+
race.otherRead.resolve(otherStaticVariable());
|
|
2916
|
+
});
|
|
2917
|
+
|
|
2918
|
+
await waitForPage();
|
|
2919
|
+
|
|
2920
|
+
expect(
|
|
2921
|
+
screen.queryByText("The API is unreachable."),
|
|
2922
|
+
).not.toBeInTheDocument();
|
|
2923
|
+
expectOtherStaticVariableShown();
|
|
2924
|
+
});
|
|
2925
|
+
|
|
2926
|
+
// A re-render of the same route is not a new page.
|
|
2927
|
+
test("a re-render for the same variable keeps the page as it is", async () => {
|
|
2928
|
+
getItem.mockResolvedValueOnce(staticVariable());
|
|
2929
|
+
|
|
2930
|
+
const result: ReturnType<typeof render> = renderGlobalPage();
|
|
2931
|
+
|
|
2932
|
+
await waitForPage();
|
|
2933
|
+
pageLoaderRenderCount = 0;
|
|
2934
|
+
|
|
2935
|
+
markContentUpdated();
|
|
2936
|
+
|
|
2937
|
+
// The same id, as a new ObjectID - as Navigation hands it back.
|
|
2938
|
+
moveGlobalPageTo(result, new ObjectID(VARIABLE_ID.toString()));
|
|
2939
|
+
|
|
2940
|
+
await act(async () => {
|
|
2941
|
+
await Promise.resolve();
|
|
2942
|
+
});
|
|
2943
|
+
|
|
2944
|
+
expect(getItem).toHaveBeenCalledTimes(1);
|
|
2945
|
+
expectNoLoaderSinceLoad();
|
|
2946
|
+
expect(contentNote()).toHaveTextContent("Content updated.");
|
|
2947
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
2948
|
+
});
|
|
2949
|
+
});
|
|
2950
|
+
});
|
|
2951
|
+
|
|
2952
|
+
describe("Workflow > Workflow Variables > View Variable", () => {
|
|
2953
|
+
function localPage(): ReactElement {
|
|
2954
|
+
return (
|
|
2955
|
+
<LocalWorkflowVariableViewPage
|
|
2956
|
+
{...({} as unknown as React.ComponentProps<
|
|
2957
|
+
typeof LocalWorkflowVariableViewPage
|
|
2958
|
+
>)}
|
|
2959
|
+
/>
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
function renderLocalPage(): ReturnType<typeof render> {
|
|
2964
|
+
return render(localPage());
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
// What following a link to another variable's page does to the wrapper.
|
|
2968
|
+
function moveLocalPageTo(
|
|
2969
|
+
result: ReturnType<typeof render>,
|
|
2970
|
+
params: { variableId: ObjectID; workflowId: ObjectID },
|
|
2971
|
+
): void {
|
|
2972
|
+
lastParams = { 0: params.variableId, 2: params.workflowId };
|
|
2973
|
+
result.rerender(localPage());
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
const localWrapper: WrapperUnderTest = {
|
|
2977
|
+
renderPage: renderLocalPage,
|
|
2978
|
+
moveToOtherVariable: (result: ReturnType<typeof render>): void => {
|
|
2979
|
+
moveLocalPageTo(result, {
|
|
2980
|
+
variableId: OTHER_VARIABLE_ID,
|
|
2981
|
+
workflowId: WORKFLOW_ID,
|
|
2982
|
+
});
|
|
2983
|
+
},
|
|
2984
|
+
previousVariable: (): WorkflowVariable => {
|
|
2985
|
+
return localOAuthVariable();
|
|
2986
|
+
},
|
|
2987
|
+
};
|
|
2988
|
+
|
|
2989
|
+
beforeEach(() => {
|
|
2990
|
+
// /dashboard/:projectId/workflows/:id/variables/:subModelId
|
|
2991
|
+
lastParams = { 0: VARIABLE_ID, 2: WORKFLOW_ID };
|
|
2992
|
+
});
|
|
2993
|
+
|
|
2994
|
+
test("reads the variable from the last segment and the workflow from two before it", async () => {
|
|
2995
|
+
getItem.mockResolvedValue(localStaticVariable());
|
|
2996
|
+
|
|
2997
|
+
renderLocalPage();
|
|
2998
|
+
|
|
2999
|
+
await waitForPage();
|
|
3000
|
+
|
|
3001
|
+
expect(getLastParamAsObjectID).toHaveBeenCalledWith(0);
|
|
3002
|
+
expect(getLastParamAsObjectID).toHaveBeenCalledWith(2);
|
|
3003
|
+
expect(getItemCall().id.toString()).toBe(VARIABLE_ID.toString());
|
|
3004
|
+
expect(capturedDeleteProps?.modelId.toString()).toBe(
|
|
3005
|
+
VARIABLE_ID.toString(),
|
|
3006
|
+
);
|
|
3007
|
+
});
|
|
3008
|
+
|
|
3009
|
+
test("is the workflow's page: a local reference, and back to its variables after a delete", async () => {
|
|
3010
|
+
getItem.mockResolvedValue(localStaticVariable());
|
|
3011
|
+
|
|
3012
|
+
renderLocalPage();
|
|
3013
|
+
|
|
3014
|
+
await waitForPage();
|
|
3015
|
+
|
|
3016
|
+
expect(
|
|
3017
|
+
within(
|
|
3018
|
+
renderDetailElement(
|
|
3019
|
+
DETAILS_CARD,
|
|
3020
|
+
"Use in Workflows",
|
|
3021
|
+
localStaticVariable(),
|
|
3022
|
+
),
|
|
3023
|
+
).getByTestId("workflow-variable-reference"),
|
|
3024
|
+
).toHaveTextContent("{{local.variables.API_KEY}}");
|
|
3025
|
+
|
|
3026
|
+
act(() => {
|
|
3027
|
+
capturedDeleteProps?.onDeleteSuccess();
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
expect(String(navigate.mock.calls[0]![0])).toBe(
|
|
3031
|
+
`/dashboard/${PROJECT_ID.toString()}/workflows/${WORKFLOW_ID.toString()}/variables`,
|
|
3032
|
+
);
|
|
3033
|
+
});
|
|
3034
|
+
|
|
3035
|
+
test("refuses another workflow's variable", async () => {
|
|
3036
|
+
getItem.mockResolvedValue(
|
|
3037
|
+
staticVariable({ workflowId: OTHER_WORKFLOW_ID }),
|
|
3038
|
+
);
|
|
3039
|
+
|
|
3040
|
+
renderLocalPage();
|
|
3041
|
+
|
|
3042
|
+
expect(
|
|
3043
|
+
await screen.findByText(
|
|
3044
|
+
"This variable does not belong to this workflow.",
|
|
3045
|
+
),
|
|
3046
|
+
).toBeInTheDocument();
|
|
3047
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
3048
|
+
});
|
|
3049
|
+
|
|
3050
|
+
test("refuses a global variable", async () => {
|
|
3051
|
+
getItem.mockResolvedValue(staticVariable());
|
|
3052
|
+
|
|
3053
|
+
renderLocalPage();
|
|
3054
|
+
|
|
3055
|
+
expect(
|
|
3056
|
+
await screen.findByText(
|
|
3057
|
+
"This variable does not belong to this workflow.",
|
|
3058
|
+
),
|
|
3059
|
+
).toBeInTheDocument();
|
|
3060
|
+
});
|
|
3061
|
+
|
|
3062
|
+
// A deleted variable's link: it is gone, not someone else's.
|
|
3063
|
+
test("says a missing variable could not be found, not that it belongs elsewhere", async () => {
|
|
3064
|
+
getItem.mockResolvedValue(missingVariable());
|
|
3065
|
+
|
|
3066
|
+
renderLocalPage();
|
|
3067
|
+
|
|
3068
|
+
expect(await screen.findByText(NOT_FOUND_MESSAGE)).toBeInTheDocument();
|
|
3069
|
+
expect(
|
|
3070
|
+
screen.queryByText("This variable does not belong to this workflow."),
|
|
3071
|
+
).not.toBeInTheDocument();
|
|
3072
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
3073
|
+
});
|
|
3074
|
+
|
|
3075
|
+
describe("moving straight to another variable's page", () => {
|
|
3076
|
+
test("another variable of the workflow mounts a fresh page, without the previous one's open modal", async () => {
|
|
3077
|
+
getItem.mockResolvedValueOnce(localStaticVariable());
|
|
3078
|
+
const otherRead: Deferred<unknown> = holdNextRead();
|
|
3079
|
+
|
|
3080
|
+
const result: ReturnType<typeof render> = renderLocalPage();
|
|
3081
|
+
|
|
3082
|
+
await waitForPage();
|
|
3083
|
+
|
|
3084
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
3085
|
+
expect(screen.getByTestId("update-content-modal")).toBeInTheDocument();
|
|
3086
|
+
|
|
3087
|
+
moveLocalPageTo(result, {
|
|
3088
|
+
variableId: OTHER_VARIABLE_ID,
|
|
3089
|
+
workflowId: WORKFLOW_ID,
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
await waitForReads(2);
|
|
3093
|
+
expect(getItemCall(1).id.toString()).toBe(OTHER_VARIABLE_ID.toString());
|
|
3094
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
3095
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
3096
|
+
|
|
3097
|
+
await act(async () => {
|
|
3098
|
+
otherRead.resolve(otherStaticVariable({ workflowId: WORKFLOW_ID }));
|
|
3099
|
+
});
|
|
3100
|
+
|
|
3101
|
+
await waitForPage();
|
|
3102
|
+
|
|
3103
|
+
expect(contentNote()).toHaveTextContent("Hidden.");
|
|
3104
|
+
expectOtherStaticVariableShown();
|
|
3105
|
+
});
|
|
3106
|
+
|
|
3107
|
+
/*
|
|
3108
|
+
* The same variable id under another workflow is another page: the scope
|
|
3109
|
+
* check, the reference and the way back after a delete all hang on the
|
|
3110
|
+
* workflow.
|
|
3111
|
+
*/
|
|
3112
|
+
test("another workflow's page mounts a fresh page too", async () => {
|
|
3113
|
+
getItem.mockResolvedValueOnce(localStaticVariable());
|
|
3114
|
+
const nextRead: Deferred<unknown> = holdNextRead();
|
|
3115
|
+
|
|
3116
|
+
const result: ReturnType<typeof render> = renderLocalPage();
|
|
3117
|
+
|
|
3118
|
+
await waitForPage();
|
|
3119
|
+
|
|
3120
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
3121
|
+
expect(screen.getByTestId("update-content-modal")).toBeInTheDocument();
|
|
3122
|
+
|
|
3123
|
+
moveLocalPageTo(result, {
|
|
3124
|
+
variableId: VARIABLE_ID,
|
|
3125
|
+
workflowId: OTHER_WORKFLOW_ID,
|
|
3126
|
+
});
|
|
3127
|
+
|
|
3128
|
+
await waitForReads(2);
|
|
3129
|
+
expect(getItemCall(1).id.toString()).toBe(VARIABLE_ID.toString());
|
|
3130
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
3131
|
+
expect(screen.queryByTestId("model-delete")).not.toBeInTheDocument();
|
|
3132
|
+
|
|
3133
|
+
await act(async () => {
|
|
3134
|
+
nextRead.resolve(staticVariable({ workflowId: OTHER_WORKFLOW_ID }));
|
|
3135
|
+
});
|
|
3136
|
+
|
|
3137
|
+
await waitForPage();
|
|
3138
|
+
|
|
3139
|
+
expect(
|
|
3140
|
+
screen.queryByTestId("update-content-modal"),
|
|
3141
|
+
).not.toBeInTheDocument();
|
|
3142
|
+
expect(contentNote()).toHaveTextContent("Hidden.");
|
|
3143
|
+
|
|
3144
|
+
act(() => {
|
|
3145
|
+
capturedDeleteProps?.onDeleteSuccess();
|
|
3146
|
+
});
|
|
3147
|
+
|
|
3148
|
+
expect(String(navigate.mock.calls[0]![0])).toBe(
|
|
3149
|
+
`/dashboard/${PROJECT_ID.toString()}/workflows/${OTHER_WORKFLOW_ID.toString()}/variables`,
|
|
3150
|
+
);
|
|
3151
|
+
});
|
|
3152
|
+
|
|
3153
|
+
test("a token refresh still out for the previous variable never lands on the new variable's page", async () => {
|
|
3154
|
+
const race: HeldRefreshRace = await startRefreshThenMove(localWrapper);
|
|
3155
|
+
|
|
3156
|
+
await act(async () => {
|
|
3157
|
+
race.otherRead.resolve(
|
|
3158
|
+
otherStaticVariable({ workflowId: WORKFLOW_ID }),
|
|
3159
|
+
);
|
|
3160
|
+
});
|
|
3161
|
+
|
|
3162
|
+
await waitForPage();
|
|
3163
|
+
expectOtherStaticVariableShown();
|
|
3164
|
+
|
|
3165
|
+
await finishPreviousRefresh(race);
|
|
3166
|
+
|
|
3167
|
+
await act(async () => {
|
|
3168
|
+
race.previousFollowUpRead.resolve(localOAuthVariable());
|
|
3169
|
+
});
|
|
3170
|
+
|
|
3171
|
+
expectOtherStaticVariableShown();
|
|
3172
|
+
});
|
|
3173
|
+
|
|
3174
|
+
test("a token refresh that settles while the new variable loads leaves that load alone", async () => {
|
|
3175
|
+
const race: HeldRefreshRace = await startRefreshThenMove(localWrapper);
|
|
3176
|
+
|
|
3177
|
+
await finishPreviousRefresh(race);
|
|
3178
|
+
|
|
3179
|
+
await act(async () => {
|
|
3180
|
+
race.previousFollowUpRead.reject(
|
|
3181
|
+
new Error("The API is unreachable."),
|
|
3182
|
+
);
|
|
3183
|
+
});
|
|
3184
|
+
|
|
3185
|
+
expect(screen.getByTestId("page-loader")).toBeInTheDocument();
|
|
3186
|
+
|
|
3187
|
+
await act(async () => {
|
|
3188
|
+
race.otherRead.resolve(
|
|
3189
|
+
otherStaticVariable({ workflowId: WORKFLOW_ID }),
|
|
3190
|
+
);
|
|
3191
|
+
});
|
|
3192
|
+
|
|
3193
|
+
await waitForPage();
|
|
3194
|
+
|
|
3195
|
+
expectOtherStaticVariableShown();
|
|
3196
|
+
});
|
|
3197
|
+
|
|
3198
|
+
// A re-render of the same route is not a new page.
|
|
3199
|
+
test("a re-render for the same workflow and variable keeps the page as it is", async () => {
|
|
3200
|
+
getItem.mockResolvedValueOnce(localStaticVariable());
|
|
3201
|
+
|
|
3202
|
+
const result: ReturnType<typeof render> = renderLocalPage();
|
|
3203
|
+
|
|
3204
|
+
await waitForPage();
|
|
3205
|
+
pageLoaderRenderCount = 0;
|
|
3206
|
+
|
|
3207
|
+
fireEvent.click(screen.getByTestId("card-button:Update Content"));
|
|
3208
|
+
expect(screen.getByTestId("update-content-modal")).toBeInTheDocument();
|
|
3209
|
+
|
|
3210
|
+
// The same ids, as new ObjectIDs - as Navigation hands them back.
|
|
3211
|
+
moveLocalPageTo(result, {
|
|
3212
|
+
variableId: new ObjectID(VARIABLE_ID.toString()),
|
|
3213
|
+
workflowId: new ObjectID(WORKFLOW_ID.toString()),
|
|
3214
|
+
});
|
|
3215
|
+
|
|
3216
|
+
await act(async () => {
|
|
3217
|
+
await Promise.resolve();
|
|
3218
|
+
});
|
|
3219
|
+
|
|
3220
|
+
expect(getItem).toHaveBeenCalledTimes(1);
|
|
3221
|
+
expectNoLoaderSinceLoad();
|
|
3222
|
+
expect(screen.getByTestId("update-content-modal")).toBeInTheDocument();
|
|
3223
|
+
expect(screen.getByTestId("model-delete")).toBeInTheDocument();
|
|
3224
|
+
});
|
|
3225
|
+
});
|
|
3226
|
+
});
|
|
3227
|
+
});
|